summaryrefslogtreecommitdiff
path: root/vendor/plugins/awesome_nested_set/lib
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/plugins/awesome_nested_set/lib')
-rw-r--r--vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb173
-rw-r--r--vendor/plugins/awesome_nested_set/lib/awesome_nested_set/compatability.rb29
-rw-r--r--vendor/plugins/awesome_nested_set/lib/awesome_nested_set/named_scope.rb140
3 files changed, 102 insertions, 240 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
index 3e10891..bafdf4c 100644
--- a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb
+++ b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb
@@ -15,24 +15,11 @@ module CollectiveIdea #:nodoc:
15 # 15 #
16 # == API 16 # == API
17 # 17 #
18 # Methods names are aligned with acts_as_tree as much as possible, to make replacment from one 18 # Methods names are aligned with acts_as_tree as much as possible to make replacment from one
19 # by another easier, except for the creation: 19 # by another easier.
20 # 20 #
21 # in acts_as_tree:
22 # item.children.create(:name => "child1") 21 # item.children.create(:name => "child1")
23 # 22 #
24 # in acts_as_nested_set:
25 # # adds a new item at the "end" of the tree, i.e. with child.left = max(tree.right)+1
26 # child = MyClass.new(:name => "child1")
27 # child.save
28 # # now move the item to its right place
29 # child.move_to_child_of my_item
30 #
31 # You can pass an id or an object to:
32 # * <tt>#move_to_child_of</tt>
33 # * <tt>#move_to_right_of</tt>
34 # * <tt>#move_to_left_of</tt>
35 #
36 module SingletonMethods 23 module SingletonMethods
37 # Configuration options are: 24 # Configuration options are:
38 # 25 #
@@ -66,35 +53,43 @@ module CollectiveIdea #:nodoc:
66 write_inheritable_attribute :acts_as_nested_set_options, options 53 write_inheritable_attribute :acts_as_nested_set_options, options
67 class_inheritable_reader :acts_as_nested_set_options 54 class_inheritable_reader :acts_as_nested_set_options
68 55
69 include Comparable 56 unless self.is_a?(ClassMethods)
70 include Columns 57 include Comparable
71 include InstanceMethods 58 include Columns
72 extend Columns 59 include InstanceMethods
73 extend ClassMethods 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
74 67
75 # no bulk assignment 68 attr_accessor :skip_before_destroy
76 attr_protected left_column_name.intern, 69
77 right_column_name.intern, 70 # no bulk assignment
78 parent_column_name.intern 71 attr_protected left_column_name.intern,
72 right_column_name.intern
79 73
80 before_create :set_default_left_and_right 74 before_create :set_default_left_and_right
81 before_destroy :prune_from_tree 75 before_save :store_new_parent
76 after_save :move_to_new_parent
77 before_destroy :destroy_descendants
82 78
83 # no assignment to structure fields 79 # no assignment to structure fields
84 [left_column_name, right_column_name, parent_column_name].each do |column| 80 [left_column_name, right_column_name].each do |column|
85 module_eval <<-"end_eval", __FILE__, __LINE__ 81 module_eval <<-"end_eval", __FILE__, __LINE__
86 def #{column}=(x) 82 def #{column}=(x)
87 raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead." 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."
88 end 84 end
89 end_eval 85 end_eval
90 end 86 end
91 87
92 named_scope :roots, :conditions => {parent_column_name => nil}, :order => quoted_left_column_name 88 named_scope :roots, :conditions => {parent_column_name => nil}, :order => quoted_left_column_name
93 named_scope :leaves, :conditions => "#{quoted_right_column_name} - #{quoted_left_column_name} = 1", :order => quoted_left_column_name 89 named_scope :leaves, :conditions => "#{quoted_right_column_name} - #{quoted_left_column_name} = 1", :order => quoted_left_column_name
94 if self.respond_to?(:define_callbacks)
95 define_callbacks("before_move", "after_move")
96 end
97 90
91 define_callbacks("before_move", "after_move") if self.respond_to?(:define_callbacks)
92 end
98 93
99 end 94 end
100 95
@@ -192,6 +187,30 @@ module CollectiveIdea #:nodoc:
192 set_left_and_rights.call(root_node) 187 set_left_and_rights.call(root_node)
193 end 188 end
194 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
195 end 214 end
196 215
197 # Mixed into both classes and instances to provide easy access to the column names 216 # Mixed into both classes and instances to provide easy access to the column names
@@ -255,7 +274,7 @@ module CollectiveIdea #:nodoc:
255 end 274 end
256 275
257 def leaf? 276 def leaf?
258 right - left == 1 277 !new_record? && right - left == 1
259 end 278 end
260 279
261 # Returns true is this is a child node 280 # Returns true is this is a child node
@@ -281,15 +300,10 @@ module CollectiveIdea #:nodoc:
281 self_and_ancestors.find(:first) 300 self_and_ancestors.find(:first)
282 end 301 end
283 302
284 # Returns the immediate parent
285 def parent
286 nested_set_scope.find_by_id(parent_id) if parent_id
287 end
288
289 # Returns the array of all parents and self 303 # Returns the array of all parents and self
290 def self_and_ancestors 304 def self_and_ancestors
291 nested_set_scope.scoped :conditions => [ 305 nested_set_scope.scoped :conditions => [
292 "#{self.class.table_name}.#{quoted_left_column_name} <= ? AND #{self.class.table_name}.#{quoted_right_column_name} >= ?", left, right 306 "#{self.class.quoted_table_name}.#{quoted_left_column_name} <= ? AND #{self.class.quoted_table_name}.#{quoted_right_column_name} >= ?", left, right
293 ] 307 ]
294 end 308 end
295 309
@@ -310,7 +324,7 @@ module CollectiveIdea #:nodoc:
310 324
311 # Returns a set of all of its nested children which do not have children 325 # Returns a set of all of its nested children which do not have children
312 def leaves 326 def leaves
313 descendants.scoped :conditions => "#{self.class.table_name}.#{quoted_right_column_name} - #{self.class.table_name}.#{quoted_left_column_name} = 1" 327 descendants.scoped :conditions => "#{self.class.quoted_table_name}.#{quoted_right_column_name} - #{self.class.quoted_table_name}.#{quoted_left_column_name} = 1"
314 end 328 end
315 329
316 # Returns the level of this object in the tree 330 # Returns the level of this object in the tree
@@ -322,7 +336,7 @@ module CollectiveIdea #:nodoc:
322 # Returns a set of itself and all of its nested children 336 # Returns a set of itself and all of its nested children
323 def self_and_descendants 337 def self_and_descendants
324 nested_set_scope.scoped :conditions => [ 338 nested_set_scope.scoped :conditions => [
325 "#{self.class.table_name}.#{quoted_left_column_name} >= ? AND #{self.class.table_name}.#{quoted_right_column_name} <= ?", left, right 339 "#{self.class.quoted_table_name}.#{quoted_left_column_name} >= ? AND #{self.class.quoted_table_name}.#{quoted_right_column_name} <= ?", left, right
326 ] 340 ]
327 end 341 end
328 342
@@ -331,11 +345,6 @@ module CollectiveIdea #:nodoc:
331 without_self self_and_descendants 345 without_self self_and_descendants
332 end 346 end
333 347
334 # Returns a set of only this entry's immediate children
335 def children
336 nested_set_scope.scoped :conditions => {parent_column_name => self}
337 end
338
339 def is_descendant_of?(other) 348 def is_descendant_of?(other)
340 other.left < self.left && self.left < other.right && same_scope?(other) 349 other.left < self.left && self.left < other.right && same_scope?(other)
341 end 350 end
@@ -361,13 +370,13 @@ module CollectiveIdea #:nodoc:
361 370
362 # Find the first sibling to the left 371 # Find the first sibling to the left
363 def left_sibling 372 def left_sibling
364 siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} < ?", left], 373 siblings.find(:first, :conditions => ["#{self.class.quoted_table_name}.#{quoted_left_column_name} < ?", left],
365 :order => "#{self.class.table_name}.#{quoted_left_column_name} DESC") 374 :order => "#{self.class.quoted_table_name}.#{quoted_left_column_name} DESC")
366 end 375 end
367 376
368 # Find the first sibling to the right 377 # Find the first sibling to the right
369 def right_sibling 378 def right_sibling
370 siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} > ?", left]) 379 siblings.find(:first, :conditions => ["#{self.class.quoted_table_name}.#{quoted_left_column_name} > ?", left])
371 end 380 end
372 381
373 # Shorthand method for finding the left sibling and moving to the left of it. 382 # Shorthand method for finding the left sibling and moving to the left of it.
@@ -417,7 +426,7 @@ module CollectiveIdea #:nodoc:
417 protected 426 protected
418 427
419 def without_self(scope) 428 def without_self(scope)
420 scope.scoped :conditions => ["#{self.class.table_name}.#{self.class.primary_key} != ?", self] 429 scope.scoped :conditions => ["#{self.class.quoted_table_name}.#{self.class.primary_key} != ?", self]
421 end 430 end
422 431
423 # All nested set queries should use this nested_set_scope, which performs finds on 432 # All nested set queries should use this nested_set_scope, which performs finds on
@@ -432,6 +441,19 @@ module CollectiveIdea #:nodoc:
432 self.class.base_class.scoped options 441 self.class.base_class.scoped options
433 end 442 end
434 443
444 def store_new_parent
445 @move_to_new_parent_id = parent_id_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
435 # on creation, set automatically lft and rgt to the end of the tree 457 # on creation, set automatically lft and rgt to the end of the tree
436 def set_default_left_and_right 458 def set_default_left_and_right
437 maxright = nested_set_scope.maximum(right_column_name) || 0 459 maxright = nested_set_scope.maximum(right_column_name) || 0
@@ -442,29 +464,38 @@ module CollectiveIdea #:nodoc:
442 464
443 # Prunes a branch off of the tree, shifting all of the elements on the right 465 # Prunes a branch off of the tree, shifting all of the elements on the right
444 # back to the left so the counts still work. 466 # back to the left so the counts still work.
445 def prune_from_tree 467 def destroy_descendants
446 return if right.nil? || left.nil? 468 return if right.nil? || left.nil? || skip_before_destroy
447 diff = right - left + 1 469
448
449 delete_method = acts_as_nested_set_options[:dependent] == :destroy ?
450 :destroy_all : :delete_all
451
452 self.class.base_class.transaction do 470 self.class.base_class.transaction do
453 nested_set_scope.send(delete_method, 471 if acts_as_nested_set_options[:dependent] == :destroy
454 ["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?", 472 descendants.each do |model|
455 left, right] 473 model.skip_before_destroy = true
456 ) 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
457 nested_set_scope.update_all( 485 nested_set_scope.update_all(
458 ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff], 486 ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff],
459 ["#{quoted_left_column_name} >= ?", right] 487 ["#{quoted_left_column_name} > ?", right]
460 ) 488 )
461 nested_set_scope.update_all( 489 nested_set_scope.update_all(
462 ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff], 490 ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff],
463 ["#{quoted_right_column_name} >= ?", right] 491 ["#{quoted_right_column_name} > ?", right]
464 ) 492 )
493
494 # Don't allow multiple calls to destroy to corrupt the set
495 self.skip_before_destroy = true
465 end 496 end
466 end 497 end
467 498
468 # reload left, right, and parent 499 # reload left, right, and parent
469 def reload_nested_set 500 def reload_nested_set
470 reload(:select => "#{quoted_left_column_name}, " + 501 reload(:select => "#{quoted_left_column_name}, " +
diff --git a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/compatability.rb b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/compatability.rb
deleted file mode 100644
index 2d11da3..0000000
--- a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/compatability.rb
+++ /dev/null
@@ -1,29 +0,0 @@
1# Rails <2.x doesn't define #except
2class Hash #:nodoc:
3 # Returns a new hash without the given keys.
4 def except(*keys)
5 clone.except!(*keys)
6 end unless method_defined?(:except)
7
8 # Replaces the hash without the given keys.
9 def except!(*keys)
10 keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
11 keys.each { |key| delete(key) }
12 self
13 end unless method_defined?(:except!)
14end
15
16# NamedScope is new to Rails 2.1
17unless defined? ActiveRecord::NamedScope
18 require 'awesome_nested_set/named_scope'
19 ActiveRecord::Base.class_eval do
20 include CollectiveIdea::NamedScope
21 end
22end
23
24# Rails 1.2.x doesn't define #quoted_table_name
25class ActiveRecord::Base #:nodoc:
26 def self.quoted_table_name
27 self.connection.quote_column_name(self.table_name)
28 end unless methods.include?('quoted_table_name')
29end \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/named_scope.rb b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/named_scope.rb
deleted file mode 100644
index 1836498..0000000
--- a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/named_scope.rb
+++ /dev/null
@@ -1,140 +0,0 @@
1# Taken from Rails 2.1
2module CollectiveIdea #:nodoc:
3 module NamedScope #:nodoc:
4 # All subclasses of ActiveRecord::Base have two named_scopes:
5 # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
6 # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly:
7 #
8 # Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
9 #
10 # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
11 # intermediate values (scopes) around as first-class objects is convenient.
12 def self.included(base)
13 base.class_eval do
14 extend ClassMethods
15 named_scope :scoped, lambda { |scope| scope }
16 end
17 end
18
19 module ClassMethods #:nodoc:
20 def scopes
21 read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
22 end
23
24 # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
25 # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
26 #
27 # class Shirt < ActiveRecord::Base
28 # named_scope :red, :conditions => {:color => 'red'}
29 # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
30 # end
31 #
32 # The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>,
33 # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
34 #
35 # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
36 # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
37 # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
38 # as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
39 # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
40 #
41 # These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
42 # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
43 # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
44 #
45 # All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to
46 # <tt>has_many</tt> associations. If,
47 #
48 # class Person < ActiveRecord::Base
49 # has_many :shirts
50 # end
51 #
52 # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
53 # only shirts.
54 #
55 # Named scopes can also be procedural.
56 #
57 # class Shirt < ActiveRecord::Base
58 # named_scope :colored, lambda { |color|
59 # { :conditions => { :color => color } }
60 # }
61 # end
62 #
63 # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
64 #
65 # Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
66 #
67 # class Shirt < ActiveRecord::Base
68 # named_scope :red, :conditions => {:color => 'red'} do
69 # def dom_id
70 # 'red_shirts'
71 # end
72 # end
73 # end
74 #
75 #
76 # For testing complex named scopes, you can examine the scoping options using the
77 # <tt>proxy_options</tt> method on the proxy itself.
78 #
79 # class Shirt < ActiveRecord::Base
80 # named_scope :colored, lambda { |color|
81 # { :conditions => { :color => color } }
82 # }
83 # end
84 #
85 # expected_options = { :conditions => { :colored => 'red' } }
86 # assert_equal expected_options, Shirt.colored('red').proxy_options
87 def named_scope(name, options = {}, &block)
88 scopes[name] = lambda do |parent_scope, *args|
89 Scope.new(parent_scope, case options
90 when Hash
91 options
92 when Proc
93 options.call(*args)
94 end, &block)
95 end
96 (class << self; self end).instance_eval do
97 define_method name do |*args|
98 scopes[name].call(self, *args)
99 end
100 end
101 end
102 end
103
104 class Scope #:nodoc:
105 attr_reader :proxy_scope, :proxy_options
106 [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
107 delegate :scopes, :with_scope, :to => :proxy_scope
108
109 def initialize(proxy_scope, options, &block)
110 [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
111 extend Module.new(&block) if block_given?
112 @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
113 end
114
115 def reload
116 load_found; self
117 end
118
119 protected
120 def proxy_found
121 @found || load_found
122 end
123
124 private
125 def method_missing(method, *args, &block)
126 if scopes.include?(method)
127 scopes[method].call(self, *args)
128 else
129 with_scope :find => proxy_options do
130 proxy_scope.send(method, *args, &block)
131 end
132 end
133 end
134
135 def load_found
136 @found = find(:all)
137 end
138 end
139 end
140end \ No newline at end of file