Module: Mongoid::Ancestry

Extended by:
ActiveSupport::Concern
Includes:
Mongoid::Attributes::Dynamic
Defined in:
lib/mongoid-ancestry.rb,
lib/mongoid-ancestry/version.rb,
lib/mongoid-ancestry/exceptions.rb,
lib/mongoid-ancestry/class_methods.rb,
lib/mongoid-ancestry/instance_methods.rb

Defined Under Namespace

Modules: ClassMethods Classes: Error, IntegrityError

Constant Summary collapse

VERSION =
'0.4.2'

Instance Method Summary collapse

Instance Method Details

#ancestor_conditionsObject



86
87
88
# File 'lib/mongoid-ancestry/instance_methods.rb', line 86

def ancestor_conditions
  { :_id.in => ancestor_ids }
end

#ancestor_idsObject

Ancestors



82
83
84
# File 'lib/mongoid-ancestry/instance_methods.rb', line 82

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

#ancestors(depth_options = {}) ⇒ Object



90
91
92
# File 'lib/mongoid-ancestry/instance_methods.rb', line 90

def ancestors depth_options = {}
  self.base_class.scope_depth(depth_options, depth).where(ancestor_conditions)
end

#ancestry_callbacks_disabled?Boolean

Returns:

  • (Boolean)


227
228
229
# File 'lib/mongoid-ancestry/instance_methods.rb', line 227

def ancestry_callbacks_disabled?
  !!@disable_ancestry_callbacks
end

#ancestry_exclude_selfObject

Validate that the ancestors don’t include itself



5
6
7
8
9
# File 'lib/mongoid-ancestry/instance_methods.rb', line 5

def ancestry_exclude_self
  if ancestor_ids.include? id
    errors.add(:base, "#{self.class.name.humanize} cannot be a descendant of itself.")
  end
end

#apply_orphan_strategyObject

