summaryrefslogtreecommitdiff
path: root/vendor/plugins/awesome_nested_set/lib
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-06-24 04:13:16 +0200
committererdgeist <erdgeist@erdgeist.org>2026-06-24 04:13:16 +0200
commite0a7e0fec760ba12c8067a37e10c96f1f05876e2 (patch)
treed0cf745592a46aee4d4913911fd34c7c24515220 /vendor/plugins/awesome_nested_set/lib
parent6424e10be5a89f175a74c71c55660412a169b8b8 (diff)
Stage 1 complete: Rails 2.3.5 to Rails 3.2.22.5 upgrade
- Converted plugins to gems (Gemfile) - Updated config structure (application.rb, boot.rb, environment.rb) - Converted routes to Rails 3 DSL - Converted named_scope to scope throughout models - Converted find(:all, :conditions) to where() chains - Fixed has_many :order to use ordering scope - Updated session store and secret token configuration - Fixed exception_notification middleware configuration - Patched Ruby 2.4 / Rails 3.2 incompatibilities: - Integer/Float duration arithmetic (ActiveSupport) - Arel visit_Integer for PostgreSQL adapter - create_database String/Integer coercion - ActionController consider_all_requests_local - Migrated taggings schema for acts-as-taggable-on - Replaced dynamic_form gem with custom form_error_messages helper - Fixed Rails 3 block helper syntax (form_for, form_tag, fields_for) - Fixed admin layout yield - Updated test suite for Rails 3 APIs
Diffstat (limited to 'vendor/plugins/awesome_nested_set/lib')
-rw-r--r--vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb577
-rw-r--r--vendor/plugins/awesome_nested_set/lib/awesome_nested_set/helper.rb40
2 files changed, 0 insertions, 617 deletions
diff --git a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb
deleted file mode 100644
index 015aa0f..0000000
--- a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb
+++ /dev/null
@@ -1,577 +0,0 @@
1module CollectiveIdea #:nodoc:
2 module Acts #:nodoc:
3 module NestedSet #:nodoc:
4 def self.included(base)
5 base.extend(SingletonMethods)
6 end
7
8 # This acts provides Nested Set functionality. Nested Set is a smart way to implement
9 # an _ordered_ tree, with the added feature that you can select the children and all of their
10 # descendants with a single query. The drawback is that insertion or move need some complex
11 # sql queries. But everything is done here by this module!
12 #
13 # Nested sets are appropriate each time you want either an orderd tree (menus,
14 # commercial categories) or an efficient way of querying big trees (threaded posts).
15 #
16 # == API
17 #
18 # Methods names are aligned with acts_as_tree as much as possible to make replacment from one
19 # by another easier.
20 #
21 # item.children.create(:name => "child1")
22 #
23 module SingletonMethods
24 # Configuration options are:
25 #
26 # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
27 # * +:left_column+ - column name for left boundry data, default "lft"
28 # * +:right_column+ - column name for right boundry data, default "rgt"
29 # * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
30 # (if it hasn't been already) and use that as the foreign key restriction. You
31 # can also pass an array to scope by multiple attributes.
32 # Example: <tt>acts_as_nested_set :scope => [:notable_id, :notable_type]</tt>
33 # * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the
34 # child objects are destroyed alongside this object by calling their destroy
35 # method. If set to :delete_all (default), all the child objects are deleted
36 # without calling their destroy method.
37 #
38 # See CollectiveIdea::Acts::NestedSet::ClassMethods for a list of class methods and
39 # CollectiveIdea::Acts::NestedSet::InstanceMethods for a list of instance methods added
40 # to acts_as_nested_set models
41 def acts_as_nested_set(options = {})
42 options = {
43 :parent_column => 'parent_id',
44 :left_column => 'lft',
45 :right_column => 'rgt',
46 :dependent => :delete_all, # or :destroy
47 }.merge(options)
48
49 if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
50 options[:scope] = "#{options[:scope]}_id".intern
51 end
52
53 write_inheritable_attribute :acts_as_nested_set_options, options
54 class_inheritable_reader :acts_as_nested_set_options
55
56 unless self.is_a?(ClassMethods)
57 include Comparable
58 include Columns
59 include InstanceMethods
60 extend Columns
61 extend ClassMethods
62
63 belongs_to :parent, :class_name => self.base_class.class_name,
64 :foreign_key => parent_column_name
65 has_many :children, :class_name => self.base_class.class_name,
66 :foreign_key => parent_column_name, :order => quoted_left_column_name
67
68 attr_accessor :skip_before_destroy
69
70 # no bulk assignment
71 attr_protected left_column_name.intern,
72 right_column_name.intern
73
74 before_create :set_default_left_and_right
75 before_save :store_new_parent
76 after_save :move_to_new_parent
77 before_destroy :destroy_descendants
78
79 # no assignment to structure fields
80 [left_column_name, right_column_name].each do |column|
81 module_eval <<-"end_eval", __FILE__, __LINE__
82 def #{column}=(x)
83 raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
84 end
85 end_eval
86 end
87
88 named_scope :roots, :conditions => {parent_column_name => nil}, :order => quoted_left_column_name
89 named_scope :leaves, :conditions => "#{quoted_right_column_name} - #{quoted_left_column_name} = 1", :order => quoted_left_column_name
90
91 define_callbacks("before_move", "after_move") if self.respond_to?(:define_callbacks)
92 end
93
94 end
95
96 end
97
98 module ClassMethods
99
100 # Returns the first root
101 def root
102 roots.find(:first)
103 end
104
105 def valid?
106 left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid?
107 end
108
109 def left_and_rights_valid?
110 count(
111 :joins => "LEFT OUTER JOIN #{quoted_table_name} AS parent ON " +
112 "#{quoted_table_name}.#{quoted_parent_column_name} = parent.#{primary_key}",
113 :conditions =>
114 "#{quoted_table_name}.#{quoted_left_column_name} IS NULL OR " +
115 "#{quoted_table_name}.#{quoted_right_column_name} IS NULL OR " +
116 "#{quoted_table_name}.#{quoted_left_column_name} >= " +
117 "#{quoted_table_name}.#{quoted_right_column_name} OR " +
118 "(#{quoted_table_name}.#{quoted_parent_column_name} IS NOT NULL AND " +
119 "(#{quoted_table_name}.#{quoted_left_column_name} <= parent.#{quoted_left_column_name} OR " +
120 "#{quoted_table_name}.#{quoted_right_column_name} >= parent.#{quoted_right_column_name}))"
121 ) == 0
122 end
123
124 def no_duplicates_for_columns?
125 scope_string = Array(acts_as_nested_set_options[:scope]).map do |c|
126 connection.quote_column_name(c)
127 end.push(nil).join(", ")
128 [quoted_left_column_name, quoted_right_column_name].all? do |column|
129 # No duplicates
130 find(:first,
131 :select => "#{scope_string}#{column}, COUNT(#{column})",
132 :group => "#{scope_string}#{column}
133 HAVING COUNT(#{column}) > 1").nil?
134 end
135 end
136
137 # Wrapper for each_root_valid? that can deal with scope.
138 def all_roots_valid?
139 if acts_as_nested_set_options[:scope]
140 roots(:group => scope_column_names).group_by{|record| scope_column_names.collect{|col| record.send(col.to_sym)}}.all? do |scope, grouped_roots|
141 each_root_valid?(grouped_roots)
142 end
143 else
144 each_root_valid?(roots)
145 end
146 end
147
148 def each_root_valid?(roots_to_validate)
149 left = right = 0
150 roots_to_validate.all? do |root|
151 returning(root.left > left && root.right > right) do
152 left = root.left
153 right = root.right
154 end
155 end
156 end
157
158 # Rebuilds the left & rights if unset or invalid. Also very useful for converting from acts_as_tree.
159 def rebuild!
160 # Don't rebuild a valid tree.
161 return true if valid?
162
163 scope = lambda{|node|}
164 if acts_as_nested_set_options[:scope]
165 scope = lambda{|node|
166 scope_column_names.inject(""){|str, column_name|
167 str << "AND #{connection.quote_column_name(column_name)} = #{connection.quote(node.send(column_name.to_sym))} "
168 }
169 }
170 end
171 indices = {}
172
173 set_left_and_rights = lambda do |node|
174 # set left
175 node[left_column_name] = indices[scope.call(node)] += 1
176 # find
177 find(:all, :conditions => ["#{quoted_parent_column_name} = ? #{scope.call(node)}", node], :order => "#{quoted_left_column_name}, #{quoted_right_column_name}, id").each{|n| set_left_and_rights.call(n) }
178 # set right
179 node[right_column_name] = indices[scope.call(node)] += 1
180 node.save!
181 end
182
183 # Find root node(s)
184 root_nodes = find(:all, :conditions => "#{quoted_parent_column_name} IS NULL", :order => "#{quoted_left_column_name}, #{quoted_right_column_name}, id").each do |root_node|
185 # setup index for this scope
186 indices[scope.call(root_node)] ||= 0
187 set_left_and_rights.call(root_node)
188 end
189 end
190
191 # Iterates over tree elements and determines the current level in the tree.
192 # Only accepts default ordering, odering by an other column than lft
193 # does not work. This method is much more efficent than calling level
194 # because it doesn't require any additional database queries.
195 #
196 # Example:
197 # Category.each_with_level(Category.root.self_and_descendants) do |o, level|
198 #
199 def each_with_level(objects)
200 path = [nil]
201 objects.each do |o|
202 if o.parent_id != path.last
203 # we are on a new level, did we decent or ascent?
204 if path.include?(o.parent_id)
205 # remove wrong wrong tailing paths elements
206 path.pop while path.last != o.parent_id
207 else
208 path << o.parent_id
209 end
210 end
211 yield(o, path.length - 1)
212 end
213 end
214 end
215
216 # Mixed into both classes and instances to provide easy access to the column names
217 module Columns
218 def left_column_name
219 acts_as_nested_set_options[:left_column]
220 end
221
222 def right_column_name
223 acts_as_nested_set_options[:right_column]
224 end
225
226 def parent_column_name
227 acts_as_nested_set_options[:parent_column]
228 end
229
230 def scope_column_names
231 Array(acts_as_nested_set_options[:scope])
232 end
233
234 def quoted_left_column_name
235 connection.quote_column_name(left_column_name)
236 end
237
238 def quoted_right_column_name
239 connection.quote_column_name(right_column_name)
240 end
241
242 def quoted_parent_column_name
243 connection.quote_column_name(parent_column_name)
244 end
245
246 def quoted_scope_column_names
247 scope_column_names.collect {|column_name| connection.quote_column_name(column_name) }
248 end
249 end
250
251 # Any instance method that returns a collection makes use of Rails 2.1's named_scope (which is bundled for Rails 2.0), so it can be treated as a finder.
252 #
253 # category.self_and_descendants.count
254 # category.ancestors.find(:all, :conditions => "name like '%foo%'")
255 module InstanceMethods
256 # Value of the parent column
257 def parent_id
258 self[parent_column_name]
259 end
260
261 # Value of the left column
262 def left
263 self[left_column_name]
264 end
265
266 # Value of the right column
267 def right
268 self[right_column_name]
269 end
270
271 # Returns true if this is a root node.
272 def root?
273 parent_id.nil?
274 end
275
276 def leaf?
277 !new_record? && right - left == 1
278 end
279
280 # Returns true is this is a child node
281 def child?
282 !parent_id.nil?
283 end
284
285 # order by left column
286 def <=>(x)
287 left <=> x.left
288 end
289
290 # Redefine to act like active record
291 def ==(comparison_object)
292 comparison_object.equal?(self) ||
293 (comparison_object.instance_of?(self.class) &&
294 comparison_object.id == id &&
295 !comparison_object.new_record?)
296 end
297
298 # Returns root
299 def root
300 self_and_ancestors.find(:first)
301 end
302
303 # Returns the array of all parents and self
304 def self_and_ancestors
305 nested_set_scope.scoped :conditions => [
306 "#{self.class.quoted_table_name}.#{quoted_left_column_name} <= ? AND #{self.class.quoted_table_name}.#{quoted_right_column_name} >= ?", left, right
307 ]
308 end
309
310 # Returns an array of all parents
311 def ancestors
312 without_self self_and_ancestors
313 end
314
315 # Returns the array of all children of the parent, including self
316 def self_and_siblings
317 nested_set_scope.scoped :conditions => {parent_column_name => parent_id}
318 end
319
320 # Returns the array of all children of the parent, except self
321 def siblings
322 without_self self_and_siblings
323 end
324
325 # Returns a set of all of its nested children which do not have children
326 def leaves
327 descendants.scoped :conditions => "#{self.class.quoted_table_name}.#{quoted_right_column_name} - #{self.class.quoted_table_name}.#{quoted_left_column_name} = 1"
328 end
329
330 # Returns the level of this object in the tree
331 # root level is 0
332 def level
333 parent_id.nil? ? 0 : ancestors.count
334 end
335
336 # Returns a set of itself and all of its nested children
337 def self_and_descendants
338 nested_set_scope.scoped :conditions => [
339 "#{self.class.quoted_table_name}.#{quoted_left_column_name} >= ? AND #{self.class.quoted_table_name}.#{quoted_right_column_name} <= ?", left, right
340 ]
341 end
342
343 # Returns a set of all of its children and nested children
344 def descendants
345 without_self self_and_descendants
346 end
347
348 def is_descendant_of?(other)
349 other.left < self.left && self.left < other.right && same_scope?(other)
350 end
351
352 def is_or_is_descendant_of?(other)
353 other.left <= self.left && self.left < other.right && same_scope?(other)
354 end
355
356 def is_ancestor_of?(other)
357 self.left < other.left && other.left < self.right && same_scope?(other)
358 end
359
360 def is_or_is_ancestor_of?(other)
361 self.left <= other.left && other.left < self.right && same_scope?(other)
362 end
363
364 # Check if other model is in the same scope
365 def same_scope?(other)
366 Array(acts_as_nested_set_options[:scope]).all? do |attr|
367 self.send(attr) == other.send(attr)
368 end
369 end
370
371 # Find the first sibling to the left
372 def left_sibling
373 siblings.find(:first, :conditions => ["#{self.class.quoted_table_name}.#{quoted_left_column_name} < ?", left],
374 :order => "#{self.class.quoted_table_name}.#{quoted_left_column_name} DESC")
375 end
376
377 # Find the first sibling to the right
378 def right_sibling
379 siblings.find(:first, :conditions => ["#{self.class.quoted_table_name}.#{quoted_left_column_name} > ?", left])
380 end
381
382 # Shorthand method for finding the left sibling and moving to the left of it.
383 def move_left
384 move_to_left_of left_sibling
385 end
386
387 # Shorthand method for finding the right sibling and moving to the right of it.
388 def move_right
389 move_to_right_of right_sibling
390 end
391
392 # Move the node to the left of another node (you can pass id only)
393 def move_to_left_of(node)
394 move_to node, :left
395 end
396
397 # Move the node to the left of another node (you can pass id only)
398 def move_to_right_of(node)
399 move_to node, :right
400 end
401
402 # Move the node to the child of another node (you can pass id only)
403 def move_to_child_of(node)
404 move_to node, :child
405 end
406
407 # Move the node to root nodes
408 def move_to_root
409 move_to nil, :root
410 end
411
412 def move_possible?(target)
413 self != target && # Can't target self
414 same_scope?(target) && # can't be in different scopes
415 # !(left..right).include?(target.left..target.right) # this needs tested more
416 # detect impossible move
417 !((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right))
418 end
419
420 def to_text
421 self_and_descendants.map do |node|
422 "#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
423 end.join("\n")
424 end
425
426 protected
427
428 def without_self(scope)
429 scope.scoped :conditions => ["#{self.class.quoted_table_name}.#{self.class.primary_key} != ?", self]
430 end
431
432 # All nested set queries should use this nested_set_scope, which performs finds on
433 # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
434 # declaration.
435 def nested_set_scope
436 options = {:order => quoted_left_column_name}
437 scopes = Array(acts_as_nested_set_options[:scope])
438 options[:conditions] = scopes.inject({}) do |conditions,attr|
439 conditions.merge attr => self[attr]
440 end unless scopes.empty?
441 self.class.base_class.scoped options
442 end
443
444 def store_new_parent
445 @move_to_new_parent_id = send("#{parent_column_name}_changed?") ? parent_id : false
446 true # force callback to return true
447 end
448
449 def move_to_new_parent
450 if @move_to_new_parent_id.nil?
451 move_to_root
452 elsif @move_to_new_parent_id
453 move_to_child_of(@move_to_new_parent_id)
454 end
455 end
456
457 # on creation, set automatically lft and rgt to the end of the tree
458 def set_default_left_and_right
459 maxright = nested_set_scope.maximum(right_column_name) || 0
460 # adds the new node to the right of all existing nodes
461 self[left_column_name] = maxright + 1
462 self[right_column_name] = maxright + 2
463 end
464
465 # Prunes a branch off of the tree, shifting all of the elements on the right
466 # back to the left so the counts still work.
467 def destroy_descendants
468 return if right.nil? || left.nil? || skip_before_destroy
469
470 self.class.base_class.transaction do
471 if acts_as_nested_set_options[:dependent] == :destroy
472 descendants.each do |model|
473 model.skip_before_destroy = true
474 model.destroy
475 end
476 else
477 nested_set_scope.delete_all(
478 ["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?",
479 left, right]
480 )
481 end
482
483 # update lefts and rights for remaining nodes
484 diff = right - left + 1
485 nested_set_scope.update_all(
486 ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff],
487 ["#{quoted_left_column_name} > ?", right]
488 )
489 nested_set_scope.update_all(
490 ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff],
491 ["#{quoted_right_column_name} > ?", right]
492 )
493
494 # Don't allow multiple calls to destroy to corrupt the set
495 self.skip_before_destroy = true
496 end
497 end
498
499 # reload left, right, and parent
500 def reload_nested_set
501 reload(:select => "#{quoted_left_column_name}, " +
502 "#{quoted_right_column_name}, #{quoted_parent_column_name}")
503 end
504
505 def move_to(target, position)
506 raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if self.new_record?
507 return if callback(:before_move) == false
508 transaction do
509 if target.is_a? self.class.base_class
510 target.reload_nested_set
511 elsif position != :root
512 # load object if node is not an object
513 target = nested_set_scope.find(target)
514 end
515 self.reload_nested_set
516
517 unless position == :root || move_possible?(target)
518 raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
519 end
520
521 bound = case position
522 when :child; target[right_column_name]
523 when :left; target[left_column_name]
524 when :right; target[right_column_name] + 1
525 when :root; 1
526 else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
527 end
528
529 if bound > self[right_column_name]
530 bound = bound - 1
531 other_bound = self[right_column_name] + 1
532 else
533 other_bound = self[left_column_name] - 1
534 end
535
536 # there would be no change
537 return if bound == self[right_column_name] || bound == self[left_column_name]
538
539 # we have defined the boundaries of two non-overlapping intervals,
540 # so sorting puts both the intervals and their boundaries in order
541 a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort
542
543 new_parent = case position
544 when :child; target.id
545 when :root; nil
546 else target[parent_column_name]
547 end
548
549 self.class.base_class.update_all([
550 "#{quoted_left_column_name} = CASE " +
551 "WHEN #{quoted_left_column_name} BETWEEN :a AND :b " +
552 "THEN #{quoted_left_column_name} + :d - :b " +
553 "WHEN #{quoted_left_column_name} BETWEEN :c AND :d " +
554 "THEN #{quoted_left_column_name} + :a - :c " +
555 "ELSE #{quoted_left_column_name} END, " +
556 "#{quoted_right_column_name} = CASE " +
557 "WHEN #{quoted_right_column_name} BETWEEN :a AND :b " +
558 "THEN #{quoted_right_column_name} + :d - :b " +
559 "WHEN #{quoted_right_column_name} BETWEEN :c AND :d " +
560 "THEN #{quoted_right_column_name} + :a - :c " +
561 "ELSE #{quoted_right_column_name} END, " +
562 "#{quoted_parent_column_name} = CASE " +
563 "WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " +
564 "ELSE #{quoted_parent_column_name} END",
565 {:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent}
566 ], nested_set_scope.proxy_options[:conditions])
567 end
568 target.reload_nested_set if target
569 self.reload_nested_set
570 callback(:after_move)
571 end
572
573 end
574
575 end
576 end
577end
diff --git a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/helper.rb b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/helper.rb
deleted file mode 100644
index 09c803f..0000000
--- a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/helper.rb
+++ /dev/null
@@ -1,40 +0,0 @@
1module CollectiveIdea #:nodoc:
2 module Acts #:nodoc:
3 module NestedSet #:nodoc:
4 # This module provides some helpers for the model classes using acts_as_nested_set.
5 # It is included by default in all views.
6 #
7 module Helper
8 # Returns options for select.
9 # You can exclude some items from the tree.
10 # You can pass a block receiving an item and returning the string displayed in the select.
11 #
12 # == Params
13 # * +class_or_item+ - Class name or top level times
14 # * +mover+ - The item that is being move, used to exlude impossible moves
15 # * +&block+ - a block that will be used to display: { |item| ... item.name }
16 #
17 # == Usage
18 #
19 # <%= f.select :parent_id, nested_set_options(Category, @category) {|i|
20 # "#{'–' * i.level} #{i.name}"
21 # }) %>
22 #
23 def nested_set_options(class_or_item, mover = nil)
24 class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
25 items = Array(class_or_item)
26 result = []
27 items.each do |root|
28 result += root.self_and_descendants.map do |i|
29 if mover.nil? || mover.new_record? || mover.move_possible?(i)
30 [yield(i), i.id]
31 end
32 end.compact
33 end
34 result
35 end
36
37 end
38 end
39 end
40end \ No newline at end of file