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

Revision 1079, 30.4 kB (checked in by julians, 3 years ago)

Added GPL boilerplate header to all .rb, .rhtml and .rxml files; added GPL v2 text in file COPYING; cf http://producingoss.com/html-chunk/license-quickstart.html#license-quickstart-applying

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