Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #135 from adammck/add_touch_option

Add the :touch to update ancestors on save
  • Loading branch information...
commit e32b068a883b64cd8ec0b74fb98148b4da1cb2b4 2 parents 4dcc935 + 1d2f925
Stefan Kroes authored
5 README.rdoc
View
@@ -73,7 +73,8 @@ The has_ancestry methods supports the following options:
: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.
+ :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 in the 'ancestry_depth' column (default: false)
If you turn depth_caching on for an existing model:
- Migrate: add_column [table], :ancestry_depth, :integer, :default => 0
@@ -81,6 +82,8 @@ The has_ancestry methods supports the following options:
:depth_cache_column Pass in a symbol to store depth cache in a different column
:primary_key_format Supply a regular expression that matches the format of your primary key.
By default, primary keys only match integers ([0-9]+).
+ :touch Instruct Ancestry to touch the ancestors of a node when it changes, to
+ invalidate nested key-based caches. (default: false)
= (Named) Scopes
12 lib/ancestry/has_ancestry.rb
View
@@ -3,7 +3,7 @@ def has_ancestry options = {}
# Check options
raise Ancestry::AncestryException.new("Options for has_ancestry must be in a hash.") unless options.is_a? Hash
options.each do |key, value|
- unless [:ancestry_column, :orphan_strategy, :cache_depth, :depth_cache_column].include? key
+ unless [:ancestry_column, :orphan_strategy, :cache_depth, :depth_cache_column, :touch].include? key
raise Ancestry::AncestryException.new("Unknown option for has_ancestry: #{key.inspect} => #{value.inspect}.")
end
end
@@ -25,7 +25,11 @@ def has_ancestry options = {}
# Save self as base class (for STI)
cattr_accessor :ancestry_base_class
self.ancestry_base_class = self
-
+
+ # Touch ancestors after updating
+ cattr_accessor :touch_ancestors
+ self.touch_ancestors = options[:touch] || false
+
# Validate format of ancestry column value
validates_format_of ancestry_column, :with => Ancestry::ANCESTRY_PATTERN, :allow_nil => true
@@ -69,6 +73,10 @@ def has_ancestry options = {}
where("#{depth_cache_column} #{operator} ?", depth)
}
end
+
+ after_save :touch_ancestors_callback
+ after_touch :touch_ancestors_callback
+ after_destroy :touch_ancestors_callback
end
# Alias has_ancestry with acts_as_tree, if it's available.
35 lib/ancestry/instance_methods.rb
View
@@ -64,6 +64,25 @@ def apply_orphan_strategy
end
end
+ # Touch each of this record's ancestors
+ def touch_ancestors_callback
+
+ # Skip this if callbacks are disabled
+ unless ancestry_callbacks_disabled?
+
+ # Only touch if the option is enabled
+ if self.ancestry_base_class.touch_ancestors
+
+ # Touch each of the old *and* new ancestors
+ self.class.where(id: (ancestor_ids + ancestor_ids_was).uniq).each do |ancestor|
+ ancestor.without_ancestry_callbacks do
+ ancestor.touch
+ end
+ end
+ end
+ end
+ end
+
# The ancestry value for this record's children
def child_ancestry
# New records cannot have children
@@ -77,8 +96,12 @@ def ancestry_changed?
changed.include?(self.ancestry_base_class.ancestry_column.to_s)
end
+ def parse_ancestry_column obj
+ obj.to_s.split('/').map { |id| cast_primary_key(id) }
+ end
+
def ancestor_ids
- read_attribute(self.ancestry_base_class.ancestry_column).to_s.split('/').map { |id| cast_primary_key(id) }
+ parse_ancestry_column(read_attribute(self.ancestry_base_class.ancestry_column))
end
def ancestor_conditions
@@ -86,7 +109,15 @@ def ancestor_conditions
end
def ancestors depth_options = {}
- self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where ancestor_conditions
+ self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where ancestor_conditions
+ end
+
+ def ancestor_was_conditions
+ {primary_key_with_table => ancestor_ids_was}
+ end
+
+ def ancestor_ids_was
+ parse_ancestry_column(changed_attributes[self.ancestry_base_class.ancestry_column.to_s])
end
def path_ids
44 test/has_ancestry_test.rb
View
@@ -826,4 +826,48 @@ def test_sort_by_ancestry_with_block
assert_equal [n1, n3, n5, n2, n4, n6].map(&:id), arranged.map(&:id)
end
end
+
+ def test_touch_option_disabled
+ AncestryTestDatabase.with_model(
+ :extra_columns => {:name => :string, :updated_at => :datetime},
+ :touch => false
+ ) do |model|
+
+ yesterday = Time.now - 1.day
+ parent = model.create!(:updated_at => yesterday)
+ child = model.create!(:updated_at => yesterday, :parent => parent)
+
+ child.update_attributes(:name => "Changed")
+ assert_equal yesterday, parent.updated_at
+ end
+ end
+
+ def test_touch_option_enabled
+ AncestryTestDatabase.with_model(
+ :extra_columns => {:updated_at => :datetime},
+ :touch => true
+ ) do |model|
+
+ way_back = Time.new(1984)
+ recently = Time.now - 1.minute
+
+ parent_1 = model.create!(:updated_at => way_back)
+ parent_2 = model.create!(:updated_at => way_back)
+ child_1_1 = model.create!(:updated_at => way_back, :parent => parent_1)
+ child_1_2 = model.create!(:updated_at => way_back, :parent => parent_1)
+ grandchild_1_1_1 = model.create!(:updated_at => way_back, :parent => child_1_1)
+ grandchild_1_1_2 = model.create!(:updated_at => way_back, :parent => child_1_1)
+
+ grandchild_1_1_1.parent = parent_2
+ grandchild_1_1_1.save!
+
+ assert grandchild_1_1_1.reload.updated_at > recently, "record was not touched"
+ assert child_1_1.reload.updated_at > recently, "old parent was not touched"
+ assert parent_1.reload.updated_at > recently, "old grandparent was not touched"
+ assert parent_2.reload.updated_at > recently, "new parent was not touched"
+
+ assert_equal way_back, grandchild_1_1_2.reload.updated_at, "old sibling was touched"
+ assert_equal way_back, child_1_2.reload.updated_at, "unrelated record was touched"
+ end
+ end
end
Please sign in to comment.
Something went wrong with that request. Please try again.