-
Notifications
You must be signed in to change notification settings - Fork 121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add unique constraint to reader and readable #78
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -266,6 +266,20 @@ | |
expect(@email2.unread?(@reader)).to be_falsey | ||
end | ||
|
||
it "should mark the rest as read when the first record is not unique" do | ||
Email.mark_as_read! [ @email1 ], for: @reader | ||
|
||
allow(@email1).to receive_message_chain("read_marks.build").and_return(@email1.read_marks.build) | ||
allow(@email1).to receive_message_chain("read_marks.where").and_return([]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of mocking the implementation: What do you think of using two threads to demonstrate that it's really thread-safe now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think database level unique constraint guarantees that no duplicate records will be saved. It's very hard to simulate a non-thread safe condition using threads because they are non-deterministic. Actually, the mocking achieves the same propose and it's deterministic. |
||
|
||
expect do | ||
Email.mark_as_read! [ @email1, @email2 ], for: @reader | ||
end.to change(ReadMark, :count).by(1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ledermann How about this ? think it's more explicit. |
||
|
||
expect(@email1.unread?(@reader)).to be_falsey | ||
expect(@email2.unread?(@reader)).to be_falsey | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please wrap the whole example into a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if nested expectations will work, but will try that. |
||
|
||
it "should perform less queries if the objects are already read" do | ||
Email.mark_as_read! :all, :for => @reader | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line seems not to be required. A rollback is already made on this exception, or am I wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially, the code here was
next
, but it did not work and I debugged for quite long time. I realised that most of the databases do not support nested transactions, and AR uses savepoints to mitigate that, thisRollback
is needed in order to let AR rollback to the savepoint. ( Check here http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html )There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my tests with SQLite and MySQL removing the
raise ActiveRecord::Rollback
works fine. IMHO there is no need for a rollback here, because there is nothing to rollback. A Rollback is required if you want to undo a successful write operation, but we don't have one here, because the only write operation has already failed.Some further investigation leads me to the following simplification of your change:
No inner transaction needed, no thinking about savepoints, just add a rescue for the
save!
operation. What do you think?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yah, MySQL works, but Postgres won't work. ( see Nested Transactions section in the link which I posted earlier ).
The solution you suggested will rollback the entire transaction, which means all elements in that array will be rollback if one of them is not unique. Is that what we want ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, Postgres needs some special handling, because of the error message "current transaction is aborted, commands ignored until end of transaction block". The inner transaction with the additional rollback both seems to be needed to make it compatible with Postgres. For SQLite and MySQL it's not needed.