ancestry 4.3.3 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,33 +1,40 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Ancestry
2
4
  module HasAncestry
3
- def has_ancestry options = {}
5
+ def has_ancestry(options = {})
4
6
  # Check options
5
- raise Ancestry::AncestryException.new(I18n.t("ancestry.option_must_be_hash")) unless options.is_a? Hash
6
- options.each do |key, value|
7
- unless [:ancestry_column, :orphan_strategy, :cache_depth, :depth_cache_column, :touch, :counter_cache, :primary_key_format, :update_strategy, :ancestry_format].include? key
8
- raise Ancestry::AncestryException.new(I18n.t("ancestry.unknown_option", key: key.inspect, value: value.inspect))
9
- end
7
+ unless options.is_a? Hash
8
+ raise Ancestry::AncestryException, I18n.t("ancestry.option_must_be_hash")
9
+ end
10
+
11
+ extra_keys = options.keys - [:ancestry_column, :orphan_strategy, :cache_depth, :depth_cache_column, :touch, :counter_cache, :primary_key_format, :update_strategy, :ancestry_format]
12
+ if (key = extra_keys.first)
13
+ raise Ancestry::AncestryException, I18n.t("ancestry.unknown_option", key: key.inspect, value: options[key].inspect)
10
14
  end
11
15
 
12
- if options[:ancestry_format].present? && ![:materialized_path, :materialized_path2].include?( options[:ancestry_format] )
13
- raise Ancestry::AncestryException.new(I18n.t("ancestry.unknown_format", value: options[:ancestry_format]))
16
+ ancestry_format = options[:ancestry_format] || Ancestry.default_ancestry_format
17
+ if ![:materialized_path, :materialized_path2].include?(ancestry_format)
18
+ raise Ancestry::AncestryException, I18n.t("ancestry.unknown_format", value: ancestry_format)
14
19
  end
15
20
 
21
+ orphan_strategy = options[:orphan_strategy] || :destroy
22
+
16
23
  # Create ancestry column accessor and set to option or default
17
- cattr_accessor :ancestry_column
18
- self.ancestry_column = options[:ancestry_column] || :ancestry
24
+ class_variable_set('@@ancestry_column', options[:ancestry_column] || :ancestry)
25
+ cattr_reader :ancestry_column, instance_reader: false
19
26
 
20
- cattr_accessor :ancestry_primary_key_format
21
- self.ancestry_primary_key_format = options[:primary_key_format].presence || Ancestry.default_primary_key_format
27
+ primary_key_format = options[:primary_key_format].presence || Ancestry.default_primary_key_format
22
28
 
23
- cattr_accessor :ancestry_delimiter
24
- self.ancestry_delimiter = '/'
29
+ class_variable_set('@@ancestry_delimiter', '/')
30
+ cattr_reader :ancestry_delimiter, instance_reader: false
25
31
 
26
32
  # Save self as base class (for STI)
27
- cattr_accessor :ancestry_base_class
28
- self.ancestry_base_class = self
33
+ class_variable_set('@@ancestry_base_class', self)
34
+ cattr_reader :ancestry_base_class, instance_reader: false
29
35
 
30
36
  # Touch ancestors after updating
37
+ # days are limited. need to handle touch in pg case
31
38
  cattr_accessor :touch_ancestors
32
39
  self.touch_ancestors = options[:touch] || false
33
40
 
@@ -36,41 +43,54 @@ module Ancestry
36
43
 
37
44
  # Include dynamic class methods
38
45
  extend Ancestry::ClassMethods
46
+ extend Ancestry::HasAncestry.ancestry_format_module(ancestry_format)
39
47
 
40
- cattr_accessor :ancestry_format
41
- self.ancestry_format = options[:ancestry_format] || Ancestry.default_ancestry_format
48
+ attribute ancestry_column, default: ancestry_root
42
49
 
