-
-
Notifications
You must be signed in to change notification settings - Fork 357
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 bug, position is recomputed when object saved #188
Conversation
|
||
def test_does_not_modify_position_of_single_item_at_bottom | ||
c = Customer.create(name: 'Bob') | ||
order = Order.new(title: 'Order 1') |
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.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
[OT] Gah, |
I checked add_to_list_top, it is implemented differently from add_to_list_bottom so doesn't exhibit this behavior. I agree that callbacks tend to be great breeding grounds for dragons, however refactoring that isn't a good option right now. My first thought was that not_in_list? would have prevented this as well, but in this case, position is not changed and scope is changed so you get false || true && true || false = true So I guess to me this didn't feel toooo work around'ish because you really don't want to increment position if you are already at the bottom. I guess the other thing that could happen is that add_to_list_bottom should return if bottom_item == self or in the list at all because if it is in the list then move should have been called? |
The CI checks that failed were
Could I have broken that? |
I'm not sure now to make this less contrived :( I need a belongs_to and update_attributes so that I get scope_changed to be true. |
That sure is a strange bundler error! I couldn't find anything with a quick search. That change log generator was added only recently. Perhaps there's a problem with the gem they published? Could you explain why you need a double save in your particular application? That might help us come up with a more legitimate test case :) |
I doubt that the double save is needed . It's an old rails app, and once upon a time people thought callbacks were where important logic went? |
Open bug on github changeling generator |
Lol, that GIF on the issue page! I see what you're saying about the rails app. I can foresee perhaps someone saving an instance twice (outside of a callback) say in a controller. Would doing that cause the bug too? |
No, it is specific to being saved via a callback, I can comment out the after_create call and call change_title right after the first save! then call save again and you don't recreate it. |
Here's a couple of other things I noticed.... Perhaps in The codebase I'm working on has overridden I think Also, in If the column default is NULL this check gets broken.
So then checks for A simple fix is
There's probably a more elegant way? |
How do you control which version of github_changelog_generator will get pulled in travis? I locked the version to the non-broken one in this PR but that doesn't seem to do it |
I submitted #190 to solve that issue. Since Gemfiles are managed with appraisal they need to be regenerated as well. As soon as the travis build finish with a green status I'll merge #190 and you then can rebase against master. |
Ok, now we have a working version in master. Please rebase against it. |
Hi @chrisortman :) I've had a bit of a think on this one. I see your point on As far as the double-save thing. You mentioned the scope is changing. Thinking more about that, it's more likely that the scope change (aka, certain tracked columns being dirty) isn't reset before the callback is called, because really, on the second save, the scope hasn't changed. How about a local variable to track wether the scope change has already been detected, similar to the The only problem is that users may have created their own scope_changed method (I have on occasion to handle a more advanced change condition), so you'd need an intermediary private method to check the public method and set the local variable appropriately. I've not tested anything so this could all be rubbish! :D |
My thinking for why I fixed it the way I did using the But, I can't think of a scenario where if the item is already at the bottom of the list it should still have it's position changed. So the I have much less experience than you with this code base and won't probably have to live with the decision for as long so if you feel it should be done a different way I'll trust you on it and make the changes. I will do a rebase here and pull the default position changes into a separate PR |
Having said all that...I can't make it work using I could though implement a comparison like
Which would work around someone overriding spaceship. |
Ok, I think I've come up with a better test case, and have fit it into the existing test suite better. The nice thing is that I think it might have exposed some other bugs related to having a model with these types of callbacks. One thing it told me was that if I do the bottom_item check then the default_position? changes need to be part of the fix because once I add the bottom_item check other tests fail without changes to default_position? Without default position changes
With
So for now I've left them in. I will look into why these other tests are failing |
I've made some progress, but am not sure what to do next. Once I started doing the equality checks, making sure to include STI in the models in the tests became important, but adding that is breaking other tests because the tests don't get the correct list when they are a different subtype. Example, create ZeroBasedMixin models, but then the test creates a ListMixin model on the same parent. Right now we construct the query using What is the desired behavior when models use STI? Should the whole type hierarchy be ordered within the parent or on each individual type? whole type hierarchy seems like what you'd want, but I could see reasons for both... |
Why in test/shared_zero_based.rb does |
@chrisortman, check out my branch: https://github.com/swanandp/acts_as_list/tree/fix-scope-change-detection and see if you still experience the bug with that. The solution feels a bit hacky though. I can see an edge case already where you want to (for some unknown reason) change the scope in the |
In regards to |
An issues the refers to this problem also: #166 |
I cherry pick'd 309ab43e59c999ed3286466f0500d296635cf69b into your branch and ran the tests and get several errors. https://github.com/chrisortman/acts_as_list/commits/fix-scope-change-detection It looks like we are still getting double entry into the insert_at_bottom
|
Bummer. Given I didn't actually test the code against the problem. Would you be able to probe that variable at the various stages in the save cycle and see if it's behaving itself? It should be true before save, then after save be set to false. Then after the initial save is committed, the variable should be unset. It would be super helpful is dirty tracking actually worked properly in |
It looks like the If I add |
Interesting. Probably due to the ordering of the after_save callbacks. My after_save would need to be executed before yours. Is your callback declaration before the |
Yeah, it's in my fork of your branch
|
What do we think with this? We have to possible ways to fix this, are we happier with one vs the other? |
If we can control the order of callbacks (or perhaps is there an underlying Rails hooks scheme in ActiveRecord that we can hook into?) then solving the real bug, which is thinking the scope has changed after the model has been saved, would be preferable to me. The other way just seems to be patching a symptom of that problem. |
Alright, I'm fine with fixing it by fixing the scope change detection. If you look at chrisortman@21a3bbf this is what I had to do to finish what you had already started. I'll update this PR with these changes |
7188d82
to
b3b354e
Compare
Eliminated long line if statement Removed redundante symbol conversion Use Rails’ changed array instead of changes hash Remove needless conversion of array to string back to array again Remove redundante attrs method
We now refer to `internal_scope_changed?` to see wether the scope has changed. This memoises `scope_changed?` until the item is committed successfully. `internal_scope_changed?` returns false in any `after_save` callbacks as long as they were defined after the `acts_as_list` declaration.
A model that calls update_attributes from a callack triggers a second pass through our callback chain and results in some values being mutated twice which leads to doubly incremented position values.
before_update :check_scope | ||
after_update :update_positions | ||
before_validation :check_top_position | ||
|
||
after_save '@scope_changed = false' |
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.
Should we now remove this line? I agree with your approach of setting @scope_changed
to false
straight after adding the item to the list.
Thanks for your patience Chris :) Nearly there, just had a few suggestions to the code. Your solution is definitely better than the |
No problem. Good stuff sometimes takes time :) I think latest commit addressed all your notes. Let me know if I missed anything else. |
Thanks @chrisortman :) I'm happy with this. I'll merge it. I just want to get @swanandp to sign off on this too since he's the boss ;) Can you give this a look soon @swanandp? |
Sorry @brendon and @chrisortman , I dropped the ball on this. Taking a look now. |
Looks good 👍 |
Merged! 💃 |
In models that get saved multiple times (due to callbacks in the case I ran into) the position is updated multiple times meaning that you start with a position of 2 instead of 1
I wasn't sure how best to integrate this with the existing tests. Happy to refactor that if you'd like that done differently