Module: Mongoid::Ancestry

Extended by:
ActiveSupport::Concern
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.2.3'

Instance Method Summary collapse

Instance Method Details

#ancestor_conditionsObject



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

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

#ancestor_idsObject

Ancestors



78
79
80
# File 'lib/mongoid-ancestry/instance_methods.rb', line 78

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

#ancestors(depth_options = {}) ⇒ Object



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

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

#ancestry_callbacks_disabled?Boolean

Returns:

  • (Boolean)


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

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



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

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



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

def cache_depth
  write_attribute self.base_class.depth_cache_field, depth
end

#child_ancestryObject

The ancestry value for this record’s children

Raises:



66
67
68
69
70
71
72
73
74
75
# File 'lib/mongoid-ancestry/instance_methods.rb', line 66

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



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

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

#child_idsObject



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

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

#childrenObject



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

def children
  self.base_class.where(child_conditions)
end

#depthObject



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

def depth
  ancestor_ids.size
end

#descendant_conditionsObject

Descendants



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

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

#descendant_ids(depth_options = {}) ⇒ Object



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

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

#descendants(depth_options = {}) ⇒ Object



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

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

#has_children?Boolean

Returns:

  • (Boolean)


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

def has_children?
  self.children.present?
end

#has_siblings?Boolean

Returns:

  • (Boolean)


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

def has_siblings?
  self.siblings.count > 1
end

#is_childless?Boolean

Returns:

  • (Boolean)


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

def is_childless?
  !has_children?
end

#is_only_child?Boolean

Returns:

  • (Boolean)


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

def is_only_child?
  !has_siblings?
end

#is_root?Boolean

Returns:

  • (Boolean)


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

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

#parentObject



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

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

#parent=(parent) ⇒ Object

Parent



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

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

#parent_idObject



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

def parent_id
  ancestor_ids.empty? ? nil : ancestor_ids.last
end

#parent_id=(parent_id) ⇒ Object



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

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

#path(depth_options = {}) ⇒ Object



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

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

#path_conditionsObject



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

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

#path_idsObject



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

def path_ids
  ancestor_ids + [id]
end

#rootObject



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

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

#root_idObject

Root



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

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

#sibling_conditionsObject

Siblings



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

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

#sibling_idsObject



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

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

#siblingsObject



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

def siblings
  self.base_class.where sibling_conditions
end

#subtree(depth_options = {}) ⇒ Object



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

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

#subtree_conditionsObject

Subtree



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

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



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

def subtree_ids depth_options = {}
  subtree(depth_options).only(:_id).map(&:id)
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



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

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