root/trunk/vendor/plugins/faster_nested_set/lib/faster_nested_set.rb

Revision 1180, 30.4 kB (checked in by matthewl, 2 years ago)

Changed licensing of plugin FasterNestedSet? to LGPL v3

Line 
1 # Copyright (C) 2007 Rising Sun Pictures and Matthew Landauer
2 #
3 # This file is part of FasterNestedSet
4 #
5 # FasterNestedSet is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Lesser General Public License for more details.
14
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 require 'active_record'
19 require 'thread'
20 require 'pp'
21
22 #
23 #  This is another nested set implementation for RoR.  Its main
24 #  advantage over existing implementations is that it allows for
25 #  deferred inserts (large subtrees can be inserted in one go where
26 #  other implementations incur a full table update for each individual
27 #  inserted node), a more natural API (there's no need to pay
28 #  attention to outdated left/right values in nodes), and that it only
29 #  uses one update statement per insertion/deletion/move operation
30 #  instead of two.
31 #
32 #  This implementation aims to be largely compatible to tree and
33 #  nested_set (the default implementations shipping with RoR) as well
34 #  as better_nested_set
35 #  (http://opensource.symetrie.com/trac/better_nested_set/).
36 #
37 #  - the acts_as_nested_set declaration's configuration is
38 #    compatible to the acts_as_nested_set declaration: the
39 #    parent_column, left_column, right_column and scope options are
40 #    supported and semantically identical.
41 #
42 #  - nested_set's children_count(), full_set(), all_children(),
43 #    direct_children(), root?() and child?() methods are supported and
44 #    semantically identical.  add_child() is supported as well but
45 #    deprecated (see below.)
46 #
47 #  Adding children shouldn't be handled as in nested_set (using the
48 #  add_child method) but as in tree, that is using
49 #  parent.children.create or parent.children.build, where the latter
50 #  will not commit the added child to the database immediately.  This
51 #  allows for building subtrees in memory and committing them to the
52 #  database in one go.  For large subtrees inserted in a deferred
53 #  manner, this reduces the O(n+n^2) complexity of an insert found in
54 #  the nested_set and better_nested_set implementations (one insert
55 #  for each new node plus two full-table updates for each new row
56 #  affecting on average half of the rows) to O(n) complexity (one
57 #  insert for each row plus two full-table updates total).  Note that
58 #  the add_child() method found in the default nested_set
59 #  implementation is supported for compatibility reasons but should be
60 #  avoided in favor of the tree interface.
61 #
62 #  This implementation tries to do "the right thing" when possible.
63 #  For instance, you shouldn't ever have to worry about outdated
64 #  nodes.
65 #
66 #  Suggested table layout (for PostgreSQL):
67 #
68 #  create table example_nodes (
69 #     id         serial   primary key,
70 #     parent_id  integer  null references example_nodes(id),
71 #     lft        integer  not null,
72 #     rgt        integer  not null,
73 #     scope      integer  not null,
74 #     ...
75 #     constraint unique_lft unique (scope, lft),
76 #     constraint unique_rgt unique (scope, rgt),
77 #     constraint valid_lft_rgt check (left > 0 and right > left)
78 #  );
79 #
80 #  Notes with regard to the table layout:
81 #
82 #  - parent_id, lft and rgt are column names inherited from the
83 #    default nested_set implementation.  Personally, the author would
84 #    use the names parent_id, left_edge and right_edge.  If you choose
85 #    different names you'll have to specify the corresponding
86 #    configuration options (:parent_column, :left_column, and
87 #    :right_column, resp.).
88 #
89 #  - The parent_id column must be nullable since a root node has no
90 #    parent.  It should have a foreign key constraint so that all
91 #    non-root nodes are guaranteed to have a valid parent.
92 #    Unfortunately, there doesn't appear to be a constraint that could
93 #    be used to make sure that there's only one root node per scope.
94 #
95 #  - You can have a scope discriminator for storing more than one
96 #    distinct, non-overlapping tree in the same table.  In this
97 #    example, an integer is used but in real life it's more likely to
98 #    be a reference to another table.  This column should usually be
99 #    declared not null since every node is in a particular scope.
100 #    Note that the scope condition must be configured explicitly using
101 #    the :scope option.
102 #
103 #  - The left and right values can (and should) be declared unique
104 #    within each scope as shown in the example - this implementation
105 #    will update existing values before inserting new entries so that
106 #    this condition will not be violated unless there's an internal
107 #    problem. 
108 #
109 #    Ideally, you would specify a constraint that makes sure that the
110 #    union of the values in both left and right is unique - in other
111 #    words, a constraint that makes sure that there is no left value
112 #    that matches a right value in the same or any other row, and vice
113 #    versa.  Unfortunately, to the knowledge of the author there is no
114 #    such constraint type in any database product.  The unique(lft,
115 #    rgt) constraint might appear to serve that purpose but instead it
116 #    means that every combination of lft and rgt needs to be unique
117 #    (and thus is weaker than defining each of the two columns unique
118 #    individually.)
119 #
120 #    You can (and should) have an additional constraint in place that
121 #    makes sure that the left and right values are positive, and that
122 #    the right value is always greater than the left value, as shown
123 #    in the example.
124
125 module Rsp
126   module Acts #:nodoc:
127     module FasterNestedSet #:nodoc:
128       module ChildrenExtension #:nodoc:
129         def build(attributes)
130           result = super
131           @node.child_added(result)
132           result.parent_assoc = @node
133           result
134         end
135
136         def node=(node)
137           @node = node
138         end
139        
140         def add_to_cache(child)
141           @target << child
142         end
143         def empty_cache
144           @target = []
145         end
146       end
147
148       def self.included(mod)
149         mod.extend(ClassMethods)
150       end
151
152       # declare the class level helper methods which
153       # will load the relevant instance methods
154       # defined below when invoked
155       module ClassMethods
156         def acts_as_nested_set(options=nil)
157
158           before_save("self.nested_set_before_save; true")
159           after_save("self.nested_set_after_save; true")
160
161           # --- Below this point taken verbose from tree.rb --- #
162
163           configuration = {
164             :foreign_key => "parent_id",
165             :order => nil,
166             :counter_cache => nil,
167             :left_column => "lft",
168             :right_column => "rgt",
169             :scope => "1 = 1" ,
170             :text_column => nil,
171             :level_column => nil
172           }
173           configuration.update(options) if options.is_a?(Hash)
174
175           configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
176
177           if configuration[:scope].is_a?(Symbol)
178             scope_condition_method = %(
179               def scope_condition
180                 if #{configuration[:scope].to_s}.nil?
181                   "#{table_name}.#{configuration[:scope].to_s} IS NULL"
182                 else
183                   "#{table_name}.#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
184                 end
185               end
186             )
187           else
188             scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
189           end
190
191           belongs_to :parent_assoc, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
192
193           has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy, :extend => Rsp::Acts::FasterNestedSet::ChildrenExtension
194
195           class_eval <<-EOV
196             #{scope_condition_method}
197
198             def left_col_name() "#{configuration[:left_column]}" end
199
200             def right_col_name() "#{configuration[:right_column]}" end
201
202             def level_col_name() "#{configuration[:level_column]}" end
203
204             def has_level_column?() #{not configuration[:level_column].nil?} end
205
206             def parent_col_name() "#{configuration[:foreign_key]}" end
207
208             def parent_column() "#{configuration[:foreign_key]}" end
209
210             def self.roots
211               find(:all,
212                    :conditions => "#{configuration[:foreign_key]} IS NULL",
213                    :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
214             end
215
216             def self.root
217               find(:first,
218                    :conditions => "#{configuration[:foreign_key]} IS NULL",
219                    :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
220             end
221
222             def children_count
223               children_count_internal
224             end
225           EOV
226
227           include Rsp::Acts::FasterNestedSet::InstanceMethods
228         end
229       end
230
231       # Adds instance methods.
232       module InstanceMethods
233
234         def initialize(attributes)
235           if (not attributes.nil?) and (attributes.has_key?(:parent) or attributes.has_key?(parent_column)) then
236             raise "Do not specify parent value on initialization; instead, use parent.children.build or parent.children.create"
237           end
238           super
239         end
240
241         def parent=(_parent)
242           prev_parent = self.parent_assoc
243           if not prev_parent.nil?
244
245             prev_parent_lft = prev_parent[left_col_name]
246             prev_parent_rgt = prev_parent[right_col_name]
247             prev_parent_id  = prev_parent.id
248
249             self.parent_assoc = _parent
250             _parent.child_added(self)
251
252             if _parent[left_col_name] > prev_parent_lft then
253               node_new_right = _parent[right_col_name] - 1
254             else
255               node_new_right = _parent[right_col_name] + (self[right_col_name] - self[left_col_name])
256             end
257             node_new_left = node_new_right - (self[right_col_name] - self[left_col_name])
258
259             moved_subtree_left_inclusive = self[left_col_name]
260             moved_subtree_right_inclusive = self[right_col_name]
261             moved_subtree_offset = node_new_left - self[left_col_name]
262
263             if _parent[left_col_name] > prev_parent_lft then
264               interior_subtree_left_inclusive = self[right_col_name] + 1
265               interior_subtree_right_inclusive = node_new_right
266               interior_subtree_offset = - (self[right_col_name] - self[left_col_name] + 1)
267             else
268               interior_subtree_left_inclusive = node_new_left
269               interior_subtree_right_inclusive = self[left_col_name] - 1
270               interior_subtree_offset = (self[right_col_name] - self[left_col_name] + 1)
271             end
272
273             self.class.update_all( [
274                                      "#{left_col_name} = #{left_col_name} + (CASE " + \
275                                      "   WHEN #{left_col_name} >= ? AND #{left_col_name} <= ? THEN ? " + \
276                                      "   WHEN #{left_col_name} >= ? AND #{left_col_name} <= ? THEN ? " + \
277                                      "   ELSE 0 " + \
278                                      "END), " + \
279                                      "#{right_col_name} = #{right_col_name} + (CASE " + \
280                                      "   WHEN #{right_col_name} >= ? AND #{right_col_name} <= ? THEN ? " + \
281                                      "   WHEN #{right_col_name} >= ? AND #{right_col_name} <= ? THEN ? " + \
282                                      "   ELSE 0 " + \
283                                      "END)",
284                                      moved_subtree_left_inclusive,
285                                      moved_subtree_right_inclusive,
286                                      moved_subtree_offset,
287
288                                      interior_subtree_left_inclusive,
289                                      interior_subtree_right_inclusive,
290                                      interior_subtree_offset,
291
292                                      moved_subtree_left_inclusive,
293                                      moved_subtree_right_inclusive,
294                                      moved_subtree_offset,
295
296                                      interior_subtree_left_inclusive,
297                                      interior_subtree_right_inclusive,
298                                      interior_subtree_offset
299                                    ], 
300                                    [
301                                      "#{scope_condition} AND " + \
302                                      "(" + \
303                                      "     (#{right_col_name} >= ? AND #{left_col_name} <= ?)" + \
304                                      "  OR (#{right_col_name} >= ? AND #{left_col_name} <= ?)" + \
305                                      ")",
306                                      moved_subtree_left_inclusive,
307                                      moved_subtree_right_inclusive,
308
309                                      interior_subtree_left_inclusive,
310                                      interior_subtree_right_inclusive
311                                    ])
312
313           else
314             self.parent_assoc = _parent
315             _parent.child_added(self)
316           end
317         end
318
319         def parent
320           self.parent_assoc || @parent_internal
321         end
322
323         def has_parent?
324           not self.parent.nil?
325         end
326
327         def after_initialize
328           self.children.node = self
329         end
330
331         def after_find
332           self.children.node = self
333         end
334
335         def reload
336           result = super
337           self.children.node = self
338           result
339         end
340
341         def parent_internal=(parent)
342           @parent_internal = parent
343         end
344
345         def child_added(node)
346           node.parent_internal = self
347           if not self.new_record? then
348             self.add_dirty_child(node)
349           end
350         end
351
352         def dirty= (val)
353           @dirty = true
354         end
355
356         def add_dirty_child(child)
357           if true
358             found = false
359             self.children.each_index do |index|
360               if self.children[index].id == child.id then
361                 self.children[index] = child
362                 found = true
363               end
364             end
365             if not found
366               self.children << child
367             end
368           end
369           child.dirty = true
370           if @parent_internal then
371             @parent_internal.add_dirty_child(self)
372           else
373             self.dirty = true
374           end
375         end
376
377         def destroy
378           if Thread.current["root_deleted_node"].nil?
379             begin
380               Thread.current["root_deleted_node"] = self
381
382               return false if callback(:before_destroy) == false
383
384               if not self.new_record?
385                 self.class.transaction do
386
387                   result = self.class.find(:first, :select => [ left_col_name, right_col_name ].join(", "), :conditions => [ "#{self.class.primary_key} = ?", self.id ])
388                   left = result[left_col_name]
389                   right = result[right_col_name]
390
391                   self.class.delete_all([
392                                           "#{scope_condition} AND #{left_col_name} BETWEEN ? AND ?",
393                                           left,
394                                           right
395                                         ])
396
397                   self.class.update_all([
398                                           "#{left_col_name} = #{left_col_name} - (CASE " + \
399                                           "   WHEN #{left_col_name} > ? " + \
400                                           "   THEN ? " + \
401                                           "   ELSE 0 " + \
402                                           "END), " + \
403                                           "#{right_col_name} = #{right_col_name} - (CASE " + \
404                                           "   WHEN #{right_col_name} > ? " + \
405                                           "   THEN ? " + \
406                                           "   ELSE 0 " + \
407                                           "END)"
408                                           right,
409                                           right-left+1,
410                                           right,
411                                           right-left+1
412                                         ],
413                                         [
414                                           "#{scope_condition} AND #{right_col_name} > ?",
415                                           right
416                                         ])
417
418                   @children.each do |child|
419                     child.destroy
420                   end
421                 end
422               end
423               callback(:after_destroy)
424             ensure               
425               Thread.current["root_deleted_node"] = nil
426             end
427           else
428             return false if callback(:before_destroy) == false
429             @children.each do |child|
430               child.destroy
431             end
432             callback(:after_destroy)
433           end
434         end
435
436         def update_edge_data(left, level)
437           self[left_col_name] = left
438           self[level_col_name] = level unless not has_level_column?
439           child_left = left + 1
440           child_right = child_left
441           if @children
442             @children.each do |child|
443               child_right = child.update_edge_data(child_left, level + 1) + 1
444               child_left = child_right
445             end
446           end
447           right = child_right
448           self[right_col_name] = right
449           right
450         end
451
452         def nested_set_after_save
453           if Thread.current["saved_node"] == self
454             Thread.current["saved_node"] = nil
455
456             if not @insert_range.nil?
457               insert_offset = "((" + @left_select_expression + ") + #{@insert_range})"
458
459               self.class.update_all( "#{left_col_name} = #{left_col_name} + (CASE WHEN #{left_col_name} >= (#{@left_select_expression}) THEN #{@insert_range} WHEN #{left_col_name} < 0 THEN #{insert_offset} ELSE 0 END), #{right_col_name} = #{right_col_name} + (CASE WHEN #{right_col_name} >= (#{@left_select_expression}) THEN #{@insert_range} WHEN #{right_col_name} < 0 THEN #{insert_offset} ELSE 0 END)"
460                                      "#{scope_condition} AND (#{right_col_name} >= (#{@left_select_expression}) OR #{right_col_name} < 0)" )
461               if not self.parent_assoc.nil?
462                 self.parent_assoc[right_col_name] += 2
463               end
464             end
465
466           end
467          
468           @dirty = false
469
470           if Thread.current["unsaved_children"] then
471             unsaved_children = Thread.current["unsaved_children"]
472             Thread.current["unsaved_children"] = []
473             unsaved_children.each do |unsaved_child|
474               unsaved_child.save
475             end
476           end
477          
478         end
479
480         def dirty?
481           @dirty
482         end
483
484         def save
485           if Thread.current["transaction_node"].nil?
486             Thread.current["transaction_node"] = self
487             self.class.transaction {
488               super
489             }
490             Thread.current["transaction_node"] = nil
491           else
492             super
493           end
494         end
495
496
497         def nested_set_before_save
498           @insert_range = nil
499
500           if @new_record or @save_required then
501             self.nested_set_before_save_new_record
502           elsif @dirty then
503             self.nested_set_before_save_dirty_record
504           end
505         end
506
507         def nested_set_before_save_dirty_record
508           if @dirty and self.children.loaded then
509             self.children.each do |child|
510               if child.dirty?
511                 if child.new_record? or child.save_required?
512                   Thread.current["unsaved_children"] = [child] + (Thread.current["unsaved_children"] || [])
513                 else
514                   child.nested_set_before_save_dirty_record
515                 end
516               end
517             end
518           end         
519         end
520
521         def nested_set_before_save_new_record
522           @insert_range = nil
523          
524           if Thread.current["saved_node"].nil?
525             Thread.current["saved_node"] = self
526
527             if self.parent_assoc.nil?
528               left = 1
529               level = 1
530               @left_select_expression = "SELECT CASE WHEN MAX(#{right_col_name}) IS NOT NULL THEN MAX(#{right_col_name}) + 1 ELSE 1 END FROM #{self.class.table_name} WHERE #{scope_condition} AND #{parent_col_name} IS NULL AND #{right_col_name} > 0"
531             else
532               left = self.parent_assoc[right_col_name]
533               if has_level_column?
534                 level = self.parent_assoc[level_col_name] + 1
535               else
536                 level = 0
537               end
538               @left_select_expression = "SELECT #{right_col_name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = #{self.parent_assoc.id}"
539             end
540
541             @insert_range = self.update_edge_data(0, level) + 1
542
543             self.update_edge_data(-@insert_range, level)
544           end
545         end
546
547         # --- Below this point taken verbose from tree.rb --- #
548
549         # Returns list of ancestors, starting from parent until root.
550         #
551         #   subchild1.ancestors # => [child1, root]
552         def ancestors
553           self.class.find(:all, :conditions => [ "#{left_col_name} < (select #{left_col_name} from #{self.class.table_name} where #{self.class.primary_key} = ?) and #{right_col_name} > (select #{right_col_name} from #{self.class.table_name} where #{self.class.primary_key} = ?) and #{scope_condition}", self.id, self.id ], :order => "#{left_col_name} desc")
554         end
555
556         #
557         # Return a list of ancestors, starting from parent, up to (but excluding) the given node
558         #
559         def self_and_ancestors_up_to(other)
560           self.class.find(:all, :conditions => [ "#{left_col_name} <= (select #{left_col_name} from #{self.class.table_name} where #{self.class.primary_key} = ?) and #{right_col_name} >= (select #{right_col_name} from #{self.class.table_name} where #{self.class.primary_key} = ?) and #{level_col_name} > (select #{level_col_name} from #{self.class.table_name} where #{self.class.primary_key} = ?) and #{scope_condition}", self.id, self.id, other.id ], :order => "#{left_col_name} desc")
561         end
562
563         def root
564           node = self
565           node = node.parent until not node.has_parent?
566           node
567         end
568
569         def siblings
570           self_and_siblings - [self]
571         end
572
573         def self_and_siblings
574           has_parent? ? parent.children : self.class.roots
575         end
576
577         # --- Above this point taken verbose from tree.rb --- #
578
579         # --- Below this point taken verbose from nested_set.rb --- #
580
581         # Returns a set of itself and all of its nested children
582         def full_set
583           self.class.base_class.find(:all, :conditions => [
584                                        "#{scope_condition} AND (#{left_col_name} BETWEEN (SELECT #{left_col_name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = ?) AND (SELECT #{right_col_name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = ?))",
585                                        self.id,
586                                        self.id
587                                      ])
588         end
589                  
590         # Returns a set of all of its children and nested children
591         def all_children
592           self.class.base_class.find(:all, :conditions => [
593                                        "#{scope_condition} AND (#{left_col_name} > (SELECT #{left_col_name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = ?)) AND (#{right_col_name} < (SELECT #{right_col_name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = ?))",
594                                        self.id,
595                                        self.id
596                                      ])
597         end
598                                  
599         # Returns a set of only this entry's immediate children
600         def direct_children
601           self.class.base_class.find(:all, :conditions => [ "#{scope_condition} and #{parent_column} = ?", self.id ])
602         end
603
604         # Returns true is this is a root node. 
605         def root?
606           parent_id = self[parent_column]
607           (parent_id == 0 || parent_id.nil?) && (self[left_col_name] == 1) && (self[right_col_name] > self[left_col_name])
608         end                                                                                             
609                                    
610         # Returns true is this is a child node
611         def child?                         
612           parent_id = self[parent_column]
613           !(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name])
614         end     
615        
616         # Returns true if we have no idea what this is
617         def unknown?
618           !root? && !child?
619         end
620
621         # --- Above this point taken verbose from nested_set.rb --- #
622
623         # --- Below this point taken verbose from better_nested_set.rb --- #
624         
625         # Returns the array of all parents and self
626         def self_and_ancestors
627           [self] + ancestors
628         end
629
630         # --- Above this point taken verbose from better_nested_set.rb --- #
631
632         def save_required?
633           @save_required
634         end
635
636         def save_required= (val)
637           @save_required = val
638         end
639
640         def add_child(child)
641           self.children << child
642           child.save
643         end
644
645         def parent_assigned(*args)
646           self.save_required = true
647           parent_assoc.child_added(self)
648         end
649
650         def child_create(attributes)
651           children.create(attributes)
652         end
653
654         def child_delete(child)
655           children.delete(child)
656         end
657
658         # Override the default implementation of update to write all attributes except
659         # lft and rgt
660         def update
661           if not self.new_record?
662             return false if callback(:before_update) == false
663             a = attributes_with_quotes(false)
664             a.delete(left_col_name)
665             a.delete(right_col_name)
666             connection.update("UPDATE #{self.class.table_name} " + \
667                               "SET #{quoted_comma_pair_list(connection, a)} " + \
668                               "WHERE #{self.class.primary_key} = #{id}",
669                               "#{self.class.name} Update"
670                               )
671             callback(:after_update)
672             return true
673           else
674             super
675           end
676         end
677        
678         def ensure_consistency
679           load_all_children
680           begin
681             self.ensure_consistency_recursive
682           rescue
683             self.print_recursive
684             raise
685           end
686         end
687
688         def print_recursive(indent=0)
689           indentation = "  " * indent
690           puts "#{indentation}#{name} (id=#{id}, lft=#{self[left_col_name]}, rgt=#{self[right_col_name]}, level=#{self[level_col_name]})"
691           self.children.each { |child| child.print_recursive(indent+1) }
692         end
693
694         def ensure_consistency_recursive
695           expect_left = self[left_col_name] + 1
696           children.each do |child|
697             if child[left_col_name] != expect_left
698               raise "in node #{child.id}, left expected to be #{expect_left} but is #{child[left_col_name]}"
699             end
700             expect_right = child.ensure_consistency_recursive
701             if child[right_col_name] != expect_right
702               raise "in node #{child.id}, right expected to be #{expect_right} but is #{child[right_col_name]}"
703             end
704             if child[level_col_name] != self[level_col_name] + 1
705               raise "in node #{child.id}, level expected to be #{self[level_col_name] + 1} but is #{child[level_col_name]}"
706             end
707             expect_left = expect_right + 1
708           end
709           if self[right_col_name] != expect_left
710             raise "in node #{id}, right expected to be #{expect_left} but is #{self[right_col_name]}"
711           end
712           return self[right_col_name]
713         end
714
715         # If depth is 0 will get *all* the children recursively. If non zero, will
716         # only get the children down to a particular level
717         def load_all_children(depth=0, options=nil)
718          
719           if depth == 0 or not has_level_column?
720             depth_condition = "1=1"
721           else
722             depth_condition = "#{self.class.table_name}.#{level_col_name} <= #{level + depth}"
723             end
724          
725           options = options || Hash.new
726
727           options[:conditions] = [
728             "#{depth_condition} AND #{scope_condition} AND (#{self.class.table_name}.#{left_col_name} > (SELECT #{left_col_name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = ?)) AND (#{self.class.table_name}.#{right_col_name} < (SELECT #{right_col_name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = ?))" ] + [
729             self.id,
730             self.id
731           ]
732
733           options[:order] = self.class.table_name + "." +left_col_name
734
735           result = self.class.find(:all, options)
736
737           idMap = Hash.new
738           result.each do |child|
739             idMap[child[:id]] = child
740           end
741
742           idMap[self.id] = self
743           self.children.empty_cache
744
745           result.each do |child|
746             idMap[child[:parent_id]].children.add_to_cache(child) unless child[:parent_id].nil?
747             child.parent_assoc = idMap[child[:parent_id]]
748           end
749
750           idMap.each do |key, child|
751             child.children.loaded
752           end
753         end
754
755         private
756         # Returns the number of nested children of this object.
757         def children_count_internal(force_reload = false)
758           if self.new_record?
759             self.children.inject(0) { |sum, child| sum + 1 + child.children_count }
760           else
761             left_and_right = self.class.find(:first,
762                                              :select => "#{left_col_name} AS left, #{right_col_name} AS right",
763                                              :conditions => [ "#{self.class.primary_key} = ?", self.id ])
764             (left_and_right["right"].to_i - left_and_right["left"].to_i - 1) / 2
765           end
766         end
767       end
768     end
769   end
770 end
771
772 # reopen ActiveRecord and include all the above to make
773 # them available to all our models if they want it
774
775 ActiveRecord::Base.class_eval do
776   include Rsp::Acts::FasterNestedSet
777 end
Note: See TracBrowser for help on using the browser.