43
- if ancestry_format == :materialized_path2
44
- extend Ancestry::MaterializedPath2
45
- else
46
- extend Ancestry::MaterializedPath
47
- end
48
-
49
- attribute self.ancestry_column, default: self.ancestry_root
50
-
51
- validates self.ancestry_column, ancestry_validation_options
50
+ validates ancestry_column, ancestry_validation_options(primary_key_format)
52
51
 
53
52
  update_strategy = options[:update_strategy] || Ancestry.default_update_strategy
54
- include Ancestry::MaterializedPathPg if update_strategy == :sql
55
-
56
- # Create orphan strategy accessor and set to option or default (writer comes from DynamicClassMethods)
57
- cattr_reader :orphan_strategy
58
- self.orphan_strategy = options[:orphan_strategy] || :destroy
53
+ include Ancestry::MaterializedPathPg
59
54
 
60
55
  # Validate that the ancestor ids don't include own id
61
56
  validate :ancestry_exclude_self
62
57
 
58
+ # Validate descendants' depths don't exceed max depth when moving them
59
+ validate :ancestry_depth_of_descendants, if: :ancestry_changed?
60
+
63
61
  # Update descendants with new ancestry after update
64
- after_update :update_descendants_with_new_ancestry
62
+ if update_strategy == :sql
63
+ after_update :update_descendants_with_new_ancestry_sql, if: :ancestry_changed?
64
+ else
65
+ after_update :update_descendants_with_new_ancestry, if: :ancestry_changed?
66
+ end
65
67
 
66
68
  # Apply orphan strategy before destroy
67
- before_destroy :apply_orphan_strategy
69
+ orphan_strategy_helper = "apply_orphan_strategy_#{orphan_strategy}"
70
+ if method_defined?(orphan_strategy_helper)
71
+ alias_method :apply_orphan_strategy, orphan_strategy_helper
72
+ before_destroy :apply_orphan_strategy
73
+ elsif orphan_strategy.to_s != "none"
74
+ raise Ancestry::AncestryException, I18n.t("ancestry.invalid_orphan_strategy")
75
+ end
68
76
 
69
77
  # Create ancestry column accessor and set to option or default
70
- if options[:cache_depth]
78
+
79
+ if options[:cache_depth] == :virtual
80
+ # NOTE: not setting self.depth_cache_column so the code does not try to update the column
81
+ depth_cache_sql = options[:depth_cache_column]&.to_s || 'ancestry_depth'
82
+ elsif options[:cache_depth]
71
83
  # Create accessor for column name and set to option or default
72
- self.cattr_accessor :depth_cache_column
73
- self.depth_cache_column = options[:depth_cache_column] || :ancestry_depth
84
+ cattr_accessor :depth_cache_column
85
+ self.depth_cache_column =
86
+ if options[:cache_depth] == true
87
+ options[:depth_cache_column]&.to_s || 'ancestry_depth'
88
+ else
89
+ options[:cache_depth].to_s
90
+ end
91
+ if options[:depth_cache_column]
92
+ ActiveSupport::Deprecation.warn("has_ancestry :depth_cache_column is deprecated. Use :cache_depth instead.")
93
+ end
74
94
 
75
95
  # Cache depth in depth cache column before save
76
96
  before_validation :cache_depth
@@ -78,8 +98,19 @@ module Ancestry
78
98
 
79
99
  # Validate depth column
80
100
  validates_numericality_of depth_cache_column, :greater_than_or_equal_to => 0, :only_integer => true, :allow_nil => false
101
+
102
+ depth_cache_sql = depth_cache_column
103
+ else
104
+ # this is not efficient, but it works
105
+ depth_cache_sql = ancestry_depth_sql
81
106
  end
82
107
 
108
+ scope :before_depth, lambda { |depth| where("#{depth_cache_sql} < ?", depth) }
109
+ scope :to_depth, lambda { |depth| where("#{depth_cache_sql} <= ?", depth) }
110
+ scope :at_depth, lambda { |depth| where("#{depth_cache_sql} = ?", depth) }
111
+ scope :from_depth, lambda { |depth| where("#{depth_cache_sql} >= ?", depth) }
112
+ scope :after_depth, lambda { |depth| where("#{depth_cache_sql} > ?", depth) }
113
+
83
114
  # Create counter cache column accessor and set to option or default
