Module: Ancestry::InstanceMethods

Defined in:
lib/ancestry/instance_methods.rb

Instance Method Summary collapse

Instance Method Details

#ancestor_conditionsObject



72
73
74
# File 'lib/ancestry/instance_methods.rb', line 72

def ancestor_conditions
  {self.base_class.primary_key => ancestor_ids}
end

#ancestor_idsObject

Ancestors



68
69
70
# File 'lib/ancestry/instance_methods.rb', line 68

def ancestor_ids
  read_attribute(self.base_class.ancestry_column).to_s.split('/').map { |id| cast_primary_key(id) }
end

#ancestors(depth_options = {}) ⇒ Object



76
77
78
# File 'lib/ancestry/instance_methods.rb', line 76

def ancestors depth_options = {}
  self.base_class.scope_depth(depth_options, depth).ordered_by_ancestry.scoped :conditions => ancestor_conditions
end

#ancestry_callbacks_disabled?Boolean

Returns:

  • (Boolean)


205
206
207
# File 'lib/ancestry/instance_methods.rb', line 205

def ancestry_callbacks_disabled?
  !!@disable_ancestry_callbacks
end

#ancestry_exclude_selfObject

Validate that the ancestors don’t include itself



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

def ancestry_exclude_self
  add_error_to_base "#{self.class.name.humanize} cannot be a descendant of itself." if ancestor_ids.include? self.id
end

#apply_orphan_strategyObject

Apply orphan strategy



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 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 al children root if orphan strategy is rootify
      if self.base_class.orphan_strategy == :rootify
        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.base_class.orphan_strategy == :destroy
        descendants.all.each do |descendant|
          descendant.without_ancestry_callbacks do
            descendant.destroy
          end
        end
      # ... throw an exception if it has children and orphan strategy is restrict
      elsif self.base_class.orphan_strategy == :restrict
        raise Ancestry::AncestryException.new('Cannot delete record because it has descendants.') unless is_childless?
      end
    end
  end
end

#cache_depthObject



96
97
98
# File 'lib/ancestry/instance_methods.rb', line 96

def cache_depth
  write_attribute self.base_class.depth_cache_column, depth
end

#child_ancestryObject

The ancestry value for this record’s children



60
61
62
63
64
65
# File 'lib/ancestry/instance_methods.rb', line 60

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.base_class.ancestry_column}_was").blank? then id.to_s else "#{self.send "#{self.base_class.ancestry_column}_was"}/#{id}" end
end

#child_conditionsObject

Children



131
132
133
# File 'lib/ancestry/instance_methods.rb', line 131

def child_conditions
  {self.base_class.ancestry_column => child_ancestry}
end

#child_idsObject



139
140
141
# File 'lib/ancestry/instance_methods.rb', line 139

def child_ids
  children.all(:select => self.base_class.primary_key).map(&self.base_class.primary_key.to_sym)
end

#childrenObject



135
136
137
# File 'lib/ancestry/instance_methods.rb', line 135

def children
  self.base_class.scoped :conditions => child_conditions
end

#depthObject



92
93
94
# File 'lib/ancestry/instance_methods.rb', line 92

def depth
  ancestor_ids.size
end

#descendant_conditionsObject

Descendants



173
174
175
# File 'lib/ancestry/instance_methods.rb', line 173

def descendant_conditions
  ["#{self.base_class.ancestry_column} like ? or #{self.base_class.ancestry_column} = ?", "#{child_ancestry}/%", child_ancestry]
end

#descendant_ids(depth_options = {}) ⇒ Object



181
182
183
# File 'lib/ancestry/instance_methods.rb', line 181

def descendant_ids depth_options = {}
  descendants(depth_options).all(:select => self.base_class.primary_key).collect(&self.base_class.primary_key.to_sym)
end

#descendants(depth_options = {}) ⇒ Object



177
178
179
# File 'lib/ancestry/instance_methods.rb', line 177

def descendants depth_options = {}
  self.base_class.ordered_by_ancestry.scope_depth(depth_options, depth).scoped :conditions => descendant_conditions
end

#has_children?Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/ancestry/instance_methods.rb', line 143

def has_children?
  self.children.exists?({})
end

#has_siblings?Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/ancestry/instance_methods.rb', line 164

def has_siblings?
  self.siblings.count > 1
end

#is_childless?Boolean

Returns:

  • (Boolean)


147
148
149
# File 'lib/ancestry/instance_methods.rb', line 147

def is_childless?
  !has_children?
end

#is_only_child?Boolean

