Skip to content
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

update Ancestry writers #633

Merged
merged 2 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions lib/ancestry/class_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Ancestry
module ClassMethods
# Fetch tree node if necessary
def to_node object
if object.is_a?(self.ancestry_base_class)
if object.is_a?(ancestry_base_class)
object
else
unscoped_where { |scope| scope.find(object.try(primary_key) || object) }
Expand All @@ -11,7 +11,7 @@ def to_node object

# Scope on relative depth options
def scope_depth depth_options, depth
depth_options.inject(self.ancestry_base_class) do |scope, option|
depth_options.inject(ancestry_base_class) do |scope, option|
scope_name, relative_depth = option
if [:before_depth, :to_depth, :at_depth, :from_depth, :after_depth].include? scope_name
scope.send scope_name, depth + relative_depth
Expand All @@ -29,9 +29,9 @@ def scope_depth depth_options, depth
# Get all nodes and sort them into an empty hash
def arrange options = {}
if (order = options.delete(:order))
arrange_nodes self.ancestry_base_class.order(order).where(options)
arrange_nodes ancestry_base_class.order(order).where(options)
else
arrange_nodes self.ancestry_base_class.where(options)
arrange_nodes ancestry_base_class.where(options)
end
end

Expand Down Expand Up @@ -125,7 +125,7 @@ def check_ancestry_integrity! options = {}
if !node.sane_ancestor_ids?
raise Ancestry::AncestryIntegrityException.new(I18n.t("ancestry.invalid_ancestry_column",
:node_id => node.id,
:ancestry_column => "#{node.read_attribute node.ancestry_column}"
:ancestry_column => "#{node.read_attribute node.class.ancestry_column}"
))
end
# ... check that all ancestors exist
Expand Down Expand Up @@ -164,7 +164,7 @@ def check_ancestry_integrity! options = {}
def restore_ancestry_integrity!
parent_ids = {}
# Wrap the whole thing in a transaction ...
self.ancestry_base_class.transaction do
ancestry_base_class.transaction do
unscoped_where do |scope|
# For each node ...
scope.find_each do |node|
Expand Down Expand Up @@ -216,7 +216,7 @@ def build_ancestry_from_parent_ids! column=:parent_id, parent_id = nil, ancestor
def rebuild_depth_cache!
raise Ancestry::AncestryException.new(I18n.t("ancestry.cannot_rebuild_depth_cache")) unless respond_to? :depth_cache_column

self.ancestry_base_class.transaction do
ancestry_base_class.transaction do
unscoped_where do |scope|
scope.find_each do |node|
node.update_attribute depth_cache_column, node.depth
Expand All @@ -226,7 +226,7 @@ def rebuild_depth_cache!
end

def unscoped_where
yield self.ancestry_base_class.default_scoped.unscope(:where)
yield ancestry_base_class.default_scoped.unscope(:where)
end

ANCESTRY_UNCAST_TYPES = [:string, :uuid, :text].freeze
Expand Down
12 changes: 6 additions & 6 deletions lib/ancestry/has_ancestry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ def has_ancestry options = {}
orphan_strategy = options[:orphan_strategy] || :destroy

# Create ancestry column accessor and set to option or default
cattr_accessor :ancestry_column
self.ancestry_column = options[:ancestry_column] || :ancestry
self.class_variable_set('@@ancestry_column', options[:ancestry_column] || :ancestry)
cattr_reader :ancestry_column, instance_reader: false

cattr_accessor :ancestry_primary_key_format
self.ancestry_primary_key_format = options[:primary_key_format].presence || Ancestry.default_primary_key_format

cattr_accessor :ancestry_delimiter
self.ancestry_delimiter = '/'
self.class_variable_set('@@ancestry_delimiter', '/')
cattr_reader :ancestry_delimiter, instance_reader: false

# Save self as base class (for STI)
cattr_accessor :ancestry_base_class
self.ancestry_base_class = self
self.class_variable_set('@@ancestry_base_class', self)
cattr_reader :ancestry_base_class, instance_reader: false

# Touch ancestors after updating
cattr_accessor :touch_ancestors
Expand Down
50 changes: 24 additions & 26 deletions lib/ancestry/instance_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def apply_orphan_strategy_restrict