84
115
  if options[:counter_cache]
85
116
  cattr_accessor :counter_cache_column
@@ -90,25 +121,27 @@ module Ancestry
90
121
  after_update :update_parent_counter_cache
91
122
  end
92
123
 
93
- # Create named scopes for depth
94
- {:before_depth => '<', :to_depth => '<=', :at_depth => '=', :from_depth => '>=', :after_depth => '>'}.each do |scope_name, operator|
95
- scope scope_name, lambda { |depth|
96
- raise Ancestry::AncestryException.new(I18n.t("ancestry.named_scope_depth_cache",
97
- :scope_name => scope_name
98
- )) unless options[:cache_depth]
99
- where("#{depth_cache_column} #{operator} ?", depth)
100
- }
124
+ if options[:touch]
125
+ after_touch :touch_ancestors_callback
126
+ after_destroy :touch_ancestors_callback
127
+ after_save :touch_ancestors_callback, if: :saved_changes?
101
128
  end
102
-
103
- after_touch :touch_ancestors_callback
104
- after_destroy :touch_ancestors_callback
105
- after_save :touch_ancestors_callback, if: :saved_changes?
106
129
  end
107
130
 
108
131
  def acts_as_tree(*args)
109
132
  return super if defined?(super)
133
+
110
134
  has_ancestry(*args)
111
135
  end
136
+
137
+ def self.ancestry_format_module(ancestry_format)
138
+ ancestry_format ||= Ancestry.default_ancestry_format
139
+ if ancestry_format == :materialized_path2
140
+ Ancestry::MaterializedPath2
141
+ else
142
+ Ancestry::MaterializedPath
143
+ end
144
+ end
112
145
  end
113
146
  end
114
147
 
@@ -1,16 +1,45 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Ancestry
2
4
  module InstanceMethods
3
5
  # Validate that the ancestors don't include itself
4
6
  def ancestry_exclude_self
5
- errors.add(:base, I18n.t("ancestry.exclude_self", class_name: self.class.name.humanize)) if ancestor_ids.include? self.id
7
+ errors.add(:base, I18n.t("ancestry.exclude_self", class_name: self.class.model_name.human)) if ancestor_ids.include?(id)
8
+ end
9
+
10
+ # Validate that descendants' depths don't exceed max depth when moving them
11
+ def ancestry_depth_of_descendants
12
+ return if new_record? || (respond_to?(:previously_new_record?) && previously_new_record?)
13
+ return unless self.class.respond_to?(:depth_cache_column) && self.class.depth_cache_column
14
+
15
+ column = self.class.depth_cache_column
16
+ validator = self.class.validators_on(column).find do |v|
17
+ v.is_a?(ActiveModel::Validations::NumericalityValidator) &&
18
+ (v.options[:less_than_or_equal_to] || v.options[:less_than])
19
+ end
20
+ return unless validator
21
+
22
+ max_depth = validator.options[:less_than_or_equal_to] || (validator.options[:less_than] - 1)
23
+
24
+ old_value = attribute_in_database(self.class.ancestry_column)
25
+ new_value = read_attribute(self.class.ancestry_column)
26
+ depth_change = self.class.ancestry_depth_change(old_value, new_value)
27
+
28
+ if depth_change > 0
29
+ max_descendant_depth = unscoped_descendants.maximum(column) || attribute_in_database(column) || 0
30
+ if max_descendant_depth + depth_change > max_depth
31
+ errors.add(column, :less_than_or_equal_to, count: max_depth)
32
+ end
33
+ end
6
34
  end
7
35
 
8
36
  # Update descendants with new ancestry (after update)
9
37
  def update_descendants_with_new_ancestry