Returns:

  • (Boolean)


168
169
170
# File 'lib/ancestry/instance_methods.rb', line 168

def is_only_child?
  !has_siblings?
end

#is_root?Boolean

Returns:

  • (Boolean)


126
127
128
# File 'lib/ancestry/instance_methods.rb', line 126

def is_root?
  read_attribute(self.base_class.ancestry_column).blank?
end

#parentObject



113
114
115
# File 'lib/ancestry/instance_methods.rb', line 113

def parent
  if parent_id.blank? then nil else self.base_class.find(parent_id) end
end

#parent=(parent) ⇒ Object

Parent



101
102
103
# File 'lib/ancestry/instance_methods.rb', line 101

def parent= parent
  write_attribute(self.base_class.ancestry_column, if parent.blank? then nil else parent.child_ancestry end)
end

#parent_idObject



109
110
111
# File 'lib/ancestry/instance_methods.rb', line 109

def parent_id
  if ancestor_ids.empty? then nil else ancestor_ids.last end
end

#parent_id=(parent_id) ⇒ Object



105
106
107
# File 'lib/ancestry/instance_methods.rb', line 105

def parent_id= parent_id
  self.parent = if parent_id.blank? then nil else self.base_class.find(parent_id) end
end

#path(depth_options = {}) ⇒ Object



88
89
90
# File 'lib/ancestry/instance_methods.rb', line 88

def path depth_options = {}
  self.base_class.scope_depth(depth_options, depth).ordered_by_ancestry.scoped :conditions => path_conditions
end

#path_conditionsObject



84
85
86
# File 'lib/ancestry/instance_methods.rb', line 84

def path_conditions
  {self.base_class.primary_key => path_ids}
end

#path_idsObject



80
81
82
# File 'lib/ancestry/instance_methods.rb', line 80

def path_ids
  ancestor_ids + [id]
end

#rootObject



122
123
124
# File 'lib/ancestry/instance_methods.rb', line 122

def root
  if root_id == id then self else self.base_class.find(root_id) end
end

#root_idObject

Root



118
119
120
# File 'lib/ancestry/instance_methods.rb', line 118

def root_id
  if ancestor_ids.empty? then id else ancestor_ids.first end
end

#sibling_conditionsObject

Siblings



152
153
154
# File 'lib/ancestry/instance_methods.rb', line 152

def sibling_conditions
  {self.base_class.ancestry_column => read_attribute(self.base_class.ancestry_column)}
end

#sibling_idsObject



160
161
162
# File 'lib/ancestry/instance_methods.rb', line 160

def sibling_ids
   siblings.all(:select => self.base_class.primary_key).collect(&self.base_class.primary_key.to_sym)
end

#siblingsObject



156
157
158
# File 'lib/ancestry/instance_methods.rb', line 156

def siblings
  self.base_class.scoped :conditions => sibling_conditions
end

#subtree(depth_options = {}) ⇒ Object



190
191
192
# File 'lib/ancestry/instance_methods.rb', line 190

def subtree depth_options = {}
  self.base_class.ordered_by_ancestry.scope_depth(depth_options, depth).scoped :conditions => subtree_conditions
end

#subtree_conditionsObject

Subtree



186
187
188
# File 'lib/ancestry/instance_methods.rb', line 186

def subtree_conditions
  ["#{self.base_class.primary_key} = ? or #{self.base_class.ancestry_column} like ? or #{self.base_class.ancestry_column} = ?", self.id, "#{child_ancestry}/%", child_ancestry]
end

#subtree_ids(depth_options = {}) ⇒ Object



194
195
196
# File 'lib/ancestry/instance_methods.rb', line 194

def subtree_ids depth_options = {}
  subtree(depth_options).all(:select => self.base_class.primary_key).collect(&self.base_class.primary_key.to_sym)
end

#update_descendants_with_new_ancestryObject

Update descendants with new ancestry



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 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 valid, not a new record and ancestry was updated ...
    if changed.include?(self.base_class.ancestry_column.to_s) && !new_record? && valid?
      # ... for each descendant ...
      descendants.each do |descendant|
        # ... replace old ancestry with new ancestry
        descendant.without_ancestry_callbacks do
          descendant.update_attribute(
            self.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

#without_ancestry_callbacksObject

Callback disabling



199
200
201
202
203
# File 'lib/ancestry/instance_methods.rb', line 199

def without_ancestry_callbacks
  @disable_ancestry_callbacks = true
  yield
  @disable_ancestry_callbacks = false
end