# * George Moschovitis # (c) 2004-2005 Navel, all rights reserved. # $Id: hierarchical.rb 1 2005-04-11 11:04:30Z gmosx $ require 'glue/dynamic_include' module Og # Implements the Nested Sets pattern for hierarchical # SQL queries. module NestedSets def self.append_dynamic_features(base, options) c = { :left => 'lft', :right => 'rgt', :type => Fixnum, :scope => '"1 = 1"', :parent => Inflector.name(base), :children => Inflector.plural_name(base) } c.update(options) if options parent = "#{c[:parent]}_oid" left = c[:left] right = c[:right] children = c[:children] child = Inflector.singularize(children) if c[:scope].is_a?(Symbol) && c[:scope].to_s !~ /_oid$/ c[:scope] = "#{c[:scope]}_oid".intern end scope = c[:scope] if scope.is_a?(Symbol) scope = %{(#{scope} ? "#{scope} = \#{@#{scope}}" : "#{scope} IS NULL")} end base.module_eval <<-EOE, __FILE__, __LINE__ property :#{parent}, Fixnum, :sql_index => true property :#{left}, :#{right}, #{c[:type]} def root? (@#{parent}.nil? || @#{parent} == 0) && (@#{left} == 1) && (@#{right} > @#{left}) end def child? (@#{parent} && @#{parent} != 0) && (@#{left} > 1) && (@#{right} > @#{left}) end def #{children}_count return (@#{right} - @#{left} - 1)/2 end def full_#{children}(extrasql = nil) #{base}.all("WHERE " + #{scope} + " AND (#{left} BETWEEN \#\{@#{left}\} AND \#{@#{right}}) \#{extrasql}") end def #{children}(extrasql = nil) #{base}.all("WHERE " + #{scope} + " AND (#{left} > \#\{@#{left}\}) AND (#{right} < \#{@#{right}}) \#{extrasql}") end def direct_#{children}(extrasql = nil) #{base}.all("WHERE " + #{scope} + " AND #{parent} = \#{@oid} \#{extrasql}") end def add_#{child}(child) self.reload if @oid child.reload if child.oid if @#{left}.nil? || @#{left} == 0 || @#{right}.nil? || @#{right} == 0 @#{left} = 1 @#{right} = 2 end child.#{parent} = @oid child.#{left} = pivot = @#{right} child.#{right} = pivot + 1 @#{right} = pivot + 2 self.class.transaction do self.class.update("#{left} = (#{left} + 2)", "WHERE " + #{scope} + " AND #{left} >= \#{pivot}") self.class.update("#{right} = (#{right} + 2)", "WHERE " + #{scope} + " AND #{right} >= \#{pivot}") end self.save child.save end def self.og_pre_delete(conn, obj) return unless (obj.#{left} and obj.#{right}) span = obj.#{right} - obj.#{left} + 1 (klass = obj.class).transaction do klass.delete(#{scope} + " AND #{left} > \#{obj.#{left}} AND (#{right} < \#{obj.#{right}})") klass.update("#{left} = (#{left} - \#{span})", "WHERE " + #{scope} + " AND #{left} >= \#{obj.#{right}}") klass.update("#{right} = (#{right} - \#{span})", "WHERE " + #{scope} + " AND #{right} >= \#{obj.#{right}}") end end EOE end end # Transform the base class to a hierarchical node. # A selection of different implementation strategies # are provided. # # === Example # # class Comment # include Hierarchical, :method => :nested_sets # end # # [+:method+] # :simple # :nested_sets # :nested_intervals module Hierarchical def self.append_dynamic_features(base, options) c = { :method => :nested_sets, } c.update(options) if options base.include(NestedSets) end end end