# Touch each of this record's ancestors (after save)
def touch_ancestors_callback
if !ancestry_callbacks_disabled? && self.ancestry_base_class.touch_ancestors
if !ancestry_callbacks_disabled? && self.class.ancestry_base_class.touch_ancestors
# Touch each of the old *and* new ancestors
unscoped_current_and_previous_ancestors.each do |ancestor|
ancestor.without_ancestry_callbacks do
Expand All @@ -74,7 +74,7 @@ def touch_ancestors_callback

# Counter Cache
def increase_parent_counter_cache
self.ancestry_base_class.increment_counter counter_cache_column, parent_id
self.class.ancestry_base_class.increment_counter counter_cache_column, parent_id
end

def decrease_parent_counter_cache
Expand All @@ -86,16 +86,14 @@ def decrease_parent_counter_cache
return if defined?(@_trigger_destroy_callback) && !@_trigger_destroy_callback
return if ancestry_callbacks_disabled?

self.ancestry_base_class.decrement_counter counter_cache_column, parent_id
self.class.ancestry_base_class.decrement_counter counter_cache_column, parent_id
end

def update_parent_counter_cache
changed = saved_change_to_attribute?(self.ancestry_base_class.ancestry_column)

return unless changed
return unless saved_change_to_attribute?(self.class.ancestry_column)

if parent_id_was = parent_id_before_last_save
self.ancestry_base_class.decrement_counter counter_cache_column, parent_id_was
self.class.ancestry_base_class.decrement_counter counter_cache_column, parent_id_was
end

parent_id && increase_parent_counter_cache
Expand All @@ -109,7 +107,7 @@ def has_parent?
alias :ancestors? :has_parent?

def ancestry_changed?
column = self.ancestry_base_class.ancestry_column.to_s
column = self.class.ancestry_column.to_s
# These methods return nil if there are no changes.
# This was fixed in a refactoring in rails 6.0: https://github.com/rails/rails/pull/35933
!!(will_save_change_to_attribute?(column) || saved_change_to_attribute?(column))
Expand All @@ -119,7 +117,7 @@ def sane_ancestor_ids?
current_context, self.validation_context = validation_context, nil
errors.clear

attribute = ancestry_base_class.ancestry_column
attribute = self.class.ancestry_column
ancestry_value = send(attribute)
return true unless ancestry_value

Expand All @@ -133,8 +131,8 @@ def sane_ancestor_ids?
end

def ancestors depth_options = {}
return self.ancestry_base_class.none unless has_parent?
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.ancestors_of(self)
return self.class.ancestry_base_class.none unless has_parent?
self.class.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.ancestors_of(self)
end

def path_ids
Expand All @@ -146,15 +144,15 @@ def path_ids_before_last_save
end

def path depth_options = {}
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.inpath_of(self)
self.class.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.inpath_of(self)
end

def depth
ancestor_ids.size
end

def cache_depth
write_attribute self.ancestry_base_class.depth_cache_column, depth
write_attribute self.class.ancestry_base_class.depth_cache_column, depth
end

def ancestor_of?(node)
Expand Down Expand Up @@ -216,11 +214,11 @@ def root_of?(node)
# Children

def children
self.ancestry_base_class.children_of(self)
self.class.ancestry_base_class.children_of(self)
end

def child_ids
children.pluck(self.ancestry_base_class.primary_key)
children.pluck(self.class.primary_key)
end

def has_children?
Expand All @@ -240,12 +238,12 @@ def child_of?(node)
# Siblings

def siblings
self.ancestry_base_class.siblings_of(self)
self.class.ancestry_base_class.siblings_of(self)
end

# NOTE: includes self
def sibling_ids
siblings.pluck(self.ancestry_base_class.primary_key)
siblings.pluck(self.class.primary_key)
end

def has_siblings?
Expand All @@ -265,11 +263,11 @@ def sibling_of?(node)
# Descendants

def descendants depth_options = {}
self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).descendants_of(self)
self.class.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).descendants_of(self)
end

def descendant_ids depth_options = {}
descendants(depth_options).pluck(self.ancestry_base_class.primary_key)
descendants(depth_options).pluck(self.class.primary_key)
end

def descendant_of?(node)
Expand All @@ -279,11 +277,11 @@ def descendant_of?(node)
# Indirects

