Module: Ancestry::ClassMethods

Defined in:
lib/ancestry/class_methods.rb

Instance Method Summary collapse

Instance Method Details

#arrange(options = {}) ⇒ Object

Arrangement



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/ancestry/class_methods.rb', line 31

def arrange options = {}
  scope =
    if options[:order].nil?
      self.base_class.ordered_by_ancestry
    else
      self.base_class.ordered_by_ancestry_and options.delete(:order)
    end
  # Get all nodes ordered by ancestry and start sorting them into an empty hash
  scope.all(options).inject(ActiveSupport::OrderedHash.new) do |arranged_nodes, node|
    # Find the insertion point for that node by going through its ancestors
    node.ancestor_ids.inject(arranged_nodes) do |insertion_point, ancestor_id|
      insertion_point.each do |parent, children|
        # Change the insertion point to children if node is a descendant of this parent
        insertion_point = children if ancestor_id == parent.id
      end; insertion_point
    end[node] = ActiveSupport::OrderedHash.new; arranged_nodes
  end
end

#build_ancestry_from_parent_ids!(parent_id = nil, ancestry = nil) ⇒ Object

Build ancestry from parent id’s for migration purposes



120
121
122
123
124
125
126
127
# File 'lib/ancestry/class_methods.rb', line 120

def build_ancestry_from_parent_ids! parent_id = nil, ancestry = nil
  self.base_class.all(:conditions => {self.base_class.parent_column => parent_id}).each do |node|
    node.without_ancestry_callbacks do
      node.update_attribute ancestry_column, ancestry
    end
    build_ancestry_from_parent_ids! node.id, if ancestry.nil? then "#{node.id}" else "#{ancestry}/#{node.id}" end
  end
end

#check_ancestry_integrity!(options = {}) ⇒ Object

Integrity checking



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/ancestry/class_methods.rb', line 51

def check_ancestry_integrity! options = {}
  parents = {}
  exceptions = [] if options[:report] == :list
  # For each node ...
  self.base_class.all.each do |node|
    begin
      # ... check validity of ancestry column
      if !node.valid? and !node.errors[node.class.ancestry_column].blank?
        raise Ancestry::AncestryIntegrityException.new "Invalid format for ancestry column of node #{node.id}: #{node.read_attribute node.ancestry_column}."
      end
      # ... check that all ancestors exist
      node.ancestor_ids.each do |ancestor_id|
        unless exists? ancestor_id
          raise Ancestry::AncestryIntegrityException.new "Reference to non-existent node in node #{node.id}: #{ancestor_id}."
        end
      end
      # ... check that all node parents are consistent with values observed earlier
      node.path_ids.zip([nil] + node.path_ids).each do |node_id, parent_id|
        parents[node_id] = parent_id unless parents.has_key? node_id
        unless parents[node_id] == parent_id
          raise Ancestry::AncestryIntegrityException.new "Conflicting parent id found in node #{node.id}: #{parent_id || 'nil'} for node #{node_id} while expecting #{parents[node_id] || 'nil'}"
        end
      end
    rescue Ancestry::AncestryIntegrityException => integrity_exception
      case options[:report]
        when :list then exceptions << integrity_exception
        when :echo then puts integrity_exception
        else raise integrity_exception
      end
    end
  end
  exceptions if options[:report] == :list
end

#orphan_strategy=(orphan_strategy) ⇒ Object

Orphan strategy writer



21
22
23
24
25
26
27
28
# File 'lib/ancestry/class_methods.rb', line 21

def orphan_strategy= orphan_strategy
  # Check value of orphan strategy, only rootify, restrict or destroy is allowed
  if [:rootify, :restrict, :destroy].include? orphan_strategy
    class_variable_set :@@orphan_strategy, orphan_strategy
  else
    raise Ancestry::AncestryException.new("Invalid orphan strategy, valid ones are :rootify, :restrict and :destroy.")
  end
end

#rebuild_depth_cache!Object

Rebuild depth cache if it got corrupted or if depth caching was just turned on



130
131
132
133
134
135
# File 'lib/ancestry/class_methods.rb', line 130

def rebuild_depth_cache!
  raise Ancestry::AncestryException.new("Cannot rebuild depth cache for model without depth caching.") unless respond_to? :depth_cache_column
  self.base_class.all.each do |node|
    node.update_attribute depth_cache_column, node.depth
  end
end

#restore_ancestry_integrity!Object

Integrity restoration



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/ancestry/class_methods.rb', line 86

def restore_ancestry_integrity!
  parents = {}
  # For each node ...
  self.base_class.all.each do |node|
    # ... set its ancestry to nil if invalid
    if node.errors[node.class.ancestry_column].blank?
      node.without_ancestry_callbacks do
        node.update_attribute node.ancestry_column, nil
      end
    end
    # ... save parent of this node in parents array if it exists
    parents[node.id] = node.parent_id if exists? node.parent_id

    # Reset parent id in array to nil if it introduces a cycle
    parent = parents[node.id]
    until parent.nil? || parent == node.id
      parent = parents[parent]
    end
    parents[node.id] = nil if parent == node.id 
  end
  # For each node ...
  self.base_class.all.each do |node|
    # ... rebuild ancestry from parents array
    ancestry, parent = nil, parents[node.id]
    until parent.nil?
      ancestry, parent = if ancestry.nil? then parent else "#{parent}/#{ancestry}" end, parents[parent]
    end
    node.without_ancestry_callbacks do
      node.update_attribute node.ancestry_column, ancestry
    end
  end
end

#scope_depth(depth_options, depth) ⇒ Object

Scope on relative depth options



9
10
11
12
13
14
15
16
17
18
# File 'lib/ancestry/class_methods.rb', line 9

def scope_depth depth_options, depth
  depth_options.inject(self.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
    else
      raise Ancestry::AncestryException.new("Unknown depth option: #{scope_name}.")
    end
  end
end

#to_node(object) ⇒ Object

Fetch tree node if necessary



4
5
6
# File 'lib/ancestry/class_methods.rb', line 4

def to_node object
  if object.is_a?(self.base_class) then object else find(object) end
end