10
- # If enabled and node is existing and ancestry was updated and the new ancestry is sane ...
11
- if !ancestry_callbacks_disabled? && !new_record? && ancestry_changed? && sane_ancestor_ids?
38
+ # If enabled and the new ancestry is sane ...
39
+ # The only way the ancestry could be bad is via `update_attribute` with a bad value
40
+ if !ancestry_callbacks_disabled? && sane_ancestor_ids?
12
41
  # ... for each descendant ...
13
- unscoped_descendants_before_save.each do |descendant|
42
+ unscoped_descendants_before_last_save.each do |descendant|
14
43
  # ... replace old ancestry with new ancestry
15
44
  descendant.without_ancestry_callbacks do
16
45
  new_ancestor_ids = path_ids + (descendant.ancestor_ids - path_ids_before_last_save)
@@ -20,37 +49,49 @@ module Ancestry
20
49
  end
21
50
  end
22
51
 
23
- # Apply orphan strategy (before destroy - no changes)
24
- def apply_orphan_strategy
25
- if !ancestry_callbacks_disabled? && !new_record?
26
- case self.ancestry_base_class.orphan_strategy
27
- when :rootify # make all children root if orphan strategy is rootify
28
- unscoped_descendants.each do |descendant|
29
- descendant.without_ancestry_callbacks do
30
- descendant.update_attribute :ancestor_ids, descendant.ancestor_ids - path_ids
31
- end
32
- end
33
- when :destroy # destroy all descendants if orphan strategy is destroy
34
- unscoped_descendants.each do |descendant|
35
- descendant.without_ancestry_callbacks do
36
- descendant.destroy
37
- end
38
- end
39
- when :adopt # make child elements of this node, child of its parent
40
- descendants.each do |descendant|
41
- descendant.without_ancestry_callbacks do
42
- descendant.update_attribute :ancestor_ids, (descendant.ancestor_ids.delete_if { |x| x == self.id })
43
- end
44
- end
45
- when :restrict # throw an exception if it has children
46
- raise Ancestry::AncestryException.new(I18n.t("ancestry.cannot_delete_descendants")) unless is_childless?
52
+ # make all children root if orphan strategy is rootify
53
+ def apply_orphan_strategy_rootify
54
+ return if ancestry_callbacks_disabled? || new_record?
55
+
56
+ unscoped_descendants.each do |descendant|
57
+ descendant.without_ancestry_callbacks do
58
+ descendant.update_attribute :ancestor_ids, descendant.ancestor_ids - path_ids
47
59
  end
48
60
  end
49
61
  end
50
62
 
63
+ # destroy all descendants if orphan strategy is destroy
64
+ def apply_orphan_strategy_destroy
65
+ return if ancestry_callbacks_disabled? || new_record?
66
+
67
+ unscoped_descendants.ordered_by_ancestry.reverse_order.each do |descendant|
68
+ descendant.without_ancestry_callbacks do
69
+ descendant.destroy
70
+ end
71
+ end
72
+ end
73
+
74
+ # make child elements of this node, child of its parent
75
+ def apply_orphan_strategy_adopt
76
+ return if ancestry_callbacks_disabled? || new_record?
77
+
78
+ descendants.each do |descendant|
79
+ descendant.without_ancestry_callbacks do
80
+ descendant.update_attribute :ancestor_ids, (descendant.ancestor_ids.delete_if { |x| x == id })
81
+ end
82
+ end
83
+ end
84
+
85
+ # throw an exception if it has children
86
+ def apply_orphan_strategy_restrict
87
+ return if ancestry_callbacks_disabled? || new_record?
88
+
89
+ raise(Ancestry::AncestryException, I18n.t("ancestry.cannot_delete_descendants")) unless is_childless?
90
+ end
91
+
51
92
  # Touch each of this record's ancestors (after save)
52
93
  def touch_ancestors_callback
53
- if !ancestry_callbacks_disabled? && self.ancestry_base_class.touch_ancestors
94
+ if !ancestry_callbacks_disabled?
54
95
  # Touch each of the old *and* new ancestors
