Skip to content

Commit

Permalink
add touch option to update ancestor timestamp
Browse files Browse the repository at this point in the history
  • Loading branch information
hasghari committed Oct 19, 2015
1 parent 4854799 commit 03a2e1d
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 4 deletions.
6 changes: 5 additions & 1 deletion lib/parentry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Parentry

def self.included(base)
base.class_eval do
mattr_accessor :parentry_column, :depth_offset, :cache_depth
mattr_accessor :parentry_column, :depth_offset, :cache_depth, :touch_ancestors

belongs_to :parent, class_name: base_class.name
has_many :children, class_name: base_class.name, foreign_key: :parent_id, dependent: :destroy
Expand All @@ -25,6 +25,10 @@ def self.included(base)
after_update :cascade_parentry, if: proc { changes[parentry_column].present? }
after_save :cache_parentry_depth, if: proc { cache_depth && depth != parentry_depth }

after_save :touch_ancestors_callback
after_touch :touch_ancestors_callback
after_destroy :touch_ancestors_callback

scope :order_by_parentry, -> { order("nlevel(#{parentry_column})") }

scope :before_depth, ->(depth) { where("nlevel(#{parentry_column}) - 1 < ?", depth + depth_offset) }
Expand Down
1 change: 1 addition & 0 deletions lib/parentry/class_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ def parentry(options = {})
self.parentry_column = options.fetch(:parentry_column, 'parentry')
self.depth_offset = options.fetch(:depth_offset, 0)
self.cache_depth = options.fetch(:cache_depth, false)
self.touch_ancestors = options.fetch(:touch, false)
end

def arrange(options = {})
Expand Down
32 changes: 30 additions & 2 deletions lib/parentry/instance_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ def parentry_scope
end

def prevent_circular_parentry
computed = compute_parentry
errors.add(:parentry, 'contains a circular reference') unless computed.split('.').uniq == computed.split('.')
computed = parse_parentry(compute_parentry)
errors.add(:parentry, 'contains a circular reference') unless computed.uniq == computed
end

def commit_parentry
Expand Down Expand Up @@ -40,5 +40,33 @@ def compute_parentry
def parentry
read_attribute(parentry_column)
end

def parse_parentry(input = parentry)
input.to_s.split('.').map(&:to_i)
end

def touch_ancestors_callback
return unless touch_ancestors
return if touch_callbacks_disabled?

parentry_scope.where(id: ancestor_ids_was + ancestor_ids).each do |ancestor|
ancestor.without_touch_callbacks { ancestor.touch }
end
end

def without_touch_callbacks
@disable_touch_callbacks = true
yield
@disable_touch_callbacks = false
end

def touch_callbacks_disabled?
@disable_touch_callbacks
end

def ancestor_ids_was
return [] unless changes[parentry_column]
parse_parentry(changes[parentry_column][0]).tap(&:pop)
end
end
end
2 changes: 1 addition & 1 deletion lib/parentry/navigation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def root?
end

def path_ids
parentry.split('.').map(&:to_i)
parse_parentry
end

def path(scopes = {})
Expand Down
11 changes: 11 additions & 0 deletions spec/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@
t.ltree :parentry
t.integer :parentry_depth
t.integer :rank

t.timestamps null: false
end

create_table :one_depth_tree_nodes, force: true do |t|
t.integer :parent_id
t.ltree :parentry

t.timestamps null: false
end

create_table :touch_tree_nodes, force: true do |t|
t.integer :parent_id
t.ltree :parentry

t.timestamps null: false
end
end
51 changes: 51 additions & 0 deletions spec/instance_methods_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,55 @@
expect(child.reload.parentry).to eq "#{node.parentry}.#{child.id}"
end
end

context 'touch ancestors option' do
context 'enabled' do
it 'should update ancestor timestamp' do
parent = TouchTreeNode.create
expect do
parent.children.create
end.to change { parent.reload.updated_at }
end

context 'parent changes' do
it 'should update old parent timestamp' do
parent = TouchTreeNode.create
node = parent.children.create
new_parent = TouchTreeNode.create

expect do
node.update_attributes(parent: new_parent)
end.to change { parent.reload.updated_at }
end

it 'should update new parent timestamp' do
parent = TouchTreeNode.create
node = parent.children.create
new_parent = TouchTreeNode.create

expect do
node.update_attributes(parent: new_parent)
end.to change { new_parent.reload.updated_at }
end
end

it 'should update parent timestamp when child is deleted' do
parent = TouchTreeNode.create
node = parent.children.create

expect do
node.destroy
end.to change { parent.reload.updated_at }
end
end

context 'disabled' do
it 'should not update ancestor timestamp' do
parent = TreeNode.create
expect do
parent.children.create
end.not_to change { parent.reload.updated_at }
end
end
end
end
5 changes: 5 additions & 0 deletions spec/support/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ class OneDepthTreeNode < ActiveRecord::Base
include Parentry
parentry depth_offset: 1
end

class TouchTreeNode < ActiveRecord::Base
include Parentry
parentry touch: true
end

0 comments on commit 03a2e1d

Please sign in to comment.