[Ovirt-devel] [PATCH] rails 2.1 upgraded betternestedset

Jason Guiditta jguiditt at redhat.com
Mon Sep 29 13:48:49 UTC 2008


On Wed, 2008-09-24 at 13:48 -0400, Scott Seago wrote:
> As of now, our custom hacks to betternestedset are no longer required, as the latest code incorporates the filtering internally.
> 
> Signed-off-by: Scott Seago <sseago at redhat.com>
> ---
>  src/vendor/plugins/betternestedset/README          |    3 +-
>  src/vendor/plugins/betternestedset/init.rb         |    5 +-
>  .../betternestedset/lib/better_nested_set.rb       | 1055 +++++++++++++++-----
>  .../plugins/betternestedset/test/abstract_unit.rb  |    6 -
>  .../test/acts_as_nested_set_test.rb                |  833 +++++++++++++---
>  .../plugins/betternestedset/test/fixtures/mixin.rb |   19 +-
>  6 files changed, 1489 insertions(+), 432 deletions(-)
> 
> diff --git a/src/vendor/plugins/betternestedset/README b/src/vendor/plugins/betternestedset/README
> index 3f1297e..d11d4b5 100644
> --- a/src/vendor/plugins/betternestedset/README
> +++ b/src/vendor/plugins/betternestedset/README
> @@ -98,9 +98,10 @@ Other instance methods added by this plugin include:
>  * <tt>self_and_ancestors</tt> - array of all parents and self
>  * <tt>siblings</tt> - array of all siblings (items sharing the same parent)
>  * <tt>self_and_siblings</tt> - array of itself and all siblings
> -* <tt>children_count</tt> - count of all nested children
> +* <tt>children_count</tt> - count of all direct children
>  * <tt>children</tt> - array of all immediate children
>  * <tt>all_children</tt> - array of all children and nested children
> +* <tt>all_children_count</tt> - count of all nested children
>  * <tt>full_set</tt> - array of itself and all children and nested children
>  * <tt>leaves</tt> - array of the children of this node who do not have children
>  * <tt>leaves_count</tt> - the number of leaves
> diff --git a/src/vendor/plugins/betternestedset/init.rb b/src/vendor/plugins/betternestedset/init.rb
> index 32401ad..6d880f3 100644
> --- a/src/vendor/plugins/betternestedset/init.rb
> +++ b/src/vendor/plugins/betternestedset/init.rb
> @@ -8,4 +8,7 @@ require 'better_nested_set_helper'
>  ActiveRecord::Base.class_eval do
>    include SymetrieCom::Acts::NestedSet
>  end
> -ActionView::Base.send :include, SymetrieCom::Acts::BetterNestedSetHelper
> \ No newline at end of file
> +
> +if Object.const_defined?('ActionView')
> +  ActionView::Base.send :include, SymetrieCom::Acts::BetterNestedSetHelper
> +end
> \ No newline at end of file
> diff --git a/src/vendor/plugins/betternestedset/lib/better_nested_set.rb b/src/vendor/plugins/betternestedset/lib/better_nested_set.rb
> index e91ca93..9efbf25 100644
> --- a/src/vendor/plugins/betternestedset/lib/better_nested_set.rb
> +++ b/src/vendor/plugins/betternestedset/lib/better_nested_set.rb
> @@ -1,59 +1,74 @@
>  module SymetrieCom
>    module Acts #:nodoc:
>      module NestedSet #:nodoc:
> +
>        def self.included(base)
> -        base.extend(ClassMethods)              
> +        base.extend(ClassMethods)
>        end
>        # This module provides an enhanced acts_as_nested_set mixin for ActiveRecord.
>        # Please see the README for background information, examples, and tips on usage.
>        module ClassMethods
>          # Configuration options are:
> +        # * +dependent+ - behaviour for cascading destroy operations (default: :delete_all)
>          # * +parent_column+ - Column name for the parent/child foreign key (default: +parent_id+).
> -        # * +left_column+ - Column name for the left index (default: +lft+). 
> -        # * +right_column+ - Column name for the right index (default: +rgt+). NOTE: 
> +        # * +left_column+ - Column name for the left index (default: +lft+).
> +        # * +right_column+ - Column name for the right index (default: +rgt+). NOTE:
>          #   Don't use +left+ and +right+, since these are reserved database words.
> -        # * +scope+ - Restricts what is to be considered a tree. Given a symbol, it'll attach "_id" 
> -        #   (if it isn't there already) and use that as the foreign key restriction. It's also possible 
> +        # * +scope+ - Restricts what is to be considered a tree. Given a symbol, it'll attach "_id"
> +        #   (if it isn't there already) and use that as the foreign key restriction. It's also possible
>          #   to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
>          #   Example: <tt>acts_as_nested_set :scope => 'tree_id = #{tree_id} AND completed = 0'</tt>
> -        # * +text_column+ - Column name for the title field (optional). Used as default in the 
> -        #   {your-class}_options_for_select helper method. If empty, will use the first string field 
> +        # * +text_column+ - Column name for the title field (optional). Used as default in the
> +        #   {your-class}_options_for_select helper method. If empty, will use the first string field
>          #   of your model class.
> -        def acts_as_nested_set(options = {})          
> -          
> +        def acts_as_nested_set(options = {})
> +
> +          extend(SingletonMethods) unless respond_to?(:find_in_nestedset)
> +
>            options[:scope] = "#{options[:scope]}_id".intern if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
> -          
> +
>            write_inheritable_attribute(:acts_as_nested_set_options,
>               { :parent_column  => (options[:parent_column] || 'parent_id'),
>                 :left_column    => (options[:left_column]   || 'lft'),
>                 :right_column   => (options[:right_column]  || 'rgt'),
>                 :scope          => (options[:scope] || '1 = 1'),
>                 :text_column    => (options[:text_column] || columns.collect{|c| (c.type == :string) ? c.name : nil }.compact.first),
> -               :class          => self # for single-table inheritance
> +               :class          => self, # for single-table inheritance
> +               :dependent      => (options[:dependent] || :delete_all) # accepts :delete_all and :destroy
>                } )
> -          
> +
>            class_inheritable_reader :acts_as_nested_set_options
> -          
> +
> +          base_set_class.class_inheritable_accessor :acts_as_nested_set_scope_enabled
> +          base_set_class.acts_as_nested_set_scope_enabled = true
> +
>            if acts_as_nested_set_options[:scope].is_a?(Symbol)
>              scope_condition_method = %(
>                def scope_condition
>                  if #{acts_as_nested_set_options[:scope].to_s}.nil?
> -                  "#{acts_as_nested_set_options[:scope].to_s} IS NULL"
> +                  self.class.use_scope_condition? ? "#{table_name}.#{acts_as_nested_set_options[:scope].to_s} IS NULL" : "(1 = 1)"
>                  else
> -                  "#{acts_as_nested_set_options[:scope].to_s} = \#{#{acts_as_nested_set_options[:scope].to_s}}"
> +                  self.class.use_scope_condition? ? "#{table_name}.#{acts_as_nested_set_options[:scope].to_s} = \#{#{acts_as_nested_set_options[:scope].to_s}}" : "(1 = 1)"
>                  end
>                end
>              )
>            else
> -            scope_condition_method = "def scope_condition() \"#{acts_as_nested_set_options[:scope]}\" end"
> +            scope_condition_method = "def scope_condition(); self.class.use_scope_condition? ? \"#{acts_as_nested_set_options[:scope]}\" : \"(1 = 1)\"; end"
>            end
> -          
> +
> +          # skip recursive destroy calls
> +          attr_accessor  :skip_before_destroy
> +
>            # no bulk assignment
>            attr_protected  acts_as_nested_set_options[:left_column].intern,
>                            acts_as_nested_set_options[:right_column].intern,
>                            acts_as_nested_set_options[:parent_column].intern
>            # no assignment to structure fields
> -          module_eval <<-"end_eval", __FILE__, __LINE__
> +          class_eval <<-EOV
> +            before_create :set_left_right
> +            before_destroy :destroy_descendants
> +            include SymetrieCom::Acts::NestedSet::InstanceMethods
> +
>              def #{acts_as_nested_set_options[:left_column]}=(x)
>                raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:left_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
>              end
> @@ -64,120 +79,362 @@ module SymetrieCom
>                raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:parent_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
>              end
>              #{scope_condition_method}
> -          end_eval
> -          
> -          
> -          include SymetrieCom::Acts::NestedSet::InstanceMethods
> -          extend SymetrieCom::Acts::NestedSet::ClassMethods
> -          
> -          # adds the helper for the class
> -#          ActionView::Base.send(:define_method, "#{Inflector.underscore(self.class)}_options_for_select") { special=nil
> -#              "#{acts_as_nested_set_options[:text_column]} || "#{self.class} id #{id}"
> -#            }
> -          
> -        end
> -        
> -        
> -        # Returns the single root for the class (or just the first root, if there are several).
> -        # Deprecation note: the original acts_as_nested_set allowed roots to have parent_id = 0,
> -        # so we currently do the same. This silliness will not be tolerated in future versions, however.
> -        def root(find_opts={})
> -          roots_internal(:first, find_opts)
> -        end
> -        
> -        # Returns the roots and/or virtual roots of all trees. See the explanation of virtual roots in the README.
> -        def roots(find_opts={})
> -          roots_internal(:all, find_opts)
> -        end
> -        def roots_internal(all_or_one, find_opts={})
> -          conditions = "(#{acts_as_nested_set_options[:parent_column]} IS NULL"
> -          conditions += " OR #{acts_as_nested_set_options[:parent_column]} = 0)"
> -          order_col = "#{acts_as_nested_set_options[:left_column]}"
> -          opts = merge_incoming_opts({:conditions => conditions,
> -                                      :order => order_col},
> -                                     find_opts)
> -          acts_as_nested_set_options[:class].find(all_or_one, opts)
> -        end
> -        
> -        # Checks the left/right indexes of all records, 
> -        # returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem.
> -        def check_all
> -          total = 0
> -          transaction do
> -            # if there are virtual roots, only call check_full_tree on the first, because it will check other virtual roots in that tree.
> -            total = roots.inject(0) {|sum, r| sum + (r[r.left_col_name] == 1 ? r.check_full_tree : 0 )}
> -            raise ActiveRecord::ActiveRecordError, "Scope problems or nodes without a valid root" unless acts_as_nested_set_options[:class].count == total
> -          end
> -          return total
> -        end
> -        
> -        # Re-calculate the left/right values of all nodes. Can be used to convert ordinary trees into nested sets.
> -        def renumber_all
> -          scopes = []
> -          # only call it once for each scope_condition (if the scope conditions are messed up, this will obviously cause problems)
> -          roots.each do |r|
> -            r.renumber_full_tree unless scopes.include?(r.scope_condition)
> -            scopes << r.scope_condition
> -          end
> -        end
> -        
> -        # Returns an SQL fragment that matches _items_ *and* all of their descendants, for use in a WHERE clause.
> -        # You can pass it a single object, a single ID, or an array of objects and/or IDs.
> -        #   # if a.lft = 2, a.rgt = 7, b.lft = 12 and b.rgt = 13
> -        #   Set.sql_for([a,b]) # returns "((lft BETWEEN 2 AND 7) OR (lft BETWEEN 12 AND 13))"
> -        # Returns "1 != 1" if passed no items. If you need to exclude items, just use "NOT (#{sql_for(items)})".
> -        # Note that if you have multiple trees, it is up to you to apply your scope condition.
> -        def sql_for(items)
> -          items = [items] unless items.is_a?(Array)
> -          # get objects for IDs
> -          items.collect! {|s| s.is_a?(acts_as_nested_set_options[:class]) ? s : acts_as_nested_set_options[:class].find(s)}.uniq
> -          items.reject! {|e| e.new_record?} # exclude unsaved items, since they don't have left/right values yet
> -          
> -          return "1 != 1" if items.empty? # PostgreSQL didn't like '0', and SQLite3 didn't like 'FALSE'
> -          items.map! {|e| "(#{acts_as_nested_set_options[:left_column]} BETWEEN #{e[acts_as_nested_set_options[:left_column]]} AND #{e[acts_as_nested_set_options[:right_column]]})" }
> -          "(#{items.join(' OR ')})"
> -        end
> -
> -        # accept incoming opts to allow filtering of results.
> -        # So far only tested in limited use cases encountered in oVirt devel.
> -        def merge_incoming_opts(set_opts, incoming_opts)
> -          new_conditions = incoming_opts.delete(:conditions)
> -          set_opts[:conditions] = "(#{set_opts[:conditions]}) AND (#{new_conditions})" if new_conditions
> -          set_opts.merge(incoming_opts)
> -        end
> -        
> +          EOV
> +        end
> +
> +        module SingletonMethods
> +
> +          # Most query methods are wrapped in with_scope to provide further filtering
> +          # find_in_nested_set(what, outer_scope, inner_scope)
> +          # inner scope is user supplied, while outer_scope is the normal query
> +          # this way the user can override most scope attributes, except :conditions
> +          # which is merged; use :reverse => true to sort result in reverse direction
> +          def find_in_nested_set(*args)
> +            what, outer_scope, inner_scope = case args.length
> +              when 3 then [args[0], args[1], args[2]]
> +              when 2 then [args[0], nil, args[1]]
> +              when 1 then [args[0], nil, nil]
> +              else [:all, nil, nil]
> +            end
> +            if inner_scope && outer_scope && inner_scope.delete(:reverse) && outer_scope[:order] == "#{prefixed_left_col_name}"
> +              outer_scope[:order] = "#{prefixed_right_col_name} DESC"
> +            end
> +            acts_as_nested_set_options[:class].with_scope(:find => (outer_scope || {})) do
> +              acts_as_nested_set_options[:class].find(what, inner_scope || {})
> +            end
> +          end
> +
> +          # Count wrapped in with_scope
> +          def count_in_nested_set(*args)
> +            outer_scope, inner_scope = case args.length
> +              when 2 then [args[0], args[1]]
> +              when 1 then [nil, args[0]]
> +              else [nil, nil]
> +            end
> +            acts_as_nested_set_options[:class].with_scope(:find => (outer_scope || {})) do
> +              acts_as_nested_set_options[:class].count(inner_scope || {})
> +            end
> +          end
> +
> +          # Loop through set using block
> +          # pass :nested => false when result is not fully parent-child relational
> +          # for example with filtered result sets
> +          def recurse_result_set(result, options = {}, &block)
> +            return result unless block_given?
> +            inner_recursion = options.delete(:inner_recursion)
> +            result_set = inner_recursion ? result : result.dup
> +
> +            parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil
> +            options[:level] ||= 0
> +            options[:nested] = true unless options.key?(:nested)
> +
> +            siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set
> +            siblings.each do |sibling|
> +              result_set.delete(sibling)
> +              block.call(sibling, options[:level])
> +              opts = { :parent_id => sibling.id, :level => options[:level] + 1, :inner_recursion => true }
> +              recurse_result_set(result_set, opts, &block) if options[:nested]
> +            end
> +            result_set.each { |orphan| block.call(orphan, options[:level]) } unless inner_recursion
> +          end
> +
> +          # Loop and create a nested array of hashes (with children property)
> +          # pass :nested => false when result is not fully parent-child relational
> +          # for example with filtered result sets
> +          def result_to_array(result, options = {}, &block)
> +            array = []
> +            inner_recursion = options.delete(:inner_recursion)
> +            result_set = inner_recursion ? result : result.dup
> +
> +            parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil
> +            level = options[:level]   || 0
> +            options[:children]        ||= 'children'
> +            options[:methods]         ||= []
> +            options[:nested] = true unless options.key?(:nested)
> +            options[:symbolize_keys] = true unless options.key?(:symbolize_keys)
> +
> +            if options[:only].blank? && options[:except].blank?
> +              options[:except] = [:left_column, :right_column, :parent_column].inject([]) do |ex, opt|
> +                column = acts_as_nested_set_options[opt].to_sym
> +                ex << column unless ex.include?(column)
> +                ex
> +              end
> +            end
> +
> +            siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set
> +            siblings.each do |sibling|
> +              result_set.delete(sibling)
> +              node = block_given? ? block.call(sibling, level) : sibling.attributes(:only => options[:only], :except => options[:except])
> +              options[:methods].inject(node) { |enum, m| enum[m.to_s] = sibling.send(m) if sibling.respond_to?(m); enum }
> +              if options[:nested]
> +                opts = options.merge(:parent_id => sibling.id, :level => level + 1, :inner_recursion => true)
> +                childnodes = result_to_array(result_set, opts, &block)
> +                node[ options[:children] ] = childnodes if !childnodes.empty? && node.respond_to?(:[]=)
> +              end
> +              array << (options[:symbolize_keys] && node.respond_to?(:symbolize_keys) ? node.symbolize_keys : node)
> +            end
> +            unless inner_recursion
> +              result_set.each do |orphan|
> +                node = (block_given? ? block.call(orphan, level) : orphan.attributes(:only => options[:only], :except => options[:except]))
> +                options[:methods].inject(node) { |enum, m| enum[m.to_s] = orphan.send(m) if orphan.respond_to?(m); enum }
> +                array << (options[:symbolize_keys] && node.respond_to?(:symbolize_keys) ? node.symbolize_keys : node)
> +              end
> +            end
> +            array
> +          end
> +
> +          # Loop and create an xml structure. The following options are available
> +          # :root sets the root tag, :children sets the siblings tag
> +          # :record sets the node item tag, if given
> +          # see also: result_to_array and ActiveRecord::XmlSerialization
> +          def result_to_xml(result, options = {}, &block)
> +            inner_recursion = options.delete(:inner_recursion)
> +            result_set = inner_recursion ? result : result.dup
> +
> +            parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil
> +            options[:nested] = true unless options.key?(:nested)
> +
> +            options[:except] ||= []
> +            [:left_column, :right_column, :parent_column].each do |opt|
> +              column = acts_as_nested_set_options[opt].intern
> +              options[:except] << column unless options[:except].include?(column)
> +            end
> +
> +            options[:indent]  ||= 2
> +            options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
> +            options[:builder].instruct! unless options.delete(:skip_instruct)
> +
> +            record = options.delete(:record)
> +            root = options.delete(:root) || :nodes
> +            children = options.delete(:children) || :children
> +
> +            attrs = {}
> +            attrs[:xmlns] = options[:namespace] if options[:namespace]
> +
> +            siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set
> +            options[:builder].tag!(root, attrs) do
> +              siblings.each do |sibling|
> +                result_set.delete(sibling) if options[:nested]
> +                procs = options[:procs] ? options[:procs].dup : []
> +                procs << Proc.new { |opts| block.call(opts, sibling) } if block_given?
> +                if options[:nested]
> +                  proc = Proc.new do |opts|
> +                    proc_opts = opts.merge(:parent_id => sibling.id, :root => children, :record => record, :inner_recursion => true)
> +                    proc_opts[:procs] ||= options[:procs] if options[:procs]
> +                    proc_opts[:methods] ||= options[:methods] if options[:methods]
> +                    sibling.class.result_to_xml(result_set, proc_opts, &block)
> +                  end
> +                  procs << proc
> +                end
> +                opts = options.merge(:procs => procs, :skip_instruct => true, :root => record)
> +                sibling.to_xml(opts)
> +              end
> +            end
> +            options[:builder].target!
> +          end
> +
> +          # Loop and create a nested xml representation of nodes with attributes
> +          # pass :nested => false when result is not fully parent-child relational
> +          # for example with filtered result sets
> +          def result_to_attributes_xml(result, options = {}, &block)
> +            inner_recursion = options.delete(:inner_recursion)
> +            result_set = inner_recursion ? result : result.dup
> +
> +            parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil
> +            level = options[:level] || 0
> +            options[:methods]       ||= []
> +            options[:nested] = true unless options.key?(:nested)
> +            options[:dasherize] = true unless options.key?(:dasherize)
> +
> +            if options[:only].blank? && options[:except].blank?
> +              options[:except] = [:left_column, :right_column, :parent_column].inject([]) do |ex, opt|
> +                column = acts_as_nested_set_options[opt].to_sym
> +                ex << column unless ex.include?(column)
> +                ex
> +              end
> +            end
> +
> +            options[:indent]  ||= 2
> +            options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
> +            options[:builder].instruct! unless options.delete(:skip_instruct)
> +
> +            parent_attrs = {}
> +            parent_attrs[:xmlns] = options[:namespace] if options[:namespace]
> +
> +            siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set
> +            siblings.each do |sibling|
> +              result_set.delete(sibling)
> +              node_tag = (options[:record] || sibling[sibling.class.inheritance_column] || 'node').underscore
> +              node_tag = node_tag.dasherize unless options[:dasherize]
> +              attrs = block_given? ? block.call(sibling, level) : sibling.attributes(:only => options[:only], :except => options[:except])
> +              options[:methods].inject(attrs) { |enum, m| enum[m.to_s] = sibling.send(m) if sibling.respond_to?(m); enum }
> +              if options[:nested] && sibling.children?
> +                opts = options.merge(:parent_id => sibling.id, :level => level + 1, :inner_recursion => true, :skip_instruct => true)
> +                options[:builder].tag!(node_tag, attrs) { result_to_attributes_xml(result_set, opts, &block) }
> +              else
> +                options[:builder].tag!(node_tag, attrs)
> +              end
> +            end
> +            unless inner_recursion
> +              result_set.each do |orphan|
> +                node_tag = (options[:record] || orphan[orphan.class.inheritance_column] || 'node').underscore
> +                node_tag = node_tag.dasherize unless options[:dasherize]
> +                attrs = block_given? ? block.call(orphan, level) : orphan.attributes(:only => options[:only], :except => options[:except])
> +                options[:methods].inject(attrs) { |enum, m| enum[m.to_s] = orphan.send(m) if orphan.respond_to?(m); enum }
> +                options[:builder].tag!(node_tag, attrs)
> +              end
> +            end
> +            options[:builder].target!
> +          end
> +
> +          # Returns the single root for the class (or just the first root, if there are several).
> +          # Deprecation note: the original acts_as_nested_set allowed roots to have parent_id = 0,
> +          # so we currently do the same. This silliness will not be tolerated in future versions, however.
> +          def root(scope = {})
> +            find_in_nested_set(:first, { :conditions => "(#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)" }, scope)
> +          end
> +
> +          # Returns the roots and/or virtual roots of all trees. See the explanation of virtual roots in the README.
> +          def roots(scope = {})
> +            find_in_nested_set(:all, { :conditions => "(#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)", :order => "#{prefixed_left_col_name}" }, scope)
> +          end
> +
> +          # Checks the left/right indexes of all records,
> +          # returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem.
> +          def check_all
> +            total = 0
> +            transaction do
> +              # if there are virtual roots, only call check_full_tree on the first, because it will check other virtual roots in that tree.
> +              total = roots.inject(0) {|sum, r| sum + (r[r.left_col_name] == 1 ? r.check_full_tree : 0 )}
> +              raise ActiveRecord::ActiveRecordError, "Scope problems or nodes without a valid root" unless acts_as_nested_set_options[:class].count == total
> +            end
> +            return total
> +          end
> +
> +          # Re-calculate the left/right values of all nodes. Can be used to convert ordinary trees into nested sets.
> +          def renumber_all
> +            scopes = []
> +            # only call it once for each scope_condition (if the scope conditions are messed up, this will obviously cause problems)
> +            roots.each do |r|
> +              r.renumber_full_tree unless scopes.include?(r.scope_condition)
> +              scopes << r.scope_condition
> +            end
> +          end
> +
> +          # Returns an SQL fragment that matches _items_ *and* all of their descendants, for use in a WHERE clause.
> +          # You can pass it a single object, a single ID, or an array of objects and/or IDs.
> +          #   # if a.lft = 2, a.rgt = 7, b.lft = 12 and b.rgt = 13
> +          #   Set.sql_for([a,b]) # returns "((lft BETWEEN 2 AND 7) OR (lft BETWEEN 12 AND 13))"
> +          # Returns "1 != 1" if passed no items. If you need to exclude items, just use "NOT (#{sql_for(items)})".
> +          # Note that if you have multiple trees, it is up to you to apply your scope condition.
> +          def sql_for(items)
> +            items = [items] unless items.is_a?(Array)
> +            # get objects for IDs
> +            items.collect! {|s| s.is_a?(acts_as_nested_set_options[:class]) ? s : acts_as_nested_set_options[:class].find(s)}.uniq
> +            items.reject! {|e| e.new_record?} # exclude unsaved items, since they don't have left/right values yet
> +
> +            return "1 != 1" if items.empty? # PostgreSQL didn't like '0', and SQLite3 didn't like 'FALSE'
> +            items.map! {|e| "(#{prefixed_left_col_name} BETWEEN #{e[left_col_name]} AND #{e[right_col_name]})" }
> +            "(#{items.join(' OR ')})"
> +          end
> +
> +          # Wrap a method with this block to disable the default scope_condition
> +          def without_scope_condition(&block)
> +            if block_given?
> +              disable_scope_condition
> +              yield
> +              enable_scope_condition
> +            end
> +          end
> +
> +          def use_scope_condition?#:nodoc:
> +            base_set_class.acts_as_nested_set_scope_enabled == true
> +          end
> +
> +          def disable_scope_condition#:nodoc:
> +            base_set_class.acts_as_nested_set_scope_enabled = false
> +          end
> +
> +          def enable_scope_condition#:nodoc:
> +            base_set_class.acts_as_nested_set_scope_enabled = true
> +          end
> +
> +          def left_col_name#:nodoc:
> +            acts_as_nested_set_options[:left_column]
> +          end
> +          def prefixed_left_col_name#:nodoc:
> +            "#{table_name}.#{left_col_name}"
> +          end
> +          def right_col_name#:nodoc:
> +            acts_as_nested_set_options[:right_column]
> +          end
> +          def prefixed_right_col_name#:nodoc:
> +            "#{table_name}.#{right_col_name}"
> +          end
> +          def parent_col_name#:nodoc:
> +            acts_as_nested_set_options[:parent_column]
> +          end
> +          def prefixed_parent_col_name#:nodoc:
> +            "#{table_name}.#{parent_col_name}"
> +          end
> +          def base_set_class#:nodoc:
> +            acts_as_nested_set_options[:class] # for single-table inheritance
> +          end
> +
> +        end
> +
>        end
>  
>        # This module provides instance methods for an enhanced acts_as_nested_set mixin. Please see the README for background information, examples, and tips on usage.
>        module InstanceMethods
>          # convenience methods to make the code more readable
> -        def left_col_name()#:nodoc:
> -          acts_as_nested_set_options[:left_column]
> +        def left_col_name#:nodoc:
> +          self.class.left_col_name
> +        end
> +        def prefixed_left_col_name#:nodoc:
> +          self.class.prefixed_left_col_name
>          end
> -        def right_col_name()#:nodoc:
> -          acts_as_nested_set_options[:right_column]
> +        def right_col_name#:nodoc:
> +         self.class.right_col_name
>          end
> -        def parent_col_name()#:nodoc:
> -          acts_as_nested_set_options[:parent_column]
> +        def prefixed_right_col_name#:nodoc:
> +          self.class.prefixed_right_col_name
> +        end
> +        def parent_col_name#:nodoc:
> +          self.class.parent_col_name
> +        end
> +        def prefixed_parent_col_name#:nodoc:
> +          self.class.prefixed_parent_col_name
>          end
>          alias parent_column parent_col_name#:nodoc: Deprecated
> -        def base_set_class()#:nodoc:
> +        def base_set_class#:nodoc:
>            acts_as_nested_set_options[:class] # for single-table inheritance
>          end
> -        
> +
> +        # This takes care of valid queries when called on a root node
> +        def sibling_condition
> +          self[parent_col_name] ? "#{prefixed_parent_col_name} = #{self[parent_col_name]}" : "(#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)"
> +        end
> +
>          # On creation, automatically add the new node to the right of all existing nodes in this tree.
> -        def before_create # already protected by a transaction
> +        def set_left_right # already protected by a transaction within #create
>            maxright = base_set_class.maximum(right_col_name, :conditions => scope_condition) || 0
>            self[left_col_name] = maxright+1
>            self[right_col_name] = maxright+2
>          end
> -        
> +
>          # On destruction, delete all children and shift the lft/rgt values back to the left so the counts still work.
> -        def before_destroy # already protected by a transaction
> -          return if self[right_col_name].nil? || self[left_col_name].nil?
> -          self.reload # in case a concurrent move has altered the indexes
> +        def destroy_descendants # already protected by a transaction within #destroy
> +          return if self[right_col_name].nil? || self[left_col_name].nil? || self.skip_before_destroy
> +          reloaded = self.reload rescue nil # in case a concurrent move has altered the indexes - rescue if non-existent
> +          return unless reloaded
>            dif = self[right_col_name] - self[left_col_name] + 1
> -          base_set_class.delete_all( "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})" )
> +          if acts_as_nested_set_options[:dependent] == :delete_all
> +            base_set_class.delete_all( "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})" )
> +          else
> +            set = base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})", :order => "#{prefixed_right_col_name} DESC")
> +            set.each { |child| child.skip_before_destroy = true; remove_descendant(child) }
> +          end
>            base_set_class.update_all("#{left_col_name} = CASE \
>                                        WHEN #{left_col_name} > #{self[right_col_name]} THEN (#{left_col_name} - #{dif}) \
>                                        ELSE #{left_col_name} END, \
> @@ -186,127 +443,264 @@ module SymetrieCom
>                                        ELSE #{right_col_name} END",
>                                   scope_condition)
>          end
> -        
> +
>          # By default, records are compared and sorted using the left column.
>          def <=>(x)
>            self[left_col_name] <=> x[left_col_name]
>          end
> -        
> +
>          # Deprecated. Returns true if this is a root node.
>          def root?
>            parent_id = self[parent_col_name]
>            (parent_id == 0 || parent_id.nil?) && self[right_col_name] && self[left_col_name] && (self[right_col_name] > self[left_col_name])
>          end
> -        
> +
>          # Deprecated. Returns true if this is a child node
> -        def child?                          
> +        def child?
>            parent_id = self[parent_col_name]
>            !(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name])
>          end
> -        
> +
>          # Deprecated. Returns true if we have no idea what this is
>          def unknown?
>            !root? && !child?
>          end
> -        
> +
>          # Returns this record's root ancestor.
> -        def root
> +        def root(scope = {})
>            # the BETWEEN clause is needed to ensure we get the right virtual root, if using those
> -          base_set_class.find(:first, :conditions => "#{scope_condition} \
> -            AND (#{parent_col_name} IS NULL OR #{parent_col_name} = 0) AND (#{self[left_col_name]} BETWEEN #{left_col_name} AND #{right_col_name})")
> +          self.class.find_in_nested_set(:first, { :conditions => "#{scope_condition} \
> +            AND (#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0) AND (#{self[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name})" }, scope)
>          end
> -        
> +
>          # Returns the root or virtual roots of this record's tree (a tree cannot have more than one real root). See the explanation of virtual roots in the README.
> -        def roots
> -          base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{parent_col_name} IS NULL OR #{parent_col_name} = 0)", :order => "#{left_col_name}")
> +        def roots(scope = {})
> +          self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND (#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)", :order => "#{prefixed_left_col_name}" }, scope)
>          end
> -        
> +
>          # Returns this record's parent.
>          def parent
> -          base_set_class.find(self[parent_col_name]) if self[parent_col_name]
> +          self.class.find_in_nested_set(self[parent_col_name]) if self[parent_col_name]
>          end
> -        
> +
>          # Returns an array of all parents, starting with the root.
> -        def ancestors
> -          self_and_ancestors - [self]
> +        def ancestors(scope = {})
> +          self_and_ancestors(scope) - [self]
>          end
> -        
> +
>          # Returns an array of all parents plus self, starting with the root.
> -        def self_and_ancestors
> -          base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{left_col_name} AND #{right_col_name})", :order => left_col_name )
> +        def self_and_ancestors(scope = {})
> +          self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name})", :order => "#{prefixed_left_col_name}" }, scope)
>          end
> -        
> +
>          # Returns all the children of this node's parent, except self.
> -        def siblings
> -          self_and_siblings - [self]
> +        def siblings(scope = {})
> +          self_and_siblings(scope) - [self]
> +        end
> +
> +        # Returns all siblings to the left of self, in descending order, so the first sibling is the one closest to the left of self
> +        def previous_siblings(scope = {})
> +          self.class.find_in_nested_set(:all,
> +            { :conditions => ["#{scope_condition} AND #{sibling_condition} AND #{self.class.table_name}.id != ? AND #{prefixed_right_col_name} < ?", self.id, self[left_col_name]], :order => "#{prefixed_left_col_name} DESC" }, scope)
>          end
> -        
> +
> +        # Returns all siblings to the right of self, in ascending order, so the first sibling is the one closest to the right of self
> +        def next_siblings(scope = {})
> +          self.class.find_in_nested_set(:all,
> +            { :conditions => ["#{scope_condition} AND #{sibling_condition} AND #{self.class.table_name}.id != ? AND #{prefixed_left_col_name} > ?", self.id, self[right_col_name]], :order => "#{prefixed_left_col_name} ASC"}, scope)
> +        end
> +
> +        # Returns first siblings amongst it's siblings.
> +        def first_sibling(scope = {})
> +          self_and_siblings(scope.merge(:limit => 1, :order => "#{prefixed_left_col_name} ASC")).first
> +        end
> +
> +        def first_sibling?(scope = {})
> +          self == first_sibling(scope)
> +        end
> +        alias :first? :first_sibling?
> +
> +        # Returns last siblings amongst it's siblings.
> +        def last_sibling(scope = {})
> +          self_and_siblings(scope.merge(:limit => 1, :order => "#{prefixed_left_col_name} DESC")).first
> +        end
> +
> +        def last_sibling?(scope = {})
> +          self == last_sibling(scope)
> +        end
> +        alias :last? :last_sibling?
> +
> +        # Returns previous sibling of node or nil if there is none.
> +        def previous_sibling(num = 1, scope = {})
> +          scope[:limit] = num
> +          siblings = previous_siblings(scope)
> +          num == 1 ? siblings.first : siblings
> +        end
> +        alias :higher_item :previous_sibling
> +
> +        # Returns next sibling of node or nil if there is none.
> +        def next_sibling(num = 1, scope = {})
> +          scope[:limit] = num
> +          siblings = next_siblings(scope)
> +          num == 1 ? siblings.first : siblings
> +        end
> +        alias :lower_item :next_sibling
> +
>          # Returns all the children of this node's parent, including self.
> -        def self_and_siblings
> +        def self_and_siblings(scope = {})
>            if self[parent_col_name].nil? || self[parent_col_name].zero?
>              [self]
>            else
> -            base_set_class.find(:all, :conditions => "#{scope_condition} AND #{parent_col_name} = #{self[parent_col_name]}", :order => left_col_name)
> +            self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND #{sibling_condition}", :order => "#{prefixed_left_col_name}" }, scope)
>            end
>          end
> -        
> +
>          # Returns the level of this object in the tree, root level being 0.
> -        def level
> +        def level(scope = {})
>            return 0 if self[parent_col_name].nil?
> -          base_set_class.count(:conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{left_col_name} AND #{right_col_name})") - 1
> +          self.class.count_in_nested_set({ :conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name})" }, scope) - 1
>          end
> -        
> +
>          # Returns the number of nested children of this object.
> -        def all_children_count
> +        def all_children_count(scope = nil)
> +          return all_children(scope).length if scope.is_a?(Hash)
>            return (self[right_col_name] - self[left_col_name] - 1)/2
>          end
> -        
> +
>          # Returns itself and all nested children.
>          # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants.
> -        # in addition to the standard find opts
> -        def full_set(find_opts={})
> -          find_opts ||= {}
> -          exclude = find_opts.delete(:exclude)
> -          if exclude
> +        def full_set(scope = {})
> +          if exclude = scope.delete(:exclude)
>              exclude_str = " AND NOT (#{base_set_class.sql_for(exclude)}) "
>            elsif new_record? || self[right_col_name] - self[left_col_name] == 1
>              return [self]
>            end
> -          opts = base_set_class.merge_incoming_opts({:conditions => "#{scope_condition} #{exclude_str} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})",
> -                                       :order => left_col_name},
> -                                     find_opts)
> -          base_set_class.find(:all, opts)
> +          self.class.find_in_nested_set(:all, {
> +            :order => "#{prefixed_left_col_name}",
> +            :conditions => "#{scope_condition} #{exclude_str} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})"
> +          }, scope)
> +        end
> +
> +        # Returns the child for the requested id within the scope of its children, otherwise nil
> +        def child_by_id(id, scope = {})
> +          children_by_id(id, scope).first
> +        end
> +
> +        # Returns a child collection for the requested ids within the scope of its children, otherwise empty array
> +        def children_by_id(*args)
> +          scope = args.last.is_a?(Hash) ? args.pop : {}
> +          ids = args.flatten.compact.uniq
> +          self.class.find_in_nested_set(:all, {
> +            :conditions => ["#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{self.class.table_name}.#{self.class.primary_key} IN (?)", ids]
> +          }, scope)
>          end
> -        
> +
> +        # Returns the child for the requested id within the scope of its immediate children, otherwise nil
> +        def direct_child_by_id(id, scope = {})
> +          direct_children_by_id(id, scope).first
> +        end
> +
> +        # Returns a child collection for the requested ids within the scope of its immediate children, otherwise empty array
> +        def direct_children_by_id(*args)
> +          scope = args.last.is_a?(Hash) ? args.pop : {}
> +          ids = args.flatten.compact.uniq
> +          self.class.find_in_nested_set(:all, {
> +            :conditions => ["#{scope_condition} AND #{prefixed_parent_col_name} = #{self.id} AND #{self.class.table_name}.#{self.class.primary_key} IN (?)", ids]
> +          }, scope)
> +        end
> +
> +        # Tests wether self is within scope of parent
> +        def child_of?(parent, scope = {})
> +          if !scope.empty? && parent.respond_to?(:child_by_id)
> +            parent.child_by_id(self.id, scope).is_a?(self.class)
> +          else
> +            parent.respond_to?(left_col_name) && self[left_col_name] > parent[left_col_name] && self[right_col_name] < parent[right_col_name]
> +          end
> +        end
> +
> +        # Tests wether self is within immediate scope of parent
> +        def direct_child_of?(parent, scope = {})
> +          if !scope.empty? && parent.respond_to?(:direct_child_by_id)
> +            parent.direct_child_by_id(self.id, scope).is_a?(self.class)
> +          else
> +            parent.respond_to?(parent_col_name) && self[parent_col_name] == parent.id
> +          end
> +        end
> +
>          # Returns all children and nested children.
>          # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants.
> -        # in addition to the standard find opts
> -        def all_children(find_opts={})
> -          full_set(find_opts) - [self]
> +        def all_children(scope = {})
> +          full_set(scope) - [self]
>          end
> -        
> +
> +        def children_count(scope= {})
> +          self.class.count_in_nested_set({ :conditions => "#{scope_condition} AND #{prefixed_parent_col_name} = #{self.id}" }, scope)
> +        end
> +
>          # Returns this record's immediate children.
> -        def children(find_opts={})
> -          opts = base_set_class.merge_incoming_opts({:conditions => "#{scope_condition} AND #{parent_col_name} = #{self.id}",
> -                                      :order => left_col_name},
> -                                     find_opts)
> -          base_set_class.find(:all, opts)
> +        def children(scope = {})
> +          self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND #{prefixed_parent_col_name} = #{self.id}", :order => "#{prefixed_left_col_name}" }, scope)
>          end
> -        
> +
> +        def children?(scope = {})
> +          children_count(scope) > 0
> +        end
> +
>          # Deprecated
>          alias direct_children children
> -        
> +
>          # Returns this record's terminal children (nodes without children).
> -        def leaves
> -          base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{left_col_name} + 1 = #{right_col_name}", :order => left_col_name)
> +        def leaves(scope = {})
> +          self.class.find_in_nested_set(:all,
> +            { :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{prefixed_left_col_name} + 1 = #{prefixed_right_col_name}", :order => "#{prefixed_left_col_name}" }, scope)
>          end
> -        
> +
>          # Returns the count of this record's terminal children (nodes without children).
> -        def leaves_count
> -          base_set_class.count(:conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{left_col_name} + 1 = #{right_col_name}")
> +        def leaves_count(scope = {})
> +          self.class.count_in_nested_set({ :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{prefixed_left_col_name} + 1 = #{prefixed_right_col_name}" }, scope)
> +        end
> +
> +        # All nodes between two nodes, those nodes included
> +        # in effect all ancestors until the other is reached
> +        def ancestors_and_self_through(other, scope = {})
> +          first, last = [self, other].sort
> +          self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND (#{last[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name}) AND #{prefixed_left_col_name} >= #{first[left_col_name]}",
> +            :order => "#{prefixed_left_col_name}" }, scope)
> +        end
> +
> +        # Ancestors until the other is reached - excluding self
> +        def ancestors_through(other, scope = {})
> +          ancestors_and_self_through(other, scope) - [self]
> +        end
> +
> +        # All children until the other is reached - excluding self
> +        def all_children_through(other, scope = {})
> +          full_set_through(other, scope) - [self]
> +        end
> +
> +        # All children until the other is reached - including self
> +        def full_set_through(other, scope = {})
> +          first, last = [self, other].sort
> +          self.class.find_in_nested_set(:all,
> +            { :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{first[left_col_name]} AND #{first[right_col_name]}) AND #{prefixed_left_col_name} <= #{last[left_col_name]}", :order => "#{prefixed_left_col_name}" }, scope)
>          end
> -        
> -        # Checks the left/right indexes of one node and all descendants. 
> +
> +        # All siblings until the other is reached - including self
> +        def self_and_siblings_through(other, scope = {})
> +          if self[parent_col_name].nil? || self[parent_col_name].zero?
> +            [self]
> +          else
> +            first, last = [self, other].sort
> +            self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND #{sibling_condition} AND (#{prefixed_left_col_name} BETWEEN #{first[left_col_name]} AND #{last[right_col_name]})", :order => "#{prefixed_left_col_name}" }, scope)
> +          end
> +        end
> +
> +        # All siblings until the other is reached - excluding self
> +        def siblings_through(other, scope = {})
> +          self_and_siblings_through(other, scope) - [self]
> +        end
> +
> +        # Checks the left/right indexes of one node and all descendants.
>          # Throws ActiveRecord::ActiveRecordError if it finds a problem.
>          def check_subtree
>            transaction do
> @@ -314,8 +708,8 @@ module SymetrieCom
>              check # this method is implemented via #check, so that we don't generate lots of unnecessary nested transactions
>            end
>          end
> -        
> -        # Checks the left/right indexes of the entire tree that this node belongs to, 
> +
> +        # Checks the left/right indexes of the entire tree that this node belongs to,
>          # returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem.
>          # This method is needed because check_subtree alone cannot find gaps between virtual roots, orphaned nodes or endless loops.
>          def check_full_tree
> @@ -323,7 +717,7 @@ module SymetrieCom
>            transaction do
>              # virtual roots make this method more complex than it otherwise would be
>              n = 1
> -            roots.each do |r| 
> +            roots.each do |r|
>                raise ActiveRecord::ActiveRecordError, "Gaps between roots in the tree containing record ##{r.id}" if r[left_col_name] != n
>                r.check_subtree
>                n = r[right_col_name] + 1
> @@ -335,14 +729,14 @@ module SymetrieCom
>            end
>            return total_nodes
>          end
> -        
> +
>          # Re-calculate the left/right values of all nodes in this record's tree. Can be used to convert an ordinary tree into a nested set.
>          def renumber_full_tree
>            indexes = []
>            n = 1
>            transaction do
>              for r in roots # because we may have virtual roots
> -              n = r.calc_numbers(n, indexes)
> +              n = 1 + r.calc_numbers(n, indexes)
>              end
>              for i in indexes
>                base_set_class.update_all("#{left_col_name} = #{i[:lft]}, #{right_col_name} = #{i[:rgt]}", "#{self.class.primary_key} = #{i[:id]}")
> @@ -350,7 +744,7 @@ module SymetrieCom
>            end
>            ## reload?
>          end
> -        
> +
>          # Deprecated. Adds a child to this object in the tree.  If this object hasn't been initialized,
>          # it gets set up as a root node.
>          #
> @@ -391,7 +785,7 @@ module SymetrieCom
>                                        scope_condition)
>                child.reload
>              end
> -            
> +
>              child.move_to_child_of(self)
>              # self.reload ## even though move_to calls target.reload, at least one object in the tests was not reloading (near the end of test_common_usage)
>            end
> @@ -405,10 +799,10 @@ module SymetrieCom
>          #     # Looks like we're now the root node!  Woo
>          #     self[left_col_name] = 1
>          #     self[right_col_name] = 4
> -        #     
> +        #
>          #     # What do to do about validation?
>          #     return nil unless self.save
> -        #     
> +        #
>          #     child[parent_col_name] = self.id
>          #     child[left_col_name] = 2
>          #     child[right_col_name]= 3
> @@ -429,99 +823,197 @@ module SymetrieCom
>          #   end
>          # end
>          end
> -        
> +
> +        # Insert a node at a specific position among the children of target.
> +        def insert_at(target, index = :last, scope = {})
> +          level_nodes = target.children(scope)
> +          current_index = level_nodes.index(self)
> +          last_index = level_nodes.length - 1
> +          as_first = (index == :first)
> +          as_last  = (index == :last || (index.is_a?(Fixnum) && index > last_index))
> +          index = 0 if as_first
> +          index = last_index if as_last
> +          if last_index < 0
> +            move_to_child_of(target)
> +          elsif index >= 0 && index <= last_index && level_nodes[index]
> +            if as_last && index != current_index
> +              move_to_right_of(level_nodes[index])
> +            elsif (as_first || index == 0) && index != current_index
> +              move_to_left_of(level_nodes[index])
> +            elsif !current_index.nil? && index > current_index
> +              move_to_right_of(level_nodes[index])
> +            elsif !current_index.nil? && index < current_index
> +              move_to_left_of(level_nodes[index])
> +            elsif current_index.nil?
> +              move_to_left_of(level_nodes[index])
> +            end
> +          end
> +        end
> +
>          # Move this node to the left of _target_ (you can pass an object or just an id).
>          # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.
>          def move_to_left_of(target)
>            self.move_to target, :left
>          end
> -        
> +
>          # Move this node to the right of _target_ (you can pass an object or just an id).
>          # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.
>          def move_to_right_of(target)
>            self.move_to target, :right
>          end
> -        
> +
>          # Make this node a child of _target_ (you can pass an object or just an id).
>          # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.
>          def move_to_child_of(target)
>            self.move_to target, :child
>          end
> -        
> +
> +        # Moves a node to a certain position amongst its siblings.
> +        def move_to_position(index, scope = {})
> +          insert_at(self.parent, index, scope)
> +        end
> +
> +        # Moves a node one up amongst its siblings. Does nothing if it's already
> +        # the first sibling.
> +        def move_lower
> +          next_sib = next_sibling
> +          move_to_right_of(next_sib) if next_sib
> +        end
> +
> +        # Moves a node one down amongst its siblings. Does nothing if it's already
> +        # the last sibling.
> +        def move_higher
> +          prev_sib = previous_sibling
> +          move_to_left_of(prev_sib) if prev_sib
> +        end
> +
> +        # Moves a node one to be the first amongst its siblings. Does nothing if it's already
> +        # the first sibling.
> +        def move_to_top
> +          first_sib = first_sibling
> +          move_to_left_of(first_sib) if first_sib && self != first_sib
> +        end
> +
> +        # Moves a node one to be the last amongst its siblings. Does nothing if it's already
> +        # the last sibling.
> +        def move_to_bottom
> +          last_sib = last_sibling
> +          move_to_right_of(last_sib) if last_sib && self != last_sib
> +        end
> +
> +        # Swaps the position of two sibling nodes preserving a sibling's descendants.
> +        # The current implementation only works amongst siblings.
> +        def swap(target, transact = true)
> +          move_to(target, :swap, transact)
> +        end
> +
> +        # Reorder children according to an array of ids
> +        def reorder_children(*ids)
> +          transaction do
> +            ordered_ids = ids.flatten.uniq
> +            current_children = children({ :conditions => { :id => ordered_ids } })
> +            current_children_ids = current_children.map(&:id)
> +            ordered_ids = ordered_ids & current_children_ids
> +            return [] unless ordered_ids.length > 1 && ordered_ids != current_children_ids
> +            perform_reorder_of_children(ordered_ids, current_children)
> +          end
> +        end
> +
>          protected
> -        def move_to(target, position) #:nodoc:
> +        def move_to(target, position, transact = true) #:nodoc:
>            raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if new_record?
>            raise ActiveRecord::ActiveRecordError, "You cannot move a node if left or right is nil" unless self[left_col_name] && self[right_col_name]
> -          
> -          transaction do
> -            self.reload # the lft/rgt values could be stale (target is reloaded below)
> +
> +          with_optional_transaction(transact) do
> +            self.reload(:select => "#{left_col_name}, #{right_col_name}, #{parent_col_name}") # the lft/rgt values could be stale (target is reloaded below)
>              if target.is_a?(base_set_class)
> -              target.reload # could be stale
> +              target.reload(:select => "#{left_col_name}, #{right_col_name}, #{parent_col_name}") # could be stale
>              else
> -              target = base_set_class.find(target) # load object if we were given an ID
> +              target = self.class.find_in_nested_set(target) # load object if we were given an ID
>              end
> -            
> +
>              if (target[left_col_name] >= self[left_col_name]) && (target[right_col_name] <= self[right_col_name])
>                raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
>              end
> -            
> +
>              # prevent moves between different trees
>              if target.scope_condition != scope_condition
>                raise ActiveRecord::ActiveRecordError, "Scope conditions do not match. Is the target in the same tree?"
>              end
> -            
> -            # the move: we just need to define two adjoining segments of the left/right index and swap their positions
> -            bound = case position
> -              when :child then target[right_col_name]
> -              when :left  then target[left_col_name]
> -              when :right then target[right_col_name] + 1
> -              else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left or :right ('#{position}' received)."
> -            end
> -            
> -            if bound > self[right_col_name]
> -              bound = bound - 1
> -              other_bound = self[right_col_name] + 1
> -            else
> -              other_bound = self[left_col_name] - 1
> -            end
> -            
> -            return if bound == self[right_col_name] || bound == self[left_col_name] # there would be no change, and other_bound is now wrong anyway
> -            
> -            # we have defined the boundaries of two non-overlapping intervals, 
> -            # so sorting puts both the intervals and their boundaries in order
> -            a, b, c, d = [self[left_col_name], self[right_col_name], bound, other_bound].sort
> -            
> -            # change nil to NULL for new parent
> -            if position == :child
> -              new_parent = target.id
> +
> +            if position == :swap
> +              unless self.siblings.include?(target)
> +                raise ActiveRecord::ActiveRecordError, "Impossible move, target node should be a sibling."
> +              end
> +
> +              direction = (self[left_col_name] < target[left_col_name]) ? :down : :up
> +
> +              i0 = (direction == :up) ? target[left_col_name] : self[left_col_name]
> +              i1 = (direction == :up) ? target[right_col_name] : self[right_col_name]
> +              i2 = (direction == :up) ? self[left_col_name] : target[left_col_name]
> +              i3 = (direction == :up) ? self[right_col_name] : target[right_col_name]
> +
> +              base_set_class.update_all(%[
> +                #{left_col_name} = CASE WHEN #{left_col_name} BETWEEN #{i0} AND #{i1} THEN #{i3} + #{left_col_name} - #{i1}
> +                  WHEN #{left_col_name} BETWEEN #{i2} AND #{i3} THEN #{i0} + #{left_col_name} - #{i2}
> +                  ELSE #{i0} + #{i3} + #{left_col_name} - #{i1} - #{i2} END,
> +                  #{right_col_name} = CASE WHEN #{right_col_name} BETWEEN #{i0} AND #{i1} THEN #{i3} + #{right_col_name} - #{i1}
> +                  WHEN #{right_col_name} BETWEEN #{i2} AND #{i3} THEN #{i0} + #{right_col_name} - #{i2}
> +                  ELSE #{i0} + #{i3} + #{right_col_name} - #{i1} - #{i2} END ], "#{left_col_name} BETWEEN #{i0} AND #{i3} AND #{i0} < #{i1} AND #{i1} < #{i2} AND #{i2} < #{i3} AND #{scope_condition}")
>              else
> -              new_parent = target[parent_col_name].nil? ? 'NULL' : target[parent_col_name]
> -            end
> -            
> -            base_set_class.update_all("\
> -              #{left_col_name} = CASE \
> -                WHEN #{left_col_name} BETWEEN #{a} AND #{b} THEN #{left_col_name} + #{d - b} \
> -                WHEN #{left_col_name} BETWEEN #{c} AND #{d} THEN #{left_col_name} + #{a - c} \
> -                ELSE #{left_col_name} END, \
> -              #{right_col_name} = CASE \
> -                WHEN #{right_col_name} BETWEEN #{a} AND #{b} THEN #{right_col_name} + #{d - b} \
> -                WHEN #{right_col_name} BETWEEN #{c} AND #{d} THEN #{right_col_name} + #{a - c} \
> -                ELSE #{right_col_name} END, \
> -              #{parent_col_name} = CASE \
> -                WHEN #{self.class.primary_key} = #{self.id} THEN #{new_parent} \
> -                ELSE #{parent_col_name} END",
> -              scope_condition)
> -            self.reload
> -            target.reload
> +              # the move: we just need to define two adjoining segments of the left/right index and swap their positions
> +              bound = case position
> +                when :child then target[right_col_name]
> +                when :left  then target[left_col_name]
> +                when :right then target[right_col_name] + 1
> +                else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left or :right ('#{position}' received)."
> +              end
> +
> +              if bound > self[right_col_name]
> +                bound = bound - 1
> +                other_bound = self[right_col_name] + 1
> +              else
> +                other_bound = self[left_col_name] - 1
> +              end
> +
> +              return if bound == self[right_col_name] || bound == self[left_col_name] # there would be no change, and other_bound is now wrong anyway
> +
> +              # we have defined the boundaries of two non-overlapping intervals,
> +              # so sorting puts both the intervals and their boundaries in order
> +              a, b, c, d = [self[left_col_name], self[right_col_name], bound, other_bound].sort
> +
> +              # change nil to NULL for new parent
> +              if position == :child
> +                new_parent = target.id
> +              else
> +                new_parent = target[parent_col_name].nil? ? 'NULL' : target[parent_col_name]
> +              end
> +
> +              base_set_class.update_all("\
> +                #{left_col_name} = CASE \
> +                  WHEN #{left_col_name} BETWEEN #{a} AND #{b} THEN #{left_col_name} + #{d - b} \
> +                  WHEN #{left_col_name} BETWEEN #{c} AND #{d} THEN #{left_col_name} + #{a - c} \
> +                  ELSE #{left_col_name} END, \
> +                #{right_col_name} = CASE \
> +                  WHEN #{right_col_name} BETWEEN #{a} AND #{b} THEN #{right_col_name} + #{d - b} \
> +                  WHEN #{right_col_name} BETWEEN #{c} AND #{d} THEN #{right_col_name} + #{a - c} \
> +                  ELSE #{right_col_name} END, \
> +                #{parent_col_name} = CASE \
> +                  WHEN #{self.class.primary_key} = #{self.id} THEN #{new_parent} \
> +                  ELSE #{parent_col_name} END",
> +                scope_condition)
> +            end
> +            self.reload(:select => "#{left_col_name}, #{right_col_name}, #{parent_col_name}")
> +            target.reload(:select => "#{left_col_name}, #{right_col_name}, #{parent_col_name}")
>            end
>          end
> -        
> +
>          def check #:nodoc:
>            # performance improvements (3X or more for tables with lots of columns) by using :select to load just id, lft and rgt
>            ## i don't use the scope condition here, because it shouldn't be needed
> -          my_children = base_set_class.find(:all, :conditions => "#{parent_col_name} = #{self.id}",
> -            :order => left_col_name, :select => "#{self.class.primary_key}, #{left_col_name}, #{right_col_name}")
> -          
> +          my_children = self.class.find_in_nested_set(:all, :conditions => "#{prefixed_parent_col_name} = #{self.id}",
> +            :order => "#{prefixed_left_col_name}", :select => "#{self.class.primary_key}, #{prefixed_left_col_name}, #{prefixed_right_col_name}")
> +
>            if my_children.empty?
>              unless self[left_col_name] && self[right_col_name]
>                raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{self.id}.#{right_col_name} or #{left_col_name} is blank"
> @@ -546,14 +1038,14 @@ module SymetrieCom
>              end
>            end
>          end
> -        
> +
>          # used by the renumbering methods
>          def calc_numbers(n, indexes) #:nodoc:
>            my_lft = n
>            # performance improvements (3X or more for tables with lots of columns) by using :select to load just id, lft and rgt
>            ## i don't use the scope condition here, because it shouldn't be needed
> -          my_children = base_set_class.find(:all, :conditions => "#{parent_col_name} = #{self.id}",
> -            :order => left_col_name, :select => "#{self.class.primary_key}, #{left_col_name}, #{right_col_name}")
> +          my_children = self.class.find_in_nested_set(:all, :conditions => "#{prefixed_parent_col_name} = #{self.id}",
> +            :order => "#{prefixed_left_col_name}", :select => "#{self.class.primary_key}, #{prefixed_left_col_name}, #{prefixed_right_col_name}")
>            if my_children.empty?
>              my_rgt = (n += 1)
>            else
> @@ -565,35 +1057,52 @@ module SymetrieCom
>            indexes << {:id => self.id, :lft => my_lft, :rgt => my_rgt} unless self[left_col_name] == my_lft && self[right_col_name] == my_rgt
>            return n
>          end
> -        
> -        
> -        
> +
> +        # Actually perform the ordering using calculated steps
> +        def perform_reorder_of_children(ordered_ids, current)
> +          steps = calculate_reorder_steps(ordered_ids, current)
> +          steps.inject([]) do |result, (source, idx)|
> +            target = current[idx]
> +            if source.id != target.id
> +              source.swap(target, false)
> +              from = current.index(source)
> +              current[from], current[idx] = current[idx], current[from]
> +              result << source
> +            end
> +            result
> +          end
> +        end
> +
> +        # Calculate the least amount of swap steps to achieve the requested order
> +        def calculate_reorder_steps(ordered_ids, current)
> +          steps = []
> +          current.each_with_index do |source, idx|
> +            new_idx = ordered_ids.index(source.id)
> +            steps << [source, new_idx] if idx != new_idx
> +          end
> +          steps
> +        end
> +
>          # The following code is my crude method of making things concurrency-safe.
>          # Basically, we need to ensure that whenever a record is saved, the lft/rgt
>          # values are _not_ written to the database, because if any changes to the tree
> -        # structure occurrred since the object was loaded, the lft/rgt values could 
> -        # be out of date and corrupt the indexes. 
> -        # I hope that someone with a little more ruby-foo can look at this and come
> -        # up with a more elegant solution.
> -        private
> -          # override ActiveRecord to prevent lft/rgt values from being saved (can corrupt indexes under concurrent usage)
> -          def update #:nodoc:
> -            connection.update(
> -              "UPDATE #{self.class.table_name} " +
> -              "SET #{quoted_comma_pair_list(connection, special_attributes_with_quotes(false))} " +
> -              "WHERE #{self.class.primary_key} = #{quote_value(id)}",
> -              "#{self.class.name} Update"
> -            )
> -          end
> +        # structure occurrred since the object was loaded, the lft/rgt values could
> +        # be out of date and corrupt the indexes.
> +        # There is an open ticket for this in the Rails Core: http://dev.rubyonrails.org/ticket/6896
>  
> -          # exclude the lft/rgt columns from update statements
> -          def special_attributes_with_quotes(include_primary_key = true) #:nodoc:
> -            attributes.inject({}) do |quoted, (name, value)|
> +        private
> +          # override the sql preparation method to exclude the lft/rgt columns
> +          # under the same conditions that the primary key column is excluded
> +          def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys) #:nodoc:
> +            left_and_right_column = [acts_as_nested_set_options[:left_column], acts_as_nested_set_options[:right_column]]
> +            quoted = {}
> +            connection = self.class.connection
> +            attribute_names.each do |name|
>                if column = column_for_attribute(name)
> -                quoted[name] = quote_value(value, column) unless (!include_primary_key && column.primary) || [acts_as_nested_set_options[:left_column], acts_as_nested_set_options[:right_column]].include?(column.name)
> +                quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && (column.primary || left_and_right_column.include?(column.name))
>                end
> -              quoted
>              end
> +            include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
>            end
>  
>            # i couldn't figure out how to call attributes_with_quotes without cutting and pasting this private method in.  :(
> @@ -602,9 +1111,17 @@ module SymetrieCom
>              self.class.connection.quote(value, column)
>            end
>  
> +          # optionally use a transaction
> +          def with_optional_transaction(bool, &block)
> +            bool ? transaction { yield } : yield
> +          end
> +
> +          # as a seperate method to facilitate custom implementations based on :dependent option
> +          def remove_descendant(descendant)
> +            descendant.destroy
> +          end
> +
>        end
>      end
>    end
>  end
> -
> -
> diff --git a/src/vendor/plugins/betternestedset/test/abstract_unit.rb b/src/vendor/plugins/betternestedset/test/abstract_unit.rb
> index 3d7bd87..a8692da 100644
> --- a/src/vendor/plugins/betternestedset/test/abstract_unit.rb
> +++ b/src/vendor/plugins/betternestedset/test/abstract_unit.rb
> @@ -1,12 +1,6 @@
>  ENV['RAILS_ENV'] = 'test'
>  require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
>  require 'test_help'
> -require File.dirname(__FILE__) + '/../init'
> -
> -
> -ActiveRecord::Base.class_eval do
> -  include SymetrieCom::Acts::NestedSet
> -end
>  
>  config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
>  ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/debug.log')
> diff --git a/src/vendor/plugins/betternestedset/test/acts_as_nested_set_test.rb b/src/vendor/plugins/betternestedset/test/acts_as_nested_set_test.rb
> index 144a83d..0cd1dfb 100644
> --- a/src/vendor/plugins/betternestedset/test/acts_as_nested_set_test.rb
> +++ b/src/vendor/plugins/betternestedset/test/acts_as_nested_set_test.rb
> @@ -4,140 +4,154 @@ require 'pp'
>  
>  class MixinNestedSetTest < Test::Unit::TestCase
>    fixtures :mixins
> -  
> +
> +  def setup
> +    # force so other tests besides test_destroy_dependent aren't affected
> +    NestedSetWithStringScope.acts_as_nested_set_options[:dependent] = :delete_all
> +  end
> +
>    ##########################################
>    # HIGH LEVEL TESTS
>    ##########################################
>    def test_mixing_in_methods
>      ns = NestedSet.new
>      assert(ns.respond_to?(:all_children)) # test a random method
> -    
> +
>      check_method_mixins(ns)
> -    check_deprecated_method_mixins(ns) 
> +    check_deprecated_method_mixins(ns)
>      check_class_method_mixins(NestedSet)
>    end
> -  
> +
>    def check_method_mixins(obj)
> -    [:<=>, :all_children, :all_children_count, :ancestors, :before_create, :before_destroy, :check_full_tree, 
> -    :check_subtree, :children, :full_set, :leaves, :leaves_count, :left_col_name, :level, :move_to_child_of, 
> -    :move_to_left_of, :move_to_right_of, :parent, :parent_col_name, :renumber_full_tree, :right_col_name, 
> +    [:<=>, :all_children, :all_children_count, :ancestors, :before_create, :before_destroy, :check_full_tree,
> +    :check_subtree, :children, :children_count, :full_set, :leaves, :leaves_count, :left_col_name, :level, :move_to_child_of,
> +    :move_to_left_of, :move_to_right_of, :parent, :parent_col_name, :renumber_full_tree, :right_col_name,
>      :root, :roots, :self_and_ancestors, :self_and_siblings, :siblings].each { |symbol| assert(obj.respond_to?(symbol)) }
>    end
> -  
> +
>    def check_deprecated_method_mixins(obj)
>      [:add_child, :direct_children, :parent_column, :root?, :child?, :unknown?].each { |symbol| assert(obj.respond_to?(symbol)) }
>    end
> -  
> +
>    def check_class_method_mixins(klass)
>      [:root, :roots, :check_all, :renumber_all].each { |symbol| assert(klass.respond_to?(symbol)) }
>    end
> -  
> +
>    def test_string_scope
>      ns = NestedSet.new
> -    assert_equal("root_id IS NULL", ns.scope_condition)
> -    
> +    assert_equal("mixins.root_id IS NULL", ns.scope_condition)
> +
>      ns = NestedSetWithStringScope.new
>      ns.root_id = 1
> -    assert_equal("root_id = 1", ns.scope_condition)
> +    assert_equal("mixins.root_id = 1", ns.scope_condition)
>      ns.root_id = 42
> -    assert_equal("root_id = 42", ns.scope_condition)
> +    assert_equal("mixins.root_id = 42", ns.scope_condition)
>      check_method_mixins ns
>    end
> -  
> +
> +  def test_without_scope_condition
> +    ns = NestedSet.new
> +    assert_equal("mixins.root_id IS NULL", ns.scope_condition)
> +    NestedSet.without_scope_condition do
> +      assert_equal("(1 = 1)", ns.scope_condition)
> +    end
> +    assert_equal("mixins.root_id IS NULL", ns.scope_condition)
> +  end
> +
>    def test_symbol_scope
>      ns = NestedSetWithSymbolScope.new
>      ns.root_id = 1
> -    assert_equal("root_id = 1", ns.scope_condition)
> +    assert_equal("mixins.root_id = 1", ns.scope_condition)
>      ns.root_id = 42
> -    assert_equal("root_id = 42", ns.scope_condition)
> +    assert_equal("mixins.root_id = 42", ns.scope_condition)
>      check_method_mixins ns
>    end
> -  
> +
>    def test_protected_attributes
>      ns = NestedSet.new(:parent_id => 2, :lft => 3, :rgt => 2)
>      [:parent_id, :lft, :rgt].each {|symbol| assert_equal(nil, ns.send(symbol))}
>    end
> -    
> +
>    def test_really_protected_attributes
>      ns = NestedSet.new
>      assert_raise(ActiveRecord::ActiveRecordError) {ns.parent_id = 1}
>      assert_raise(ActiveRecord::ActiveRecordError) {ns.lft = 1}
>      assert_raise(ActiveRecord::ActiveRecordError) {ns.rgt = 1}
>    end
> -  
> +
>    ##########################################
>    # CLASS METHOD TESTS
>    ##########################################
>    def test_class_root
>      NestedSetWithStringScope.roots.each {|r| r.destroy unless r.id == 4001}
> -    assert_equal(NestedSetWithStringScope.find(4001), NestedSetWithStringScope.root)
> +    assert_equal([NestedSetWithStringScope.find(4001)], NestedSetWithStringScope.roots)
>      NestedSetWithStringScope.find(4001).destroy
>      assert_equal(nil, NestedSetWithStringScope.root)
>      ns = NestedSetWithStringScope.create(:root_id => 2)
>      assert_equal(ns, NestedSetWithStringScope.root)
>    end
> -  
> +
>    def test_class_root_again
>      NestedSetWithStringScope.roots.each {|r| r.destroy unless r.id == 101}
>      assert_equal(NestedSetWithStringScope.find(101), NestedSetWithStringScope.root)
>    end
> -  
> +
>    def test_class_roots
>      assert_equal(2, NestedSetWithStringScope.roots.size)
>      assert_equal(10, NestedSet.roots.size) # May change if STI behavior changes
>    end
> -  
> +
>    def test_check_all_1
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>      NestedSetWithStringScope.update_all("lft = 3", "id = 103")
>      assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_check_all_2
>      NestedSetWithStringScope.update_all("lft = lft + 1", "lft > 11 AND root_id = 101")
>      NestedSetWithStringScope.update_all("rgt = rgt + 1", "lft > 11 AND root_id = 101")
> -    assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all} 
> +    assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_check_all_3
>      NestedSetWithStringScope.update_all("lft = lft + 2", "lft > 11 AND root_id = 101")
>      NestedSetWithStringScope.update_all("rgt = rgt + 2", "lft > 11 AND root_id = 101")
> -    assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all} 
> +    assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_check_all_4
>      ns = NestedSetWithStringScope.create(:root_id => 101) # virtual root
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>      NestedSetWithStringScope.update_all("rgt = rgt + 2, lft = lft + 2", "id = #{ns.id}") # create a gap between virtual roots
>      assert_nothing_raised {ns.check_subtree}
> -    assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all} 
> +    assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_renumber_all
>      NestedSetWithStringScope.update_all("lft = NULL, rgt = NULL")
>      assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
> -    NestedSetWithStringScope.renumber_all    
> +    NestedSetWithStringScope.renumber_all
>      assert_nothing_raised(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
>      NestedSetWithStringScope.update_all("lft = 1, rgt = 2")
>      assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
> -    NestedSetWithStringScope.renumber_all    
> +    NestedSetWithStringScope.renumber_all
>      assert_nothing_raised(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_sql_for
>      assert_equal("1 != 1", Category.sql_for([]))
>      c = Category.new
>      assert_equal("1 != 1", Category.sql_for(c))
>      assert_equal("1 != 1", Category.sql_for([c]))
>      c.save
> -    assert_equal("((lft BETWEEN 1 AND 2))", Category.sql_for(c))
> -    assert_equal("((lft BETWEEN 1 AND 2))", Category.sql_for([c]))
> -    assert_equal("((lft BETWEEN 1 AND 20))", NestedSetWithStringScope.sql_for(101))
> -    assert_equal("((lft BETWEEN 1 AND 20) OR (lft BETWEEN 4 AND 11))", NestedSetWithStringScope.sql_for([101, set2(3)]))
> -    assert_equal("((lft BETWEEN 5 AND 6) OR (lft BETWEEN 7 AND 8) OR (lft BETWEEN 9 AND 10))", NestedSetWithStringScope.sql_for(set2(3).children))
> -  end
> -  
> -  
> +    assert_equal("((mixins.lft BETWEEN 1 AND 2))", Category.sql_for(c))
> +    assert_equal("((mixins.lft BETWEEN 1 AND 2))", Category.sql_for([c]))
> +    assert_equal("((mixins.lft BETWEEN 1 AND 20))", NestedSetWithStringScope.sql_for(101))
> +    assert_equal("((mixins.lft BETWEEN 1 AND 20) OR (mixins.lft BETWEEN 4 AND 11))", NestedSetWithStringScope.sql_for([101, set2(3)]))
> +    assert_equal("((mixins.lft BETWEEN 5 AND 6) OR (mixins.lft BETWEEN 7 AND 8) OR (mixins.lft BETWEEN 9 AND 10))", NestedSetWithStringScope.sql_for(set2(3).children))
> +  end
> +
> +
>    ##########################################
>    # CALLBACK TESTS
>    ##########################################
> @@ -150,22 +164,22 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal(3, ns.lft)
>      assert_equal(4, ns.rgt)
>    end
> -  
> +
>    # test pruning a branch. only works if we allow the deletion of nodes with children
>    def test_destroy
>      big_tree = NestedSetWithStringScope.find(4001)
> -    
> +
>      # Make sure we have the right one
>      assert_equal(3, big_tree.direct_children.length)
>      assert_equal(10, big_tree.full_set.length)
> -    
> +
>      NestedSetWithStringScope.find(4005).destroy
>  
>      big_tree = NestedSetWithStringScope.find(4001)
> -    
> +
>      assert_equal(7, big_tree.full_set.length)
>      assert_equal(2, big_tree.direct_children.length)
> -    
> +
>      assert_equal(1, NestedSetWithStringScope.find(4001).lft)
>      assert_equal(2, NestedSetWithStringScope.find(4002).lft)
>      assert_equal(3, NestedSetWithStringScope.find(4003).lft)
> @@ -181,10 +195,10 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal(13, NestedSetWithStringScope.find(4008).rgt)
>      assert_equal(14, NestedSetWithStringScope.find(4001).rgt)
>    end
> -  
> +
>    def test_destroy_2
>      assert_nothing_raised {set2(1).check_subtree}
> -    assert set2(10).destroy    
> +    assert set2(10).destroy
>      assert_nothing_raised {set2(1).reload.check_subtree}
>      assert set2(9).children.empty?
>      assert set2(9).destroy
> @@ -192,7 +206,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_nothing_raised {set2(1).reload.check_subtree}
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_destroy_3
>      assert set2(3).destroy
>      assert_equal(2, set2(1).children.size)
> @@ -203,35 +217,67 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal(12, set2(1).rgt)
>      assert_nothing_raised {set2(1).check_subtree}
>    end
> -  
> +
>    def test_destroy_root
>      NestedSetWithStringScope.find(4001).destroy
>      assert_equal(0, NestedSetWithStringScope.count(:conditions => "root_id = 42"))
> -  end            
> -  
> +  end
> +
> +  def test_destroy_dependent
> +    NestedSetWithStringScope.acts_as_nested_set_options[:dependent] = :destroy
> +
> +    big_tree = NestedSetWithStringScope.find(4001)
> +
> +    # Make sure we have the right one
> +    assert_equal(3, big_tree.direct_children.length)
> +    assert_equal(10, big_tree.full_set.length)
> +
> +    NestedSetWithStringScope.find(4005).destroy
> +
> +    big_tree = NestedSetWithStringScope.find(4001)
> +
> +    assert_equal(7, big_tree.full_set.length)
> +    assert_equal(2, big_tree.direct_children.length)
> +
> +    assert_equal(1, NestedSetWithStringScope.find(4001).lft)
> +    assert_equal(2, NestedSetWithStringScope.find(4002).lft)
> +    assert_equal(3, NestedSetWithStringScope.find(4003).lft)
> +    assert_equal(4, NestedSetWithStringScope.find(4003).rgt)
> +    assert_equal(5, NestedSetWithStringScope.find(4004).lft)
> +    assert_equal(6, NestedSetWithStringScope.find(4004).rgt)
> +    assert_equal(7, NestedSetWithStringScope.find(4002).rgt)
> +    assert_equal(8, NestedSetWithStringScope.find(4008).lft)
> +    assert_equal(9, NestedSetWithStringScope.find(4009).lft)
> +    assert_equal(10, NestedSetWithStringScope.find(4009).rgt)
> +    assert_equal(11, NestedSetWithStringScope.find(4010).lft)
> +    assert_equal(12, NestedSetWithStringScope.find(4010).rgt)
> +    assert_equal(13, NestedSetWithStringScope.find(4008).rgt)
> +    assert_equal(14, NestedSetWithStringScope.find(4001).rgt)
> +  end
> +
>    ##########################################
>    # QUERY METHOD TESTS
>    ##########################################
>    def set(id) NestedSet.find(3000 + id) end # helper method
> -  
> +
>    def set2(id) NestedSetWithStringScope.find(100 + id) end # helper method
> -  
> +
>    def test_root?
>      assert NestedSetWithStringScope.find(4001).root?
>      assert !NestedSetWithStringScope.find(4002).root?
>    end
> -  
> +
>    def test_child?
>      assert !NestedSetWithStringScope.find(4001).child?
> -    assert NestedSetWithStringScope.find(4002).child?    
> +    assert NestedSetWithStringScope.find(4002).child?
>    end
> -  
> +
>    # Deprecated, delete this test when we nuke the method
>    def test_unknown?
>      assert !NestedSetWithStringScope.find(4001).unknown?
> -    assert !NestedSetWithStringScope.find(4002).unknown?        
> +    assert !NestedSetWithStringScope.find(4002).unknown?
>    end
> -  
> +
>    # Test the <=> method implicitly
>    def test_comparison
>      ar = NestedSetWithStringScope.find(:all, :conditions => "root_id = 42", :order => "lft")
> @@ -239,7 +285,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_not_equal(ar, ar2)
>      assert_equal(ar, ar2.sort)
>    end
> -  
> +
>    def test_root
>      assert_equal(NestedSetWithStringScope.find(4001), NestedSetWithStringScope.find(4007).root)
>      assert_equal(set2(1), set2(8).root)
> @@ -249,13 +295,13 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      c3.move_to_child_of(c2)
>      assert_equal(c2, c3.root)
>    end
> -  
> +
>    def test_roots
>      assert_equal([set2(1)], set2(8).roots)
>      assert_equal([set2(1)], set2(1).roots)
>      assert_equal(NestedSet.find(:all, :conditions => "id > 3000 AND id < 4000").size, set(1).roots.size)
>    end
> -  
> +
>    def test_parent
>      ns = NestedSetWithStringScope.create(:root_id => 45)
>      assert_equal(nil, ns.parent)
> @@ -264,40 +310,80 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal(set2(1), set2(2).parent)
>      assert_equal(set2(3), set2(7).parent)
>    end
> -  
> +
>    def test_ancestors
>      assert_equal([], set2(1).ancestors)
>      assert_equal([set2(1), set2(4), set2(9)], set2(10).ancestors)
>    end
> -  
> +
>    def test_self_and_ancestors
>      assert_equal([set2(1)], set2(1).self_and_ancestors)
>      assert_equal([set2(1), set2(4), set2(8)], set2(8).self_and_ancestors)
>      assert_equal([set2(1), set2(4), set2(9), set2(10)], set2(10).self_and_ancestors)
>    end
> -  
> +
>    def test_siblings
>      assert_equal([], set2(1).siblings)
>      assert_equal([set2(2), set2(4)], set2(3).siblings)
>    end
> -  
> +
> +  def test_first_sibling
> +    assert set2(2).first_sibling?
> +    assert_equal(set2(2), set2(2).first_sibling)
> +    assert_equal(set2(2), set2(3).first_sibling)
> +    assert_equal(set2(2), set2(4).first_sibling)
> +  end
> +
> +  def test_last_sibling
> +    assert set2(4).last_sibling?
> +    assert_equal(set2(4), set2(2).last_sibling)
> +    assert_equal(set2(4), set2(3).last_sibling)
> +    assert_equal(set2(4), set2(4).last_sibling)
> +  end
> +
> +  def test_previous_siblings
> +    assert_equal([], set2(2).previous_siblings)
> +    assert_equal([set2(2)], set2(3).previous_siblings)
> +    assert_equal([set2(3), set2(2)], set2(4).previous_siblings)
> +  end
> +
> +  def test_previous_sibling
> +    assert_equal(nil, set2(2).previous_sibling)
> +    assert_equal(set2(2), set2(3).previous_sibling)
> +    assert_equal(set2(3), set2(4).previous_sibling)
> +    assert_equal([set2(3), set2(2)], set2(4).previous_sibling(2))
> +  end
> +
> +  def test_next_siblings
> +    assert_equal([], set2(4).next_siblings)
> +    assert_equal([set2(4)], set2(3).next_siblings)
> +    assert_equal([set2(3), set2(4)], set2(2).next_siblings)
> +  end
> +
> +  def test_next_sibling
> +    assert_equal(nil, set2(4).next_sibling)
> +    assert_equal(set2(4), set2(3).next_sibling)
> +    assert_equal(set2(3), set2(2).next_sibling)
> +    assert_equal([set2(3), set2(4)], set2(2).next_sibling(2))
> +  end
> +
>    def test_self_and_siblings
>      assert_equal([set2(1)], set2(1).self_and_siblings)
> -    assert_equal([set2(2), set2(3), set2(4)], set2(3).self_and_siblings)    
> +    assert_equal([set2(2), set2(3), set2(4)], set2(3).self_and_siblings)
>    end
> -  
> +
>    def test_level
>      assert_equal(0, set2(1).level)
>      assert_equal(1, set2(3).level)
>      assert_equal(3, set2(10).level)
>    end
> -  
> +
>    def test_all_children_count
>      assert_equal(0, set2(10).all_children_count)
>      assert_equal(1, set2(3).level)
> -    assert_equal(3, set2(10).level)    
> +    assert_equal(3, set2(10).level)
>    end
> -  
> +
>    def test_full_set
>      assert_equal(NestedSetWithStringScope.find(:all, :conditions => "root_id = 101", :order => "lft"), set2(1).full_set)
>      new_ns = NestedSetWithStringScope.new(:root_id => 101)
> @@ -311,9 +397,9 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      ns = NestedSetWithStringScope.create(:root_id => 234)
>      assert_equal([], ns.full_set(:exclude => ns))
>      assert_equal([set2(4), set2(8), set2(9)], set2(4).full_set(:exclude => set2(10)))
> -    assert_equal([set2(4), set2(8)], set2(4).full_set(:exclude => set2(9))) 
> +    assert_equal([set2(4), set2(8)], set2(4).full_set(:exclude => set2(9)))
>    end
> -    
> +
>    def test_all_children
>      assert_equal(NestedSetWithStringScope.find(:all, :conditions => "root_id = 101 AND id > 101", :order => "lft"), set2(1).all_children)
>      assert_equal([], NestedSetWithStringScope.new(:root_id => 101).all_children)
> @@ -323,27 +409,311 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal([set2(2), set2(4), set2(8)], set2(1).all_children(:exclude => [set2(9), 103]))
>      assert_equal([set2(2), set2(4), set2(8)], set2(1).all_children(:exclude => [set2(9), 103, 106]))
>    end
> -  
> +
>    def test_children
> -    assert_equal([], set2(10).children) 
> -    assert_equal([], set(1).children) 
> -    assert_equal([set2(2), set2(3), set2(4)], set2(1).children) 
> -    assert_equal([set2(5), set2(6), set2(7)], set2(3).children) 
> -    assert_equal([NestedSetWithStringScope.find(4006), NestedSetWithStringScope.find(4007)], NestedSetWithStringScope.find(4005).children) 
> +    assert_equal([], set2(10).children)
> +    assert_equal([], set(1).children)
> +    assert_equal([set2(2), set2(3), set2(4)], set2(1).children)
> +    assert_equal([set2(5), set2(6), set2(7)], set2(3).children)
> +    assert_equal([NestedSetWithStringScope.find(4006), NestedSetWithStringScope.find(4007)], NestedSetWithStringScope.find(4005).children)
> +  end
> +
> +  def test_children_count
> +    assert_equal(0, set2(10).children_count)
> +    assert_equal(3, set2(1).children_count)
>    end
> -  
> +
>    def test_leaves
>      assert_equal([set2(10)], set2(9).leaves)
>      assert_equal([set2(10)], set2(10).leaves)
>      assert_equal([set2(2), set2(5), set2(6), set2(7), set2(8), set2(10)], set2(1).leaves)
>    end
> -  
> +
>    def test_leaves_count
>      assert_equal(1, set2(10).leaves_count)
>      assert_equal(1, set2(9).leaves_count)
>      assert_equal(6, set2(1).leaves_count)
>    end
> -  
> +
> +  ##########################################
> +  # CASTING RESULT TESTS
> +  ##########################################
> +
> +  def test_recurse_result_set
> +    result = []
> +    NestedSetWithStringScope.recurse_result_set(set2(1).full_set) do |node, level|
> +      result << [level, node.id]
> +    end
> +    expected = [[0, 101], [1, 102], [1, 103], [2, 105], [2, 106], [2, 107], [1, 104], [2, 108], [2, 109], [3, 110]]
> +    assert_equal expected, result
> +  end
> +
> +  def test_disjointed_result_set
> +    result_set = set2(1).full_set(:conditions => { :type => 'NestedSetWithStringScope' })
> +    result = []
> +    NestedSetWithStringScope.recurse_result_set(result_set) do |node, level|
> +      result << [level, node.id]
> +    end
> +    expected = [[0, 102], [0, 104], [0, 105], [0, 106], [0, 107], [0, 110]]
> +    assert_equal expected, result
> +  end
> +
> +  def test_result_to_array
> +    result = NestedSetWithStringScope.result_to_array(set2(1).full_set) do |node, level|
> +      { :id => node.id, :level => level }
> +    end
> +    expected = [{:level=>0, :children=>[{:level=>1, :id=>102}, {:level=>1,
> +      :children=>[{:level=>2, :id=>105}, {:level=>2, :id=>106}, {:level=>2, :id=>107}], :id=>103}, {:level=>1,
> +      :children=>[{:level=>2, :id=>108}, {:level=>2, :children=>[{:level=>3, :id=>110}], :id=>109}], :id=>104}], :id=>101}]
> +    assert_equal expected, result
> +  end
> +
> +  def test_result_to_array_with_method_calls
> +    result = NestedSetWithStringScope.result_to_array(set2(1).full_set, :only => [:id], :methods => [:children_count])
> +    expected = [{:children=>[{:children_count=>0, :id=>102}, {:children=>[{:children_count=>0, :id=>105}, {:children_count=>0, :id=>106},
> +      {:children_count=>0, :id=>107}], :children_count=>3, :id=>103}, {:children=>[{:children_count=>0, :id=>108}, {:children=>[{:children_count=>0, :id=>110}],
> +      :children_count=>1, :id=>109}], :children_count=>2, :id=>104}], :children_count=>3, :id=>101}]
> +    assert_equal expected, result
> +  end
> +
> +  def test_disjointed_result_to_array
> +    result_set = set2(1).full_set(:conditions => { :type => 'NestedSetWithStringScope' })
> +    result = NestedSetWithStringScope.result_to_array(result_set) do |node, level|
> +      { :id => node.id, :level => level }
> +    end
> +    expected = [{:level=>0, :id=>102}, {:level=>0, :id=>104}, {:level=>0, :id=>105}, {:level=>0, :id=>106}, {:level=>0, :id=>107}, {:level=>0, :id=>110}]
> +    assert_equal expected, result
> +  end
> +
> +  def test_result_to_array_flat
> +    result = NestedSetWithStringScope.result_to_array(set2(1).full_set, :nested => false) do |node, level|
> +      { :id => node.id, :level => level }
> +    end
> +    expected = [{:level=>0, :id=>101}, {:level=>0, :id=>103}, {:level=>0, :id=>106}, {:level=>0, :id=>104}, {:level=>0, :id=>109},
> +      {:level=>0, :id=>102}, {:level=>0, :id=>105}, {:level=>0, :id=>107}, {:level=>0, :id=>108}, {:level=>0, :id=>110}]
> +    assert_equal expected, result
> +  end
> +
> +  def test_result_to_xml
> +    result = NestedSetWithStringScope.result_to_xml(set2(3).full_set, :record => 'node', :dasherize => false, :only => [:id]) do |options, subnode|
> +       options[:builder].tag!('type', subnode[:type])
> +    end
> +    expected = '<?xml version="1.0" encoding="UTF-8"?>
> +<nodes>
> +  <node>
> +    <id type="integer">103</id>
> +    <type>NS2</type>
> +    <children>
> +      <node>
> +        <id type="integer">105</id>
> +        <type>NestedSetWithStringScope</type>
> +        <children>
> +        </children>
> +      </node>
> +      <node>
> +        <id type="integer">106</id>
> +        <type>NestedSetWithStringScope</type>
> +        <children>
> +        </children>
> +      </node>
> +      <node>
> +        <id type="integer">107</id>
> +        <type>NestedSetWithStringScope</type>
> +        <children>
> +        </children>
> +      </node>
> +    </children>
> +  </node>
> +</nodes>'
> +    assert_equal expected, result.strip
> +  end
> +
> +  def test_disjointed_result_to_xml
> +    result_set = set2(1).full_set(:conditions => ['type IN(?)', ['NestedSetWithStringScope', 'NS2']])
> +    result = NestedSetWithStringScope.result_to_xml(result_set, :only => [:id])
> +    # note how nesting is preserved where possible; this is not always what you want though,
> +    # so you can force a flattened set with :nested => false instead (see below)
> +    expected = '<?xml version="1.0" encoding="UTF-8"?>
> +<nodes>
> +  <nested-set-with-string-scope>
> +    <id type="integer">102</id>
> +    <children>
> +    </children>
> +  </nested-set-with-string-scope>
> +  <ns2>
> +    <id type="integer">103</id>
> +    <children>
> +      <nested-set-with-string-scope>
> +        <id type="integer">105</id>
> +        <children>
> +        </children>
> +      </nested-set-with-string-scope>
> +      <nested-set-with-string-scope>
> +        <id type="integer">106</id>
> +        <children>
> +        </children>
> +      </nested-set-with-string-scope>
> +      <nested-set-with-string-scope>
> +        <id type="integer">107</id>
> +        <children>
> +        </children>
> +      </nested-set-with-string-scope>
> +    </children>
> +  </ns2>
> +  <nested-set-with-string-scope>
> +    <id type="integer">104</id>
> +    <children>
> +      <ns2>
> +        <id type="integer">108</id>
> +        <children>
> +        </children>
> +      </ns2>
> +    </children>
> +  </nested-set-with-string-scope>
> +</nodes>'
> +    assert_equal expected, result.strip
> +  end
> +
> +  def test_result_to_xml_flat
> +    result = NestedSetWithStringScope.result_to_xml(set2(3).full_set, :record => 'node', :dasherize => false, :only => [:id], :nested => false)
> +    expected = '<?xml version="1.0" encoding="UTF-8"?>
> +<nodes>
> +  <node>
> +    <id type="integer">103</id>
> +  </node>
> +  <node>
> +    <id type="integer">105</id>
> +  </node>
> +  <node>
> +    <id type="integer">106</id>
> +  </node>
> +  <node>
> +    <id type="integer">107</id>
> +  </node>
> +</nodes>'
> +    assert_equal expected, result.strip
> +  end
> +
> +  def test_result_to_attribute_based_xml
> +    result = NestedSetWithStringScope.result_to_attributes_xml(set2(1).full_set, :record => 'node', :only => [:id, :parent_id])
> +    expected = '<?xml version="1.0" encoding="UTF-8"?>
> +<node id="101" parent_id="0">
> +  <node id="102" parent_id="101"/>
> +  <node id="103" parent_id="101">
> +    <node id="105" parent_id="103"/>
> +    <node id="106" parent_id="103"/>
> +    <node id="107" parent_id="103"/>
> +  </node>
> +  <node id="104" parent_id="101">
> +    <node id="108" parent_id="104"/>
> +    <node id="109" parent_id="104">
> +      <node id="110" parent_id="109"/>
> +    </node>
> +  </node>
> +</node>'
> +    assert_equal expected, result.strip
> +  end
> +
> +  def test_result_to_attribute_based_xml_flat
> +    result = NestedSetWithStringScope.result_to_attributes_xml(set2(1).full_set, :only => [:id], :nested => false, :skip_instruct => true)
> +    expected = '<ns1 id="101"/>
> +<ns2 id="103"/>
> +<nested_set_with_string_scope id="106"/>
> +<nested_set_with_string_scope id="104"/>
> +<ns1 id="109"/>
> +<nested_set_with_string_scope id="102"/>
> +<nested_set_with_string_scope id="105"/>
> +<nested_set_with_string_scope id="107"/>
> +<ns2 id="108"/>
> +<nested_set_with_string_scope id="110"/>'
> +    assert_equal expected, result.strip
> +  end
> +
> +  ##########################################
> +  # WITH_SCOPE QUERY TESTS
> +  ##########################################
> +
> +  def test_filtered_full_set
> +    result_set = set2(1).full_set(:conditions => { :type => 'NestedSetWithStringScope' })
> +    assert_equal [102, 105, 106, 107, 104, 110], result_set.map(&:id)
> +  end
> +
> +  def test_reverse_result_set
> +    result_set = set2(1).full_set(:reverse => true)
> +    assert_equal [101, 104, 109, 110, 108, 103, 107, 106, 105, 102], result_set.map(&:id)
> +    # NestedSetWithStringScope.recurse_result_set(result_set) { |node, level| puts "#{'--' * level}#{node.id}" }
> +  end
> +
> +  def test_reordered_full_set
> +    result_set = set2(1).full_set(:order => 'id DESC')
> +    assert_equal [110, 109, 108, 107, 106, 105, 104, 103, 102, 101], result_set.map(&:id)
> +  end
> +
> +  def test_filtered_siblings
> +    node = set2(2)
> +    result_set = node.siblings(:conditions => { :type => node[:type] })
> +    assert_equal [104], result_set.map(&:id)
> +  end
> +
> +  def test_include_option_with_full_set
> +    result_set = set2(3).full_set(:include => :parent_node)
> +    assert_equal [[103, 101], [105, 103], [106, 103], [107, 103]], result_set.map { |n| [n.id, n.parent_node.id] }
> +  end
> +
> +  ##########################################
> +  # FIND UNTIL/THROUGH METHOD TESTS
> +  ##########################################
> +
> +  def test_ancestors_and_self_through
> +    result = set2(10).ancestors_and_self_through(set2(4))
> +    assert_equal [104, 109, 110], result.map(&:id)
> +    result = set2(10).ancestors_through(set2(4))
> +    assert_equal [104, 109], result.map(&:id)
> +  end
> +
> +  def test_full_set_through
> +    result = set2(4).full_set_through(set2(10))
> +    assert_equal [104, 108, 109, 110], result.map(&:id)
> +  end
> +
> +  def test_all_children_through
> +    result = set2(4).all_children_through(set2(10))
> +    assert_equal [108, 109, 110], result.map(&:id)
> +  end
> +
> +  def test_siblings_through
> +    result = set2(5).self_and_siblings_through(set2(7))
> +    assert_equal [105, 106, 107], result.map(&:id)
> +    result = set2(7).siblings_through(set2(5))
> +    assert_equal [105, 106], result.map(&:id)
> +  end
> +
> +  ##########################################
> +  # FIND CHILD BY ID METHOD TESTS
> +  ##########################################
> +
> +  def test_child_by_id
> +    assert_equal set2(6), set2(3).child_by_id(set2(6).id)
> +    assert_nil set2(3).child_by_id(set2(8).id)
> +  end
> +
> +  def test_child_of
> +    assert  set2(6).child_of?(set2(3))
> +    assert !set2(8).child_of?(set2(3))
> +    assert set2(6).child_of?(set2(3), :conditions => '1 = 1')
> +  end
> +
> +  def test_direct_child_by_id
> +    assert_equal set2(9), set2(4).direct_child_by_id(set2(9).id)
> +    assert_nil set2(4).direct_child_by_id(set2(10).id)
> +  end
> +
> +  def test_direct_child_of
> +    assert set2(9).direct_child_of?(set2(4))
> +    assert !set2(10).direct_child_of?(set2(4))
> +    assert set2(9).direct_child_of?(set2(4), :conditions => '1 = 1')
> +  end
> +
>    ##########################################
>    # INDEX-CHECKING METHOD TESTS
>    ##########################################
> @@ -362,7 +732,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.find(4001).reload.check_subtree}
>      # this method receives lots of additional testing through tests of check_full_tree and check_all
>    end
> -  
> +
>    def test_check_full_tree
>      assert_nothing_raised {set2(1).check_full_tree}
>      assert_nothing_raised {NestedSetWithStringScope.find(4006).check_full_tree}
> @@ -374,31 +744,31 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      NestedSetWithStringScope.update_all("lft = lft + 1", "id > 101")
>      assert_raise(ActiveRecord::ActiveRecordError) {set2(4).check_full_tree}
>    end
> -  
> +
>    def test_check_full_tree_orphan
>      assert_raise(ActiveRecord::RecordNotFound) {NestedSetWithStringScope.find(99)} # make sure ID 99 doesn't exist
>      ns = NestedSetWithStringScope.create(:root_id => 101)
>      NestedSetWithStringScope.update_all("parent_id = 99", "id = #{ns.id}")
>      assert_raise(ActiveRecord::ActiveRecordError) {set2(3).check_full_tree}
>    end
> -  
> +
>    def test_check_full_tree_endless_loop
>      ns = NestedSetWithStringScope.create(:root_id => 101)
>      NestedSetWithStringScope.update_all("parent_id = #{ns.id}", "id = #{ns.id}")
>      assert_raise(ActiveRecord::ActiveRecordError) {set2(6).check_full_tree}
>    end
> -  
> +
>    def test_check_full_tree_virtual_roots
> -    a = Category.create    
> +    a = Category.create
>      b = Category.create
> -    
> +
>      assert_nothing_raised {a.check_full_tree}
>      Category.update_all("rgt = rgt + 2, lft = lft + 2", "id = #{b.id}") # create a gap between virtual roots
>      assert_raise(ActiveRecord::ActiveRecordError) {a.check_full_tree}
>    end
> -  
> +
>    # see also the tests of check_all under 'class method tests'
> -    
> +
>    ##########################################
>    # INDEX-ALTERING (UPDATE) METHOD TESTS
>    ##########################################
> @@ -416,7 +786,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal(2, set2(3).lft)
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_move_to_right_of # this method undergoes additional testing elsewhere
>      set2(3).move_to_right_of(set2(2)) # should cause no change
>      set2(4).move_to_right_of(set2(3)) # should cause no change
> @@ -432,28 +802,28 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal(4, set2(4).lft)
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_adding_children
>      assert(set(1).unknown?)
>      assert(set(2).unknown?)
>      set(1).add_child set(2)
> -    
> +
>      # Did we maintain adding the parent_ids?
>      assert(set(1).root?)
>      assert(set(2).child?)
>      assert(set(2).parent_id == set(1).id)
> -    
> +
>      # Check boundaries
>      assert_equal(set(1).lft, 1)
>      assert_equal(set(2).lft, 2)
>      assert_equal(set(2).rgt, 3)
>      assert_equal(set(1).rgt, 4)
> -    
> +
>      # Check children cound
>      assert_equal(set(1).all_children_count, 1)
> -    
> +
>      set(1).add_child set(3)
> -    
> +
>      #check boundries
>      assert_equal(set(1).lft, 1)
>      assert_equal(set(2).lft, 2)
> @@ -461,7 +831,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal(set(3).lft, 4)
>      assert_equal(set(3).rgt, 5)
>      assert_equal(set(1).rgt, 6)
> -    
> +
>      # How is the count looking?
>      assert_equal(set(1).all_children_count, 2)
>  
> @@ -476,26 +846,26 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal(set(3).lft, 6)
>      assert_equal(set(3).rgt, 7)
>      assert_equal(set(1).rgt, 8)
> -    
> +
>      # Children count
>      assert_equal(set(1).all_children_count, 3)
>      assert_equal(set(2).all_children_count, 1)
>      assert_equal(set(3).all_children_count, 0)
>      assert_equal(set(4).all_children_count, 0)
> -    
> +
>      set(2).add_child set(5)
>      set(4).add_child set(6)
> -    
> +
>      assert_equal(set(2).all_children_count, 3)
>  
>      # Children accessors
>      assert_equal(set(1).full_set.length, 6)
>      assert_equal(set(2).full_set.length, 4)
>      assert_equal(set(4).full_set.length, 2)
> -    
> +
>      assert_equal(set(1).all_children.length, 5)
>      assert_equal(set(6).all_children.length, 0)
> -    
> +
>      assert_equal(set(1).direct_children.length, 2)
>  
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
> @@ -505,39 +875,39 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      mixins(:set_1).add_child(mixins(:set_2))
>      assert_equal(1, mixins(:set_1).direct_children.length)
>  
> -    mixins(:set_2).add_child(mixins(:set_3))                      
> -    assert_equal(1, mixins(:set_1).direct_children.length)     
> -    
> +    mixins(:set_2).add_child(mixins(:set_3))
> +    assert_equal(1, mixins(:set_1).direct_children.length)
> +
>      # Local cache is now out of date!
>      # Problem: the update_alls update all objects up the tree
>      mixins(:set_1).reload
> -    assert_equal(2, mixins(:set_1).all_children.length)              
> -    
> +    assert_equal(2, mixins(:set_1).all_children.length)
> +
>      assert_equal(1, mixins(:set_1).lft)
>      assert_equal(2, mixins(:set_2).lft)
>      assert_equal(3, mixins(:set_3).lft)
>      assert_equal(4, mixins(:set_3).rgt)
>      assert_equal(5, mixins(:set_2).rgt)
> -    assert_equal(6, mixins(:set_1).rgt)  
> +    assert_equal(6, mixins(:set_1).rgt)
>      assert(mixins(:set_1).root?)
> -                  
> +
>      begin
>        mixins(:set_4).add_child(mixins(:set_1))
>        fail
>      rescue
>      end
> -    
> +
>      assert_equal(2, mixins(:set_1).all_children.length)
>      mixins(:set_1).add_child mixins(:set_4)
>      assert_equal(3, mixins(:set_1).all_children.length)
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_move_to_child_of_1
>      bill = NestedSetWithStringScope.new(:root_id => 101, :pos => 2)
> -    assert_raise(ActiveRecord::ActiveRecordError) { bill.move_to_child_of(set2(1)) }    
> -    assert_raise(ActiveRecord::ActiveRecordError) { set2(1).move_to_child_of(set2(1)) }    
> -    assert_raise(ActiveRecord::ActiveRecordError) { set2(4).move_to_child_of(set2(9)) }    
> +    assert_raise(ActiveRecord::ActiveRecordError) { bill.move_to_child_of(set2(1)) }
> +    assert_raise(ActiveRecord::ActiveRecordError) { set2(1).move_to_child_of(set2(1)) }
> +    assert_raise(ActiveRecord::ActiveRecordError) { set2(4).move_to_child_of(set2(9)) }
>      assert bill.save
>      assert_nothing_raised {set2(1).reload.check_subtree}
>      assert bill.move_to_left_of(set2(3))
> @@ -554,7 +924,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal(18, set2(9).lft) # to the right of existing children?
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_move_to_child_of_2
>      bill = NestedSetWithStringScope.new(:root_id => 101)
>      assert_nothing_raised {set2(1).check_subtree}
> @@ -573,7 +943,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_nothing_raised {set2(1).reload.check_subtree}
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_move_to_child_of_3
>      bill = NestedSetWithStringScope.new(:root_id => 101)
>      assert bill.save
> @@ -582,7 +952,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_nothing_raised {set2(1).reload.check_subtree}
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_move_1
>      set2(4).move_to_child_of(set2(3))
>      assert_equal(set2(3), set2(4).reload.parent)
> @@ -593,7 +963,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_nothing_raised {set2(1).reload.check_subtree}
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_move_2
>      initial = set2(1).full_set
>      assert_raise(ActiveRecord::ActiveRecordError) { set2(3).move_to_child_of(set2(6)) } # can't set a current child as the parent-- creates a loop
> @@ -601,7 +971,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      set2(2).move_to_child_of(set2(5))
>      set2(4).move_to_child_of(set2(2))
>      set2(10).move_to_right_of(set2(3))
> -    
> +
>      assert_equal 105, set2(2).parent_id
>      assert_equal 102, set2(4).parent_id
>      assert_equal 101, set2(10).parent_id
> @@ -615,7 +985,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      set2(4).move_to_right_of(set2(3))
>      set2(10).move_to_child_of(set2(9))
>      set2(2).move_to_left_of(set2(3))
> -    
> +
>      # now everything should be back where it started-- check against initial
>      final = set2(1).reload.full_set
>      assert_equal(initial, final)
> @@ -626,13 +996,138 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      end
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_scope_enforcement # prevent moves between trees
>      assert_raise(ActiveRecord::ActiveRecordError) { set(3).move_to_child_of(set2(6)) }
>      ns = NestedSetWithStringScope.create(:root_id => 214)
>      assert_raise(ActiveRecord::ActiveRecordError) { ns.move_to_child_of(set2(1)) }
>    end
> -  
> +
> +  ##########################################
> +  # ACTS_AS_LIST-LIKE BEHAVIOUR TESTS
> +  ##########################################
> +
> +  def test_swap
> +    set2(5).swap(set2(7))
> +    assert_equal [107, 106, 105], set2(3).children.map(&:id)
> +    assert_nothing_raised {set2(3).check_full_tree}
> +    assert_raise(ActiveRecord::ActiveRecordError) { set2(3).swap(set2(10)) } # isn't a sibling...
> +  end
> +
> +  def test_insert_at
> +    child = NestedSetWithStringScope.create(:root_id => 101)
> +    child.insert_at(set2(3), :last)
> +    assert_equal child, set2(3).children.last
> +
> +    child = NestedSetWithStringScope.create(:root_id => 101)
> +    child.insert_at(set2(3), :first)
> +    assert_equal child, set2(3).children.first
> +
> +    child = NestedSetWithStringScope.create(:root_id => 101)
> +    child.insert_at(set2(3), 2)
> +    assert_equal child, set2(3).children[2]
> +
> +    child = NestedSetWithStringScope.create(:root_id => 101)
> +    child.insert_at(set2(3), 1000)
> +    assert_equal child, set2(3).children.last
> +
> +    child = NestedSetWithStringScope.create(:root_id => 101)
> +    child.insert_at(set2(3), 1)
> +    assert_equal child, set2(3).children[1]
> +  end
> +
> +  def test_move_higher
> +    set2(7).move_higher
> +    assert_equal [105, 107, 106], set2(3).children.map(&:id)
> +    set2(7).move_higher
> +    assert_equal [107, 105, 106], set2(3).children.map(&:id)
> +    set2(7).move_higher
> +    assert_equal [107, 105, 106], set2(3).children.map(&:id)
> +  end
> +
> +  def test_move_lower
> +    set2(5).move_lower
> +    assert_equal [106, 105, 107], set2(3).children.map(&:id)
> +    set2(5).move_lower
> +    assert_equal [106, 107, 105], set2(3).children.map(&:id)
> +    set2(5).move_lower
> +    assert_equal [106, 107, 105], set2(3).children.map(&:id)
> +  end
> +
> +  def test_move_to_top
> +    set2(7).move_to_top
> +    assert_equal [107, 105, 106], set2(3).children.map(&:id)
> +  end
> +
> +  def test_move_to_bottom
> +    set2(5).move_to_bottom
> +    assert_equal [106, 107, 105], set2(3).children.map(&:id)
> +  end
> +
> +  def test_move_to_position
> +    set2(7).move_to_position(:first)
> +    assert_equal [107, 105, 106], set2(3).children.map(&:id)
> +    set2(7).move_to_position(:last)
> +    assert_equal [105, 106, 107], set2(3).children.map(&:id)
> +  end
> +
> +  def test_move_to_position_limits
> +    set2(7).move_to_position(0)
> +    assert_equal [107, 105, 106], set2(3).children.map(&:id)
> +    set2(7).move_to_position(100)
> +    assert_equal [105, 106, 107], set2(3).children.map(&:id)
> +  end
> +
> +  def test_move_to_position_index
> +    set2(7).move_to_position(0)
> +    assert_equal [107, 105, 106], set2(3).children.map(&:id)
> +    set2(7).move_to_position(1)
> +    assert_equal [105, 107, 106], set2(3).children.map(&:id)
> +    set2(7).move_to_position(2)
> +    assert_equal [105, 106, 107], set2(3).children.map(&:id)
> +    set2(5).move_to_position(2)
> +    assert_equal [106, 107, 105], set2(3).children.map(&:id)
> +  end
> +
> +  def test_scoped_move_to_position
> +    set2(7).move_to_position(0, :conditions => { :id => [105, 106, 107] })
> +    assert_equal [107, 105, 106], set2(3).children.map(&:id)
> +    set2(7).move_to_position(1, :conditions => { :id => [105, 107] })
> +    assert_equal [105, 107, 106], set2(3).children.map(&:id)
> +    set2(7).move_to_position(1, :conditions => { :id => [106, 107] })
> +    assert_equal [105, 106, 107], set2(3).children.map(&:id)
> +  end
> +
> +  def test_reorder_children
> +    assert_equal [105], set2(3).reorder_children(107, 106, 105).map(&:id)
> +    assert_equal [107, 106, 105], set2(3).children.map(&:id)
> +    assert_equal [107, 106], set2(3).reorder_children(106, 105, 107).map(&:id)
> +    assert_equal [106, 105, 107], set2(3).children.map(&:id)
> +  end
> +
> +  def test_reorder_children_with_random_samples
> +    10.times do
> +      child = NestedSetWithStringScope.create(:root_id => 101)
> +      child.move_to_child_of set2(3)
> +    end
> +    ordered_ids = set2(3).children.map(&:id).sort_by { rand }
> +    set2(3).reorder_children(ordered_ids)
> +    assert_equal ordered_ids, set2(3).children.map(&:id)
> +  end
> +
> +  def test_reorder_children_with_partial_id_set
> +    10.times do
> +      child = NestedSetWithStringScope.create(:root_id => 101)
> +      child.move_to_child_of set2(3)
> +    end
> +    child_ids = set2(3).children.map(&:id)
> +    set2(3).reorder_children(child_ids.last, child_ids.first)
> +    ordered_ids = set2(3).children.map(&:id)
> +    assert_equal ordered_ids.first, child_ids.last
> +    assert_equal ordered_ids.last, child_ids.first
> +    assert_equal child_ids[1, -2], ordered_ids[1, -2]
> +  end
> +
>    ##########################################
>    # RENUMBERING TESTS
>    ##########################################
> @@ -648,7 +1143,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal 11, set2(3).rgt
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_renumber_full_tree_2
>      NestedSetWithStringScope.update_all("lft = lft + 1, rgt = rgt + 1", "root_id = 101")
>      assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
> @@ -659,8 +1154,8 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      set2(8).renumber_full_tree
>      assert_nothing_raised(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
>    end
> -  
> -  
> +
> +
>    ##########################################
>    # CONCURRENCY TESTS
>    ##########################################
> @@ -670,14 +1165,14 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      c1.move_to_right_of(c3)
>      c2.save
>      assert_nothing_raised {Category.check_all}
> -    
> +
>      ns1 = set2(3)
>      ns2 = set2(4)
>      ns2.move_to_left_of(102) # ns1 is now out-of-date
>      ns1.save
>      assert_nothing_raised {set2(1).check_subtree}
>    end
> -  
> +
>    def test_concurrent_add_add
>      c1 = Category.new
>      c2 = Category.new
> @@ -689,21 +1184,21 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      c3.save
>      assert_nothing_raised {Category.check_all}
>    end
> -  
> +
>    def test_concurrent_add_delete
>      ns = set2(3)
>      new_ns = NestedSetWithStringScope.create(:root_id => 101)
>      ns.destroy
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_concurrent_add_move
>      ns = set2(3)
>      new_ns = NestedSetWithStringScope.create(:root_id => 101)
>      ns.move_to_left_of(102)
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_concurrent_delete_add
>      ns = set2(3)
>      new_ns = NestedSetWithStringScope.new(:root_id => 101)
> @@ -711,7 +1206,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      new_ns.save
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_concurrent_delete_delete
>      ns1 = set2(3)
>      ns2 = set2(4)
> @@ -719,7 +1214,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      ns2.destroy
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_concurrent_delete_move
>      ns1 = set2(3)
>      ns2 = set2(4)
> @@ -727,7 +1222,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      ns2.move_to_left_of(102)
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_concurrent_move_add
>      ns = set2(3)
>      new_ns = NestedSetWithStringScope.new(:root_id => 101)
> @@ -735,7 +1230,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      new_ns.save
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_concurrent_move_delete
>      ns1 = set2(3)
>      ns2 = set2(4)
> @@ -743,15 +1238,50 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      ns1.destroy
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
>    def test_concurrent_move_move
>      ns1 = set2(3)
>      ns2 = set2(4)
>      ns1.move_to_left_of(102)
>      ns2.move_to_child_of(102)
> -    assert_nothing_raised {NestedSetWithStringScope.check_all}    
> +    assert_nothing_raised {NestedSetWithStringScope.check_all}
> +  end
> +
> +  ##########################################
> +  # CALLBACK TESTS
> +  ##########################################
> +  # Because the nested set code relies heavily on callbacks, we
> +  # want to ensure that we aren't causing problems for user-defined callbacks
> +  def test_callbacks
> +    # 1) Do all user-defined callbacks work?
> +    $callbacks = []
> +    ns = NS2.new(:root_id => 101) # NS2 adds symbols to $callbacks when the callbacks fire
> +    assert_equal([], $callbacks)
> +    ns.save!
> +    assert_equal([:before_save, :before_create, :after_create, :after_save], $callbacks)
> +    $callbacks = []
> +    ns.pos = 2
> +    ns.save!
> +    assert_equal([:before_save, :before_update, :after_update, :after_save], $callbacks)
> +    $callbacks = []
> +    ns.destroy
> +    assert_equal([:before_destroy, :after_destroy], $callbacks)
>    end
> -  
> +
> +  def test_callbacks2
> +    # 2) Do our callbacks still work, even when a programmer defines
> +    # their own callbacks in the overwriteable style?
> +    # (the NS2 model defines callbacks in the overwritable style)
> +    ns = NS2.create(:root_id => 101)
> +    assert ns.lft != nil && ns.rgt != nil
> +    child_ns = NS2.create(:root_id => 101)
> +    child_ns.move_to_child_of(ns)
> +    id = child_ns.id
> +    ns.destroy
> +    assert_equal(nil, NS2.find(:first, :conditions => "id = #{id}"))
> +    # lots of implicit testing occurs in other test methods
> +  end
> +
>    ##########################################
>    # BUG-SPECIFIC TESTS
>    ##########################################
> @@ -763,17 +1293,17 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      sub.move_to_child_of main
>      sub.save
>      main.save
> -    
> +
>      assert_equal(1, main.all_children_count)
>      assert_equal([main, sub], main.full_set)
>      assert_equal([sub], main.all_children)
> -    
> +
>      assert_equal(1, main.lft)
>      assert_equal(2, sub.lft)
>      assert_equal(3, sub.rgt)
>      assert_equal(4, main.rgt)
>    end
> -  
> +
>    def test_ticket_19
>      # this test currently relies on the fact that objects are reloaded at the beginning of the move_to methods
>      root = Category.create
> @@ -781,7 +1311,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      second = Category.create
>      first.move_to_child_of(root)
>      second.move_to_child_of(root)
> -    
> +
>      # now we should have the situation described in the ticket
>      assert_nothing_raised {first.move_to_child_of(second)}
>      assert_raise(ActiveRecord::ActiveRecordError) {second.move_to_child_of(first)} # try illegal move
> @@ -789,7 +1319,7 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_nothing_raised {first.move_to_child_of(second)} # try it the other way-- first is now on the other side of second
>      assert_nothing_raised {Category.check_all}
>    end
> -  
> +
>    # Note that single-table inheritance recieves extensive implicit testing,
>    # because one of the fixture trees contains a hodge-podge of classes.
>    def test_ticket_10
> @@ -799,10 +1329,19 @@ class MixinNestedSetTest < Test::Unit::TestCase
>      assert_equal(10, set2(9).rgt)
>      NS2.find(103).destroy
>      assert_equal(12, set2(1).rgt)
> -    assert_equal(6, NestedSetWithStringScope.count("root_id = 101"))
> +    assert_equal(6, NestedSetWithStringScope.count(:conditions => "root_id = 101"))
>      assert_nothing_raised {NestedSetWithStringScope.check_all}
>    end
> -  
> +
> +  # the next virtual root was not starting with the correct lft value
> +  def test_ticket_29
> +    first = Category.create
> +    second = Category.create
> +    Category.renumber_all
> +    second.reload
> +    assert_equal(3, second.lft)
> +  end
> +
>  end
>  
> 
> @@ -810,7 +1349,7 @@ end
>  ###################################################################
>  ## Tests that don't pass yet or haven't been finished
>  
> -## make #destroy set left & rgt to nil? 
> +## make #destroy set left & rgt to nil?
>  
>  #def test_find_insertion_point
>  #  bill = NestedSetWithStringScope.create(:pos => 2, :root_id => 101)
> diff --git a/src/vendor/plugins/betternestedset/test/fixtures/mixin.rb b/src/vendor/plugins/betternestedset/test/fixtures/mixin.rb
> index 7238b92..7888d26 100644
> --- a/src/vendor/plugins/betternestedset/test/fixtures/mixin.rb
> +++ b/src/vendor/plugins/betternestedset/test/fixtures/mixin.rb
> @@ -1,30 +1,33 @@
>  class Mixin < ActiveRecord::Base
> +  belongs_to :parent_node, :class_name => 'Mixin', :foreign_key => 'parent_id'
>  end
>  
>  class NestedSet < Mixin
> -  acts_as_nested_set :scope => "root_id IS NULL"
> -  def self.table_name() "mixins" end
> +  acts_as_nested_set :scope => "mixins.root_id IS NULL"
>  end
>  
>  class NestedSetWithStringScope < Mixin
> -  acts_as_nested_set :scope => 'root_id = #{root_id}'
> -  def self.table_name() "mixins" end
> +  acts_as_nested_set :scope => 'mixins.root_id = #{root_id}'
>  end
>  
>  class NS1 < NestedSetWithStringScope
> -  def self.table_name() "mixins" end
>  end
>  
>  class NS2 < NS1
> -  def self.table_name() "mixins" end
> +  my_callbacks = [:before_create, :before_save, :before_update, :before_destroy,
> +    :after_create, :after_save, :after_update, :after_destroy]
> +  my_callbacks.each do |sym|
> +    define_method(sym) do
> +      $callbacks ||= []
> +      $callbacks << sym
> +    end
> +  end
>  end
>  
>  class NestedSetWithSymbolScope < Mixin
>    acts_as_nested_set :scope => :root
> -  def self.table_name() "mixins" end
>  end
>  
>  class Category < Mixin
>    acts_as_nested_set
> -  def self.table_name() "mixins" end
>  end

ACK, works for me.




More information about the ovirt-devel mailing list