def indirects depth_options = {}
self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).indirects_of(self)
self.class.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).indirects_of(self)
end

def indirect_ids depth_options = {}
indirects(depth_options).pluck(self.ancestry_base_class.primary_key)
indirects(depth_options).pluck(self.class.primary_key)
end

def indirect_of?(node)
Expand All @@ -293,11 +291,11 @@ def indirect_of?(node)
# Subtree

def subtree depth_options = {}
self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).subtree_of(self)
self.class.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).subtree_of(self)
end

def subtree_ids depth_options = {}
subtree(depth_options).pluck(self.ancestry_base_class.primary_key)
subtree(depth_options).pluck(self.class.primary_key)
end

# Callback disabling
Expand All @@ -316,13 +314,13 @@ def ancestry_callbacks_disabled?
private
def unscoped_descendants
unscoped_where do |scope|
scope.where self.ancestry_base_class.descendant_conditions(self)
scope.where self.class.ancestry_base_class.descendant_conditions(self)
end
end

def unscoped_descendants_before_save
unscoped_where do |scope|
scope.where self.ancestry_base_class.descendant_before_save_conditions(self)
scope.where self.class.ancestry_base_class.descendant_before_save_conditions(self)
end
end

Expand All @@ -340,7 +338,7 @@ def unscoped_find id
end

def unscoped_where
self.ancestry_base_class.unscoped_where do |scope|
self.class.ancestry_base_class.unscoped_where do |scope|
yield scope
end
end
Expand Down
24 changes: 12 additions & 12 deletions lib/ancestry/materialized_path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,29 +112,29 @@ def ancestry_format_regexp
module InstanceMethods
# optimization - better to go directly to column and avoid parsing
def ancestors?
read_attribute(self.ancestry_base_class.ancestry_column) != self.ancestry_base_class.ancestry_root
read_attribute(self.class.ancestry_column) != self.class.ancestry_root
end
alias :has_parent? :ancestors?

def ancestor_ids=(value)
write_attribute(self.ancestry_base_class.ancestry_column, generate_ancestry(value))
write_attribute(self.class.ancestry_column, generate_ancestry(value))
end

def ancestor_ids
parse_ancestry_column(read_attribute(self.ancestry_base_class.ancestry_column))
parse_ancestry_column(read_attribute(self.class.ancestry_column))
end

def ancestor_ids_before_last_save
parse_ancestry_column(attribute_before_last_save(self.ancestry_base_class.ancestry_column))
parse_ancestry_column(attribute_before_last_save(self.class.ancestry_column))
end

def parent_id_before_last_save
parse_ancestry_column(attribute_before_last_save(self.ancestry_base_class.ancestry_column)).last
parse_ancestry_column(attribute_before_last_save(self.class.ancestry_column)).last
end

# optimization - better to go directly to column and avoid parsing
def sibling_of?(node)
self.read_attribute(self.ancestry_base_class.ancestry_column) == node.read_attribute(self.ancestry_base_class.ancestry_column)
self.read_attribute(self.class.ancestry_column) == node.read_attribute(node.class.ancestry_column)
end

# private (public so class methods can find it)
Expand All @@ -143,26 +143,26 @@ def sibling_of?(node)
def child_ancestry
# New records cannot have children
raise Ancestry::AncestryException.new(I18n.t("ancestry.no_child_for_new_record")) if new_record?
[attribute_in_database(self.ancestry_base_class.ancestry_column), id].compact.join(self.ancestry_base_class.ancestry_delimiter)
[attribute_in_database(self.class.ancestry_column), id].compact.join(self.class.ancestry_delimiter)
end

def child_ancestry_before_save
# New records cannot have children
raise Ancestry::AncestryException.new(I18n.t("ancestry.no_child_for_new_record")) if new_record?
[attribute_before_last_save(self.ancestry_base_class.ancestry_column), id].compact.join(self.ancestry_base_class.ancestry_delimiter)
[attribute_before_last_save(self.class.ancestry_column), id].compact.join(self.class.ancestry_delimiter)
end

