Skip to content

Commit

Permalink
Merge pull request #658 from kbrock/extensible_orphan_strategy
Browse files Browse the repository at this point in the history
Extensible orphan_strategy
  • Loading branch information
kbrock authored Jul 13, 2023
2 parents 22c1991 + 753013a commit c08b07d
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 32 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ a nice looking [Changelog](http://keepachangelog.com).

## Version [HEAD] <sub><sup>Unreleased</sub></sup>

* Introduce `orphan_strategy: :none` [#658](https://github.com/stefankroes/ancestry/pull/658)
* Introduce `rebuild_counter_cache!` to reset counter caches. [#663](https://github.com/stefankroes/ancestry/pull/663) [#668](https://github.com/stefankroes/ancestry/pull/668) (thx @RongRongTeng)
* Documentation fixes [#664](https://github.com/stefankroes/ancestry/pull/664) [#667](https://github.com/stefankroes/ancestry/pull/667) (thx @motokikando, @onerinas)
* Introduce `build_cache_depth_sql!`, a sql alternative to `build_cache_depth` [#654](https://github.com/stefankroes/ancestry/pull/654)
Expand Down Expand Up @@ -41,7 +42,7 @@ jobs. If you need to do this in the ui, please use `cache_depth`.
- `ancestry_primary_key_format` (introduced 4.3.0, removed by #649)
- `touch_ancestors` (introduced 2.1, removed by TODO)
* These are seen as internal and may go away:
- `apply_orphan_strategy` (TODO: use `orphan_strategy => none` and define `before_destory`)
- `apply_orphan_strategy` Please use `orphan_strategy: :none` and a custom `before_destory` instead.

## Version [4.3.3] <sub><sup>2023-04-01</sub></sup>

Expand Down
42 changes: 22 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,33 +164,35 @@ The `has_ancestry` method supports the following options:
:ancestry_column Column name to store ancestry
'ancestry' (default)
:ancestry_format Format for ancestry column (see Ancestry Formats section):
:materialized_path (default) 1/2/3, root nodes ancestry=nil
:materialized_path2 (preferred) /1/2/3/, root nodes ancestry=/
:orphan_strategy Instruct Ancestry what to do with children of a node that is destroyed:
:materialized_path 1/2/3, root nodes ancestry=nil (default)
:materialized_path2 /1/2/3/, root nodes ancestry=/ (preferred)
:orphan_strategy How to handle children of a destroyed node:
:destroy All children are destroyed as well (default)
:rootify The children of the destroyed node become root nodes
:restrict An AncestryException is raised if any children exist
:adopt The orphan subtree is added to the parent of the deleted node
If the deleted node is Root, then rootify the orphan subtree
:cache_depth Cache the depth of each node (See Depth Cache section)
:none skip this logic. (add your own `before_destroy`)
:cache_depth Cache the depth of each node: (See Depth Cache section)
false Do not cache depth (default)
true Cache depth in 'ancestry_depth'
String Cache depth in column referenced
:primary_key_format regular expression that matches the format of the primary key
'[0-9]+' (default) integer ids
'[-A-Fa-f0-9]{36}' UUIDs
:touch Instruct Ancestry to touch the ancestors of a node when it changes
false (default) don't invalide nested key-based caches
:counter_cache Whether to create counter cache column accessor.
false (default) don't store a counter cache
true store counter cache in `children_count`.
String name of column to store counter cache.
:update_strategy Choose the strategy to update descendants nodes
:ruby (default) All descendants are updated using the ruby algorithm.
This triggers update callbacks for each descendant node
:sql All descendants are updated using a single SQL statement.
This strategy does not trigger update callbacks for the descendants.
This strategy is available only for PostgreSql implementations
String Cache depth in the column referenced
:primary_key_format Regular expression that matches the format of the primary key:
'[0-9]+' integer ids (default)
'[-A-Fa-f0-9]{36}' UUIDs
:touch Touch the ancestors of a node when it changes:
false don't invalide nested key-based caches (default)
true touch all ancestors of previous and new parents
:counter_cache Create counter cache column accessor:
false don't store a counter cache (default)
true store counter cache in `children_count`.
String name of column to store counter cache.
:update_strategy How to update descendants nodes:
:ruby All descendants are updated using the ruby algorithm. (default)
This triggers update callbacks for each descendant node
:sql All descendants are updated using a single SQL statement.
This strategy does not trigger update callbacks for the descendants.
This strategy is available only for PostgreSql implementations

Legacy configuration using `acts_as_tree` is still available. Ancestry defers to `acts_as_tree` if that gem is installed.

Expand Down
16 changes: 5 additions & 11 deletions lib/ancestry/has_ancestry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,13 @@ def has_ancestry options = {}
after_update :update_descendants_with_new_ancestry, if: :ancestry_changed?

# Apply orphan strategy before destroy
case orphan_strategy
when :rootify
alias_method :apply_orphan_strategy, :apply_orphan_strategy_rootify
when :destroy
alias_method :apply_orphan_strategy, :apply_orphan_strategy_destroy
when :adopt
alias_method :apply_orphan_strategy, :apply_orphan_strategy_adopt
when :restrict
alias_method :apply_orphan_strategy, :apply_orphan_strategy_restrict
else
orphan_strategy_helper = "apply_orphan_strategy_#{orphan_strategy}"
if method_defined?(orphan_strategy_helper)
alias_method :apply_orphan_strategy, orphan_strategy_helper
before_destroy :apply_orphan_strategy
elsif orphan_strategy.to_s != "none"
raise Ancestry::AncestryException.new(I18n.t("ancestry.invalid_orphan_strategy"))
end
before_destroy :apply_orphan_strategy

# Create ancestry column accessor and set to option or default
if options[:cache_depth]
Expand Down
56 changes: 56 additions & 0 deletions test/concerns/orphan_strategies_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,62 @@ def apply_orphan_strategy
end
end

def test_apply_orphan_strategy_none
AncestryTestDatabase.with_model orphan_strategy: :none do |model, roots|
root = model.create!
child = model.create!(:parent => root)
model.class_eval do
def apply_orphan_strategy
raise "this should not be called"
end
end
assert_difference 'model.count', -1 do
root.destroy
end
# this record should still exist
assert child.reload.root_id == root.id
end
end

def test_apply_orphan_strategy_custom
AncestryTestDatabase.with_model orphan_strategy: :none do |model|
model.class_eval do
before_destroy :apply_orphan_strategy_abc

def apply_orphan_strategy_abc
apply_orphan_strategy_destroy
end
end

root = model.create!
3.times { root.children.create! }
model.create! # a node that is not affected
assert_difference 'model.count', -4 do
root.destroy
end
end
end

# Not supported. Keeping around to explore for future uses.
def test_apply_orphan_strategy_custom_unsupported
AncestryTestDatabase.with_model skip_ancestry: true do |model|
model.class_eval do
# needs to be defined before calling has_ancestry
def apply_orphan_strategy_abc
apply_orphan_strategy_destroy
end

has_ancestry orphan_strategy: :abc, ancestry_column: AncestryTestDatabase.ancestry_column
end
root = model.create!
3.times { root.children.create! }
model.create! # a node that is not affected
assert_difference 'model.count', -4 do
root.destroy
end
end
end

def test_basic_delete
AncestryTestDatabase.with_model do |model|
n1 = model.create! #create a root node
Expand Down

0 comments on commit c08b07d

Please sign in to comment.