# File lib/ancestry/instance_methods.rb, line 236 def primary_key_type @primary_key_type ||= column_for_attribute(self.class.primary_key).type end
# File lib/ancestry/instance_methods.rb, line 84 def ancestor_conditions {primary_key_with_table => ancestor_ids} end
# File lib/ancestry/instance_methods.rb, line 80 def ancestor_ids read_attribute(self.ancestry_base_class.ancestry_column).to_s.split('/').map { |id| cast_primary_key(id) } end
# File lib/ancestry/instance_methods.rb, line 88 def ancestors depth_options = {} self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where ancestor_conditions end
# File lib/ancestry/instance_methods.rb, line 222 def ancestry_callbacks_disabled? !!@disable_ancestry_callbacks end
Ancestors
# File lib/ancestry/instance_methods.rb, line 76 def ancestry_changed? changed.include?(self.ancestry_base_class.ancestry_column.to_s) end
Validate that the ancestors don't include itself
# File lib/ancestry/instance_methods.rb, line 4 def ancestry_exclude_self errors.add(:base, "#{self.class.name.humanize} cannot be a descendant of itself.") if ancestor_ids.include? self.id end
Apply orphan strategy
# File lib/ancestry/instance_methods.rb, line 32 def apply_orphan_strategy # Skip this if callbacks are disabled unless ancestry_callbacks_disabled? # If this isn't a new record ... unless new_record? # ... make all children root if orphan strategy is rootify if self.ancestry_base_class.orphan_strategy == :rootify unscoped_descendants.each do |descendant| descendant.without_ancestry_callbacks do descendant.update_attribute descendant.class.ancestry_column, (if descendant.ancestry == child_ancestry then nil else descendant.ancestry.gsub(/^#{child_ancestry}\//, '') end) end end # ... destroy all descendants if orphan strategy is destroy elsif self.ancestry_base_class.orphan_strategy == :destroy unscoped_descendants.each do |descendant| descendant.without_ancestry_callbacks do descendant.destroy end end # ... make child elements of this node, child of its parent if orphan strategy is adopt elsif self.ancestry_base_class.orphan_strategy == :adopt descendants.each do |descendant| descendant.without_ancestry_callbacks do new_ancestry = descendant.ancestor_ids.delete_if { |x| x == self.id }.join("/") descendant.update_attribute descendant.class.ancestry_column, new_ancestry || nil end end # ... throw an exception if it has children and orphan strategy is restrict elsif self.ancestry_base_class.orphan_strategy == :restrict raise Ancestry::AncestryException.new('Cannot delete record because it has descendants.') unless is_childless? end end end end
# File lib/ancestry/instance_methods.rb, line 108 def cache_depth write_attribute self.ancestry_base_class.depth_cache_column, depth end
The ancestry value for this record's children
# File lib/ancestry/instance_methods.rb, line 68 def child_ancestry # New records cannot have children raise Ancestry::AncestryException.new('No child ancestry for new record. Save record before performing tree operations.') if new_record? if self.send("#{self.ancestry_base_class.ancestry_column}_was").blank? then id.to_s else "#{self.send "#{self.ancestry_base_class.ancestry_column}_was"}/#{id}" end end
Children
# File lib/ancestry/instance_methods.rb, line 148 def child_conditions {ancestry_column_with_table => child_ancestry} end
# File lib/ancestry/instance_methods.rb, line 156 def child_ids children.select(self.ancestry_base_class.primary_key).map(&self.ancestry_base_class.primary_key.to_sym) end
# File lib/ancestry/instance_methods.rb, line 152 def children self.ancestry_base_class.where child_conditions end
# File lib/ancestry/instance_methods.rb, line 104 def depth ancestor_ids.size end
Descendants
# File lib/ancestry/instance_methods.rb, line 190 def descendant_conditions ["#{ancestry_column_with_table} like ? or #{ancestry_column_with_table} = ?", "#{child_ancestry}/%", child_ancestry] end
# File lib/ancestry/instance_methods.rb, line 198 def descendant_ids depth_options = {} descendants(depth_options).select(self.ancestry_base_class.primary_key).collect(&self.ancestry_base_class.primary_key.to_sym) end
# File lib/ancestry/instance_methods.rb, line 194 def descendants depth_options = {} self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).where descendant_conditions end
# File lib/ancestry/instance_methods.rb, line 160 def has_children? self.children.exists?({}) end
# File lib/ancestry/instance_methods.rb, line 181 def has_siblings? self.siblings.count > 1 end
# File lib/ancestry/instance_methods.rb, line 164 def is_childless? !has_children? end
# File lib/ancestry/instance_methods.rb, line 185 def is_only_child? !has_siblings? end
# File lib/ancestry/instance_methods.rb, line 142 def is_root? read_attribute(self.ancestry_base_class.ancestry_column).blank? end
# File lib/ancestry/instance_methods.rb, line 125 def parent if parent_id.blank? then nil else unscoped_find(parent_id) end end
Parent
# File lib/ancestry/instance_methods.rb, line 113 def parent= parent write_attribute(self.ancestry_base_class.ancestry_column, if parent.nil? then nil else parent.child_ancestry end) end
# File lib/ancestry/instance_methods.rb, line 121 def parent_id if ancestor_ids.empty? then nil else ancestor_ids.last end end
# File lib/ancestry/instance_methods.rb, line 117 def parent_id= parent_id self.parent = if parent_id.blank? then nil else unscoped_find(parent_id) end end
# File lib/ancestry/instance_methods.rb, line 129 def parent_id? parent_id.present? end
# File lib/ancestry/instance_methods.rb, line 100 def path depth_options = {} self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where path_conditions end
# File lib/ancestry/instance_methods.rb, line 96 def path_conditions {primary_key_with_table => path_ids} end
# File lib/ancestry/instance_methods.rb, line 92 def path_ids ancestor_ids + [id] end
# File lib/ancestry/instance_methods.rb, line 138 def root if root_id == id then self else unscoped_find(root_id) end end
Root
# File lib/ancestry/instance_methods.rb, line 134 def root_id if ancestor_ids.empty? then id else ancestor_ids.first end end
Siblings
# File lib/ancestry/instance_methods.rb, line 169 def sibling_conditions {ancestry_column_with_table => read_attribute(self.ancestry_base_class.ancestry_column)} end
# File lib/ancestry/instance_methods.rb, line 177 def sibling_ids siblings.select(self.ancestry_base_class.primary_key).collect(&self.ancestry_base_class.primary_key.to_sym) end
# File lib/ancestry/instance_methods.rb, line 173 def siblings self.ancestry_base_class.where sibling_conditions end
# File lib/ancestry/instance_methods.rb, line 207 def subtree depth_options = {} self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).where subtree_conditions end
Subtree
# File lib/ancestry/instance_methods.rb, line 203 def subtree_conditions ["#{primary_key_with_table} = ? or #{ancestry_column_with_table} like ? or #{ancestry_column_with_table} = ?", self.id, "#{child_ancestry}/%", child_ancestry] end
# File lib/ancestry/instance_methods.rb, line 211 def subtree_ids depth_options = {} subtree(depth_options).select(self.ancestry_base_class.primary_key).collect(&self.ancestry_base_class.primary_key.to_sym) end
Update descendants with new ancestry
# File lib/ancestry/instance_methods.rb, line 9 def update_descendants_with_new_ancestry # Skip this if callbacks are disabled unless ancestry_callbacks_disabled? # If node is not a new record and ancestry was updated and the new ancestry is sane ... if ancestry_changed? && !new_record? && sane_ancestry? # ... for each descendant ... unscoped_descendants.each do |descendant| # ... replace old ancestry with new ancestry descendant.without_ancestry_callbacks do descendant.update_attribute( self.ancestry_base_class.ancestry_column, descendant.read_attribute(descendant.class.ancestry_column).gsub( /^#{self.child_ancestry}/, if read_attribute(self.class.ancestry_column).blank? then id.to_s else "#{read_attribute self.class.ancestry_column }/#{id}" end ) ) end end end end end
Callback disabling
# File lib/ancestry/instance_methods.rb, line 216 def without_ancestry_callbacks @disable_ancestry_callbacks = true yield @disable_ancestry_callbacks = false end
# File lib/ancestry/instance_methods.rb, line 259 def ancestry_column_with_table "#{self.ancestry_base_class.table_name}.#{self.ancestry_base_class.ancestry_column}" end
# File lib/ancestry/instance_methods.rb, line 228 def cast_primary_key(key) if primary_key_type == :string key else key.to_i end end
# File lib/ancestry/instance_methods.rb, line 236 def primary_key_type @primary_key_type ||= column_for_attribute(self.class.primary_key).type end
# File lib/ancestry/instance_methods.rb, line 255 def primary_key_with_table "#{self.ancestry_base_class.table_name}.#{self.ancestry_base_class.primary_key}" end
basically validates the ancestry, but also applied if validation is bypassed to determine if chidren should be affected
# File lib/ancestry/instance_methods.rb, line 247 def sane_ancestry? ancestry.nil? || (ancestry.to_s =~ Ancestry::ANCESTRY_PATTERN && !ancestor_ids.include?(self.id)) end
# File lib/ancestry/instance_methods.rb, line 239 def unscoped_descendants self.ancestry_base_class.unscoped do self.ancestry_base_class.where descendant_conditions end end
# File lib/ancestry/instance_methods.rb, line 251 def unscoped_find id self.ancestry_base_class.unscoped { self.ancestry_base_class.find(id) } end