Apply orphan strategy



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/mongoid-ancestry/instance_methods.rb', line 40

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
            val = \
              unless descendant.ancestry == child_ancestry
                descendant.read_attribute(descendant.class.ancestry_field).gsub(/^#{child_ancestry}\//, '')
              end
            descendant.update_attribute descendant.class.ancestry_field, val
          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 { descendant.destroy }
        end
        # ... throw an exception if it has children and orphan strategy is restrict
      elsif self.base_class.orphan_strategy == :restrict
        raise Error.new('Cannot delete record because it has descendants.') unless is_childless?
      end
    end
  end
end

#cache_depthObject



110
111
112
# File 'lib/mongoid-ancestry/instance_methods.rb', line 110

def cache_depth
  write_attribute self.base_class.depth_cache_field, depth
end

#child_ancestryObject

The ancestry value for this record’s children

Raises:



70
71
72
73
74
75
76
77
78
79
# File 'lib/mongoid-ancestry/instance_methods.rb', line 70

def child_ancestry
  # New records cannot have children
  raise Error.new('No child ancestry for new record. Save record before performing tree operations.') if new_record?

  if self.send("#{self.base_class.ancestry_field}_was").blank?
    id.to_s
  else
    "#{self.send "#{self.base_class.ancestry_field}_was"}/#{id}"
  end
end

#child_conditionsObject

Children



146
147
148
# File 'lib/mongoid-ancestry/instance_methods.rb', line 146

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

#child_idsObject



154
155
156
# File 'lib/mongoid-ancestry/instance_methods.rb', line 154

def child_ids
  children.only(:_id).map(&:id)
end

#childrenObject



150
151
152
# File 'lib/mongoid-ancestry/instance_methods.rb', line 150

def children
  self.base_class.where(child_conditions)
end

#depthObject



106
107
108
# File 'lib/mongoid-ancestry/instance_methods.rb', line 106

def depth
  ancestor_ids.size
end

#descendant_conditionsObject

Descendants



188
189
190
191
192
193
# File 'lib/mongoid-ancestry/instance_methods.rb', line 188

def descendant_conditions
  [
    { self.base_class.ancestry_field => /^#{child_ancestry}\// },
    { self.base_class.ancestry_field => child_ancestry }
  ]
end

#descendant_ids(depth_options = {}) ⇒ Object



199
200
201
# File 'lib/mongoid-ancestry/instance_methods.rb', line 199

def descendant_ids depth_options = {}
  descendants(depth_options).only(:_id).map(&:id)
end

#descendants(depth_options = {}) ⇒ Object



195
196
197
# File 'lib/mongoid-ancestry/instance_methods.rb', line 195

def descendants depth_options = {}
  self.base_class.scope_depth(depth_options, depth).any_of(descendant_conditions)
end

#has_children?Boolean

Returns:

  • (Boolean)


158
159
160
# File 'lib/mongoid-ancestry/instance_methods.rb', line 158

def has_children?
  self.children.present?
end

#has_siblings?Boolean

Returns:

  • (Boolean)


179
180
181
# File 'lib/mongoid-ancestry/instance_methods.rb', line 179

def has_siblings?
  self.siblings.count > 1
end

#is_childless?Boolean

Returns:

  • (Boolean)


162
163
164
# File 'lib/mongoid-ancestry/instance_methods.rb', line 162

def is_childless?
  !has_children?
end

#is_only_child?Boolean

Returns:

  • (Boolean)


183
184
185
# File 'lib/mongoid-ancestry/instance_methods.rb', line 183

def is_only_child?
  !has_siblings?
end

#is_root?Boolean

Returns:

  • (Boolean)


141
142
143
# File 'lib/mongoid-ancestry/instance_methods.rb', line 141

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

#parentObject



128
129
130
# File 'lib/mongoid-ancestry/instance_methods.rb', line 128

def parent
  parent_id.blank? ? nil : self.base_class.find(parent_id)
end

#parent=(parent) ⇒ Object

Parent



115
116
117
# File 'lib/mongoid-ancestry/instance_methods.rb', line 115

def parent= parent
  write_attribute(self.base_class.ancestry_field, parent.blank? ? nil : parent.child_ancestry)
end

#parent_idObject



123
124
125
126
# File 'lib/mongoid-ancestry/instance_methods.rb', line 123

def parent_id
  parent_id = read_attribute(self.base_class.ancestry_field).to_s.split('/').last
  return cast_primary_key(parent_id) if parent_id
end

#parent_id=(parent_id) ⇒ Object



119
120
121
# File 'lib/mongoid-ancestry/instance_methods.rb', line 119

def parent_id= parent_id
  self.parent = parent_id.blank? ? nil : self.base_class.find(parent_id)
end

#path(depth_options = {}) ⇒ Object



102
103
104
# File 'lib/mongoid-ancestry/instance_methods.rb', line 102

def path depth_options = {}
  self.base_class.scope_depth(depth_options, depth).where(path_conditions)
end

#path_conditionsObject



98
99
100
# File 'lib/mongoid-ancestry/instance_methods.rb', line 98

def path_conditions
  { :_id.in => path_ids }
end

#path_idsObject



94
95
96
# File 'lib/mongoid-ancestry/instance_methods.rb', line 94

def path_ids
  ancestor_ids + [id]
end

#rootObject



137
138
139
# File 'lib/mongoid-ancestry/instance_methods.rb', line 137

def root
  (root_id == id) ? self : self.base_class.find(root_id)
end

#root_idObject

Root



133
134
135
# File 'lib/mongoid-ancestry/instance_methods.rb', line 133

def root_id
  ancestor_ids.empty? ? id : ancestor_ids.first
end

#sibling_conditionsObject

Siblings



167
168
169
# File 'lib/mongoid-ancestry/instance_methods.rb', line 167

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

#sibling_idsObject



175
176
177
# File 'lib/mongoid-ancestry/instance_methods.rb', line 175

def sibling_ids
  siblings.only(:_id).map(&:id)
end

#siblingsObject



171
172
173
# File 'lib/mongoid-ancestry/instance_methods.rb', line 171

def siblings
  self.base_class.where sibling_conditions
end

#subtree(depth_options = {}) ⇒ Object



212
213
214
# File 'lib/mongoid-ancestry/instance_methods.rb', line 212

def subtree depth_options = {}
  self.base_class.scope_depth(depth_options, depth).any_of(subtree_conditions)
end

#subtree_conditionsObject

Subtree



204
205
206
207
208
209
210
# File 'lib/mongoid-ancestry/instance_methods.rb', line 204

def subtree_conditions
  [
    { :_id => id },
    { self.base_class.ancestry_field => /^#{child_ancestry}\// },
    { self.base_class.ancestry_field => child_ancestry }
  ]
end

#subtree_ids(depth_options = {}) ⇒ Object



216
217
218
# File 'lib/mongoid-ancestry/instance_methods.rb', line 216

def subtree_ids depth_options = {}
  subtree(depth_options).only(:_id).map(&:id)
end

#touch_parentObject



35
36
37
# File 'lib/mongoid-ancestry/instance_methods.rb', line 35

def touch_parent
  parent.touch
end

#update_descendants_with_new_ancestryObject

Update descendants with new ancestry



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/mongoid-ancestry/instance_methods.rb', line 12

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_field.to_s) && !new_record? && valid?
      # ... for each descendant ...
      descendants.each do |descendant|
        # ... replace old ancestry with new ancestry
        descendant.without_ancestry_callbacks do
          for_replace = \
            if read_attribute(self.class.ancestry_field).blank?
              id.to_s
            else
              "#{read_attribute self.class.ancestry_field}/#{id}"
            end
          new_ancestry = descendant.read_attribute(descendant.class.ancestry_field).gsub(/^#{self.child_ancestry}/, for_replace)
          descendant.update_attribute(self.base_class.ancestry_field, new_ancestry)
        end
      end
    end
  end
end

#without_ancestry_callbacksObject

Callback disabling



221
222
223
224
225
# File 'lib/mongoid-ancestry/instance_methods.rb', line 221

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