Repository navigation
fix: preserve dirty tracking when reordering siblings with numeric_order - #498
Open
ihamzatarar wants to merge 1 commit into
Open
ihamzatarar wants to merge 1 commit into
ihamzatarar wants to merge 1 commit into
Conversation
_ct_reorder_siblings called reload to pick up the sort order the reorder had just written. reload also clears @mutations_before_last_save, so every saved_change_to_* predicate returned false for a node that had moved, and after_update_commit callbacks guarded by them stopped firing. Read the order column back with pick instead, then clear_attribute_change so the assignment is not reported as a pending change. That touches only mutations_from_database, leaving the post-save dirty state intact, and costs the same single SELECT that reload did. Fixes ClosureTree#414.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #414.
The problem
With
numeric_order: true, a node that changes parent comes out of the save with nodirty tracking at all:
_ct_after_savecallsrebuild!, which calls_ct_reorder_siblings, which ended withreload unless destroyed?.ActiveRecord::AttributeMethods::Dirty#reloadsets both@mutations_before_last_saveand@mutations_from_databasetonil, so everysaved_change_to_*predicate returns false from that point on.The callbacks that read those predicates run after the reload, so they see nothing.
after_update_commit :reindex, if: :saved_change_to_name?and the same shape used foraudit logs and cache busting silently skip moved nodes. Nothing raises, which is why
this is easy to miss.
The fix
_ct_reorder_siblingsonly needs one value back from the database: the sort order thereorder statement just wrote. It now reads that single column with
pickand clears theresulting attribute change:
clear_attribute_changecallsmutations_from_database.forget_change. It does not touchmutations_before_last_save, so the post-save dirty state survives while the record isstill reported as unchanged, which is what
reloadused to give callers. Without theclear_attribute_changethe record would be leftchanged?with a pending write to theorder column that is in fact already in the database.
Query count is unchanged: one SELECT before, one SELECT now.
I checked what else the
reloadwas providing. The threereorder_with_parent_idadapters in
numeric_order_support.rbwrite the order column and nothing else, so noother attribute could be stale. The association cache does not depend on it either: the
two associations that matter after a reparent are reloaded explicitly a few lines later
in
_ct_after_save(theancestor_hierarchies.reload/self_and_ancestors.reloadpairadded for #68), and
rebuild!'s recursive calls passcalled_by_rebuild = true, whichskips
_ct_reorder_siblingsentirely and therefore never had the reload to begin with.prepend_childandadd_siblingcallreloadthemselves where they need it.The
after_destroy :_ct_reorder_siblingspath behaves as before:reload unless destroyed?becamereturn if destroyed?.Sibling methods
_ct_reorder_childrenand_ct_reorder_prior_siblings_if_parent_changeddo not need thesame treatment and are left alone. Neither calls
reload, and neither can change thereceiver's own order column.
_ct_reorder_childrenrenumbers rows whose parent is thereceiver, and
_ct_reorder_prior_siblings_if_parent_changedrenumbers rows under theprevious parent, which by then no longer include the receiver.
Test
test/closure_tree/label_order_value_test.rbgains one test next to the existing"resets order_value when a node is moved" case. It moves a node to a new parent while
also changing its name, and asserts both halves of the behaviour: that
saved_change_to_name?andsaved_change_to_namesurvive and the record is not leftdirty, and that the sort order is still right afterwards, both for the moved node and
for the siblings it left behind.
Without the lib change it fails on the first assertion:
Full suite against PostgreSQL, MySQL and SQLite: 369 runs / 1350 assertions / 0 failures
on master, 370 runs / 1356 assertions / 0 failures with this change. I also confirmed by
hand that the fix holds on all three adapters, including the generic SQLite path where
reorder_with_parent_idgoes through ActiveRecord objects rather than raw SQL.Credit
@ryanb reported the issue and posted a working monkey-patch in
#414 (comment). This is
that patch, rebased onto the current shape of
_ct_reorder_siblings, which now passesscope_conditionsthrough toreorder_with_parent_id.