-
Notifications
You must be signed in to change notification settings - Fork 324
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
Fix change tracking when fetching from collections or associations #298
Conversation
a5bd627
to
d6b1b47
Compare
Just rebased this for your convenience. Please merge! 😺 |
d6b1b47
to
825db00
Compare
Rebased again! |
Hmmm. This newest fix only works with Rails 4.2+. I therefore made it optional by checking for the presence of a method but I assumed that the specs would always use the latest version. I forgot that Travis also checks older ones. |
hi @chewi I've just got commit access on this repo and plan to get back on track with maintenance over time - will be in touch soon! |
6825f06
to
550663a
Compare
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.
specs look 💯.
left a few comments before merging. can you also take a look at the travis build?
@edtjones we'll need to update ActiveModel too which should help us clean up some of the dirty attributes logic.
lib/her/model/attributes.rb
Outdated
@@ -25,6 +25,7 @@ def initialize(attributes={}) | |||
|
|||
attributes = self.class.default_scope.apply_to(attributes) | |||
assign_attributes(attributes) | |||
@changed_attributes = {} if persisted? |
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 should be covered in #404.
entry.send("#{inverse_of}=", @parent) | ||
|
||
# This fix requires ActiveModel 4.2+. | ||
if entry.respond_to?(:clear_attribute_changes, true) |
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.
nice 👍
Once we update ActiveModel
we should probably look at clear_changes_information
after a record is fetched or reloaded and changes_applied
after persistence.
spec/model/dirty_spec.rb
Outdated
builder.use Her::Middleware::FirstLevelParseJSON | ||
builder.use Faraday::Request::UrlEncoded | ||
builder.adapter :test do |stub| | ||
stub.get("/users/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke" }.to_json] } | ||
stub.get("/users/2") { [200, {}, { id: 2, fullname: "Maeby Fünke" }.to_json] } | ||
stub.get("/users") { [200, {}, [user1, user2].to_json] } |
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.
Can we stick w/ the current, explicit style to keep the specs consistent? Also think it helps to see the json w/ each stub 😺
550663a
to
6dfbfd5
Compare
Sorry, this fell beneath the pile. I have rebased this, fixed the specs as requested, and changed the second commit to not require AR 4.2+. I believe assigning directly to the attributes hash should be safe here. I see that #411 hasn't been merged yet. They deal with the same issue but I think this solution handles more cases. |
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.
lib/her/model/attributes.rb
Outdated
@@ -32,6 +32,7 @@ def initialize(attributes={}) | |||
attributes = self.class.default_scope.apply_to(attributes) | |||
assign_attributes(attributes) | |||
yield self if block_given? | |||
@changed_attributes = {} if persisted? |
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.
Can we reset changed_attributes
inside instantiate_record like #411? That would be closer to the recommendation and correct this small difference
# ActiveRecord behavior
user = User.new(id: 1)
user.changes # => {"id"=>[nil, 1]}
# Her behavior
user = User.new(id: 1)
user.changes # => {}
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.
Well spotted. About to head out the door. I think this may miss some cases but I have an idea that I will try on Monday. Thanks!
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.
I've tried to make this work but regardless of which approach I use, the id
parameter is ignored here. I haven't figured out why yet and I'm very busy this week but hopefully I'll get time on Friday.
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.
Found the reason. In use_setter_methods
, both id
and the actual primary key name are treated as reserved so assignment methods are not created for them when the record is instantiated. They end up in the unset_attributes
hash in the assign_attributes
method and simply get merged into the @attributes
hash. This means that no change tracking is applied. If you directly assign new values using setter methods afterwards then change tracking does work because method_missing
is invoked, which creates attribute methods without checking a reserved list first.
If I change use_setter_methods
to not treat these as reserved then the above works but it breaks the handles new resource with custom primary key
test. I need to find a way for the id
method to not be redefined without blocking the creation of an id=
method.
6dfbfd5
to
d407ef4
Compare
Okay @zacharywelch, I've made a change to how primary keys are handled that I believe makes things cleaner and deals with the issue we were seeing above. I've also changed the handling of I forgot to mention in the last commit message that ActiveRecord also allows |
Hmm looks like this approach needs adjusting for Rails 3.2. It's also not quite right for subclasses either. Bear with me. |
Not doing do was leading to change tracking not being applied in some circumstances. With a custom primary key, id is now treated as a formal alias. This alters the behaviour slightly but setting an attribute with id= and then getting a totally different value back with id is just as useless as it is confusing. Some tests have had to be adjusted accordingly. Note that you cannot set the primary key back to :id if it was set to something else in an ancestor class. Undoing attribute aliases leads to infinite loops.
d407ef4
to
e0e41f5
Compare
I've put in a restriction to avoid the subclass and Rails 3.2 issues. Haven't had time to fully test this yet but I thought I'd push it up anyway because my break next week. Maybe I'll test it out at home if I get a chance. |
Okay, I've tried this against our own test suite and it all looks good. |
Unfortunately had to remove the primary key changes before merging to master, but we solved the original issue of freshly-loaded resources being marked as dirty 🎩 💯 Still open to a solution of the primary key problem that doesn't involve raising an error if anyone's interested in putting together a PR. Glad we were able to identify some of the challenges. Thanks! |
I may have found a solution to the primary key problem @chewi. Working on a PR. |
Thanks. Do you mean the |
Right, would rather find a solution that solves both use cases and remains backwards compatible. In the process of testing I uncovered another pre-existing issue related to Thanks! |
This should close #295.