def parse_ancestry_column(obj)
return [] if obj.nil? || obj == self.ancestry_base_class.ancestry_root
obj_ids = obj.split(self.ancestry_base_class.ancestry_delimiter).delete_if(&:blank?)
return [] if obj.nil? || obj == self.class.ancestry_root
obj_ids = obj.split(self.class.ancestry_delimiter).delete_if(&:blank?)
self.class.primary_key_is_an_integer? ? obj_ids.map!(&:to_i) : obj_ids
end

def generate_ancestry(ancestor_ids)
if ancestor_ids.present? && ancestor_ids.any?
ancestor_ids.join(self.ancestry_base_class.ancestry_delimiter)
ancestor_ids.join(self.class.ancestry_delimiter)
else
self.ancestry_base_class.ancestry_root
self.class.ancestry_root
end
end
end
Expand Down
8 changes: 4 additions & 4 deletions lib/ancestry/materialized_path2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,20 @@ module InstanceMethods
def child_ancestry
# New records cannot have children
raise Ancestry::AncestryException.new(I18n.t("ancestry.no_child_for_new_record")) if new_record?
"#{attribute_in_database(self.ancestry_base_class.ancestry_column)}#{id}#{self.ancestry_base_class.ancestry_delimiter}"
"#{attribute_in_database(self.class.ancestry_column)}#{id}#{self.class.ancestry_delimiter}"
end

def child_ancestry_before_save
# New records cannot have children
raise Ancestry::AncestryException.new(I18n.t("ancestry.no_child_for_new_record")) if new_record?
"#{attribute_before_last_save(self.ancestry_base_class.ancestry_column)}#{id}#{self.ancestry_base_class.ancestry_delimiter}"
"#{attribute_before_last_save(self.class.ancestry_column)}#{id}#{self.class.ancestry_delimiter}"
end

def generate_ancestry(ancestor_ids)
if ancestor_ids.present? && ancestor_ids.any?
"#{self.ancestry_base_class.ancestry_delimiter}#{ancestor_ids.join(self.ancestry_base_class.ancestry_delimiter)}#{self.ancestry_base_class.ancestry_delimiter}"
"#{self.class.ancestry_delimiter}#{ancestor_ids.join(self.class.ancestry_delimiter)}#{self.class.ancestry_delimiter}"
else
self.ancestry_base_class.ancestry_root
self.class.ancestry_root
end
end
end
Expand Down
9 changes: 4 additions & 5 deletions lib/ancestry/materialized_path_pg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ module MaterializedPathPg
def update_descendants_with_new_ancestry
# If enabled and node is existing and ancestry was updated and the new ancestry is sane ...
if !ancestry_callbacks_disabled? && !new_record? && ancestry_changed? && sane_ancestor_ids?
ancestry_column = ancestry_base_class.ancestry_column
old_ancestry = generate_ancestry( path_ids_before_last_save )
new_ancestry = generate_ancestry( path_ids )
update_clause = [
"#{ancestry_column} = regexp_replace(#{ancestry_column}, '^#{Regexp.escape(old_ancestry)}', '#{new_ancestry}')"
"#{self.class.ancestry_column} = regexp_replace(#{self.class.ancestry_column}, '^#{Regexp.escape(old_ancestry)}', '#{new_ancestry}')"
]

if ancestry_base_class.respond_to?(:depth_cache_column) && respond_to?(ancestry_base_class.depth_cache_column)
depth_cache_column = ancestry_base_class.depth_cache_column.to_s
update_clause << "#{depth_cache_column} = length(regexp_replace(regexp_replace(ancestry, '^#{Regexp.escape(old_ancestry)}', '#{new_ancestry}'), '[^#{ancestry_base_class.ancestry_delimiter}]', '', 'g')) #{ancestry_base_class.ancestry_format == :materialized_path2 ? '-' : '+'} 1"
if self.class.respond_to?(:depth_cache_column) && respond_to?(self.class.depth_cache_column)
depth_cache_column = self.class.depth_cache_column.to_s
update_clause << "#{depth_cache_column} = length(regexp_replace(regexp_replace(ancestry, '^#{Regexp.escape(old_ancestry)}', '#{new_ancestry}'), '[^#{self.class.ancestry_delimiter}]', '', 'g')) #{self.class.ancestry_format == :materialized_path2 ? '-' : '+'} 1"
end

unscoped_descendants_before_save.update_all update_clause.join(', ')
Expand Down
Loading