55
96
  unscoped_current_and_previous_ancestors.each do |ancestor|
56
97
  ancestor.without_ancestry_callbacks do
@@ -62,7 +103,7 @@ module Ancestry
62
103
 
63
104
  # Counter Cache
64
105
  def increase_parent_counter_cache
65
- self.ancestry_base_class.increment_counter counter_cache_column, parent_id
106
+ self.class.ancestry_base_class.increment_counter counter_cache_column, parent_id
66
107
  end
67
108
 
68
109
  def decrease_parent_counter_cache
@@ -74,16 +115,14 @@ module Ancestry
74
115
  return if defined?(@_trigger_destroy_callback) && !@_trigger_destroy_callback
75
116
  return if ancestry_callbacks_disabled?
76
117
 
77
- self.ancestry_base_class.decrement_counter counter_cache_column, parent_id
118
+ self.class.ancestry_base_class.decrement_counter counter_cache_column, parent_id
78
119
  end
79
120
 
80
121
  def update_parent_counter_cache
81
- changed = saved_change_to_attribute?(self.ancestry_base_class.ancestry_column)
122
+ return unless saved_change_to_attribute?(self.class.ancestry_column)
82
123
 
83
- return unless changed
84
-
85
- if parent_id_was = parent_id_before_last_save
86
- self.ancestry_base_class.decrement_counter counter_cache_column, parent_id_was
124
+ if (parent_id_was = parent_id_before_last_save)
125
+ self.class.ancestry_base_class.decrement_counter counter_cache_column, parent_id_was
87
126
  end
88
127
 
89
128
  parent_id && increase_parent_counter_cache
@@ -94,20 +133,20 @@ module Ancestry
94
133
  def has_parent?
95
134
  ancestor_ids.present?
96
135
  end
97
- alias :ancestors? :has_parent?
136
+ alias ancestors? has_parent?
98
137
 
99
138
  def ancestry_changed?
100
- column = self.ancestry_base_class.ancestry_column.to_s
101
- # These methods return nil if there are no changes.
102
- # This was fixed in a refactoring in rails 6.0: https://github.com/rails/rails/pull/35933
103
- !!(will_save_change_to_attribute?(column) || saved_change_to_attribute?(column))
139
+ column = self.class.ancestry_column.to_s
140
+ # These methods return nil if there are no changes.
141
+ # This was fixed in a refactoring in rails 6.0: https://github.com/rails/rails/pull/35933
142
+ !!(will_save_change_to_attribute?(column) || saved_change_to_attribute?(column))
104
143
  end
105
144
 
106
145
  def sane_ancestor_ids?
107
146
  current_context, self.validation_context = validation_context, nil
108
147
  errors.clear
109
148
 
110
- attribute = ancestry_base_class.ancestry_column
149
+ attribute = self.class.ancestry_column
111
150
  ancestry_value = send(attribute)
112
151
  return true unless ancestry_value
113
152
 
@@ -120,9 +159,10 @@ module Ancestry
120
159
  self.validation_context = current_context
121
160
  end
122
161
 
123
- def ancestors depth_options = {}
124
- return self.ancestry_base_class.none unless has_parent?
125
- self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.ancestors_of(self)
162
+ def ancestors(depth_options = {})
163
+ return self.class.ancestry_base_class.none unless has_parent?
164
+
165
+ self.class.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.ancestors_of(self)
126
166
  end
127
167
 
128
168
  def path_ids
@@ -137,8 +177,8 @@ module Ancestry
137
177
  ancestor_ids_in_database + [id]
138
178
  end
139
179
 
140
- def path depth_options = {}
141
- self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.inpath_of(self)
180
+ def path(depth_options = {})
181
+ self.class.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.inpath_of(self)
142
182
  end
143
183
 
144
184
  def depth
@@ -146,29 +186,29 @@ module Ancestry
146
186
  end
147
187
 
148
188
  def cache_depth
149
- write_attribute self.ancestry_base_class.depth_cache_column, depth
189
+ write_attribute self.class.ancestry_base_class.depth_cache_column, depth
150
190
  end
151
191
 
152
192
  def ancestor_of?(node)
153
- node.ancestor_ids.include?(self.id)
193
+ node.ancestor_ids.include?(id)
154
194
  end
155
195
 
156
196
  # Parent
157
197
 
158
198
  # currently parent= does not work in after save callbacks
159
199
  # assuming that parent hasn't changed
160
- def parent= parent
200
+ def parent=(parent)
161
201
  self.ancestor_ids = parent ? parent.path_ids : []
162
202
  end
163
203
 
164
- def parent_id= new_parent_id
204
+ def parent_id=(new_parent_id)
165
205
  self.parent = new_parent_id.present? ? unscoped_find(new_parent_id) : nil
166
206
  end
167
207
 
168
208
  def parent_id
169
209
  ancestor_ids.last if has_parent?
170
210
  end
171
- alias :parent_id? :ancestors?
211
+ alias parent_id? ancestors?
172
212
 
173
213
  def parent
174
214
  if has_parent?
@@ -179,7 +219,7 @@ module Ancestry
179
219
  end
180
220
 
181
221
  def parent_of?(node)
182
- self.id == node.parent_id
222
+ id == node.parent_id
183
223
  end
184
224
 
185
225
  # Root
@@ -199,24 +239,24 @@ module Ancestry
199
239
  def is_root?
200
240
  !has_parent?
201
241
  end
202
- alias :root? :is_root?
242
+ alias root? is_root?
203
243
 
204
244
  def root_of?(node)
205
- self.id == node.root_id
245
+ id == node.root_id
206
246
  end
207
247
 
208
248
  # Children
209
249
 
210
250
  def children
211
- self.ancestry_base_class.children_of(self)
251
+ self.class.ancestry_base_class.children_of(self)
212
252
  end
213
253
 
214
254
  def child_ids
215
- children.pluck(self.ancestry_base_class.primary_key)
255
+ children.pluck(self.class.primary_key)
216
256
  end
217
257
 
218
258
  def has_children?
219
- self.children.exists?
259
+ children.exists?
220
260
  end
221
261
  alias_method :children?, :has_children?
222
262
 
@@ -226,22 +266,21 @@ module Ancestry
226
266
  alias_method :childless?, :is_childless?
227
267
 
228
268
  def child_of?(node)
229
- self.parent_id == node.id
269
+ parent_id == node.id
230
270
  end
231
271
 
232
272
  # Siblings
233
273
 
234
274
  def siblings
235
- self.ancestry_base_class.siblings_of(self)
275
+ self.class.ancestry_base_class.siblings_of(self).where.not(self.class.primary_key => id)
236
276
  end
237
277
 
238
- # NOTE: includes self
239
278
  def sibling_ids
240
- siblings.pluck(self.ancestry_base_class.primary_key)
279
+ siblings.pluck(self.class.primary_key)
241
280
  end
242
281
 
243
282
  def has_siblings?
244
- self.siblings.count > 1
283
+ siblings.exists?
245
284
  end
246
285
  alias_method :siblings?, :has_siblings?
247
286
 
@@ -251,17 +290,17 @@ module Ancestry
251
290
  alias_method :only_child?, :is_only_child?
252
291
 
253
292
  def sibling_of?(node)
254
- self.ancestor_ids == node.ancestor_ids
293
+ ancestor_ids == node.ancestor_ids
255
294
  end
256
295
 
257
296
  # Descendants
258
297
 
259
- def descendants depth_options = {}
260
- self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).descendants_of(self)
298
+ def descendants(depth_options = {})
299
+ self.class.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).descendants_of(self)
261
300
  end
262
301
 
263
- def descendant_ids depth_options = {}
264
- descendants(depth_options).pluck(self.ancestry_base_class.primary_key)
302
+ def descendant_ids(depth_options = {})
303
+ descendants(depth_options).pluck(self.class.primary_key)
265
304
  end
266
305
 
267
306
  def descendant_of?(node)
@@ -270,12 +309,12 @@ module Ancestry
270
309
 
271
310
  # Indirects
272
311
 
273
- def indirects depth_options = {}
274
- self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).indirects_of(self)
312
+ def indirects(depth_options = {})
313
+ self.class.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).indirects_of(self)
275
314
  end
276
315
 
277
- def indirect_ids depth_options = {}
278
- indirects(depth_options).pluck(self.ancestry_base_class.primary_key)
316
+ def indirect_ids(depth_options = {})
317
+ indirects(depth_options).pluck(self.class.primary_key)
279
318
  end
280
319
 
281
320
  def indirect_of?(node)
@@ -284,12 +323,16 @@ module Ancestry
284
323
 
285
324
  # Subtree
286
325
 
287
- def subtree depth_options = {}
288
- self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).subtree_of(self)
326
+ def subtree(depth_options = {})
327
+ self.class.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).subtree_of(self)
289
328
  end
290
329
 
291
- def subtree_ids depth_options = {}
292
- subtree(depth_options).pluck(self.ancestry_base_class.primary_key)
330
+ def subtree_ids(depth_options = {})
331
+ subtree(depth_options).pluck(self.class.primary_key)
332
+ end
333
+
334
+ def in_subtree_of?(node)
335
+ id == node.id || descendant_of?(node)
293
336
  end
294
337
 
295
338
  # Callback disabling
@@ -305,36 +348,35 @@ module Ancestry
305
348
  defined?(@disable_ancestry_callbacks) && @disable_ancestry_callbacks
306
349
  end
307
350
 
308
- private
351
+ private
352
+
309
353
  def unscoped_descendants
310
354
  unscoped_where do |scope|
311
- scope.where self.ancestry_base_class.descendant_conditions(self)
355
+ scope.where(self.class.ancestry_base_class.descendant_conditions(self))
312
356
  end
313
357
  end
314
358
 
315
- def unscoped_descendants_before_save
359
+ def unscoped_descendants_before_last_save
316
360
  unscoped_where do |scope|
317
- scope.where self.ancestry_base_class.descendant_before_save_conditions(self)
361
+ scope.where(self.class.ancestry_base_class.descendant_before_last_save_conditions(self))
318
362
  end
319
363
  end
320
364
 
321
365
  # works with after save context (hence before_last_save)
322
366
  def unscoped_current_and_previous_ancestors
323
367
  unscoped_where do |scope|
324
- scope.where scope.primary_key => (ancestor_ids + ancestor_ids_before_last_save).uniq
368
+ scope.where(scope.primary_key => (ancestor_ids + ancestor_ids_before_last_save).uniq)
325
369
  end
326
370
  end
327
371
 
328
- def unscoped_find id
372
+ def unscoped_find(id)
329
373
  unscoped_where do |scope|
330
- scope.find id
374
+ scope.find(id)
331
375
  end
332
376
  end
333
377
 
334
- def unscoped_where
335
- self.ancestry_base_class.unscoped_where do |scope|
336
- yield scope
337
- end
378
+ def unscoped_where(&block)
379
+ self.class.ancestry_base_class.unscoped_where(&block)
338
380
  end
339
381
  end
340
382
  end
@@ -10,7 +10,6 @@ en:
10
10
  option_must_be_hash: "Options for has_ancestry must be in a hash."
11
11
  unknown_option: "Unknown option for has_ancestry: %{key} => %{value}."
12
12
  unknown_format: "Unknown ancestry format: %{value}."
13
- named_scope_depth_cache: "Named scope '%{scope_name}' is only available when depth caching is enabled."
14
13
 
15
14
  exclude_self: "%{class_name} cannot be a descendant of itself."
16
15
  cannot_delete_descendants: "Cannot delete record because it has descendants."