module Customize module Inherited def self.included base base.extend ClassMethods base.has_one :inherit_node, :class_name=>Customize::InheritNode.name, :as=>:node, :dependent=>:destroy base.scope :ascents_for, lambda { |object, options={:include=>false}| ntn = Customize::InheritNode.table_name node = object.inherit_node condition_string = if options[:include] "#{ntn}.left <= ? and #{ntn}.right >= ?" else "#{ntn}.left < ? and #{ntn}.right > ?" end base.joins(:inherit_node).where(condition_string, node.left, node.right) } base.scope :descents_for, lambda { |object, options={:include=>false}| ntn = Customize::InheritNode.table_name node = object.inherit_node condition_string = if options[:include] "#{ntn}.left >= ? and #{ntn}.right <= ?" else "#{ntn}.left > ? and #{ntn}.right < ?" end base.joins(:inherit_node).where(condition_string, node.left, node.right) } base.after_create { |object| object.create_inherit_node :left=>0, :right=>1 } base.delegate :leaf?, :to=>:inherit_node base.before_destroy { |object| raise 'object should not have children when destroy' if object.inherit_node.children.size > 0 } end module ClassMethods def root joins(:inherit_node).where("parent_id is null") end def type_tree roots = root converter = proc {|items| out = items.collect { |item| {:id=>item.id,:label=>item.label,:inherit_node_id=>item.inherit_node.id, :children=>converter.call(item.children)} } } converter.call(roots) end end def parent inherit_node.parent_node.try(:node) end def children inherit_node.children.collect(&:node) end def label self.to_s end def inherit parent raise 'should be save first' if self.new_record? raise 'should be same class' if self.class != parent.class raise 'should not be self' if self.id == parent.id self.class.transaction do inherit_node.parent_id = parent.inherit_node.id right = parent.inherit_node.right InheritNode.where("right >= ?", inherit_node.left).update_all("right = right+2") inherit_node.left = right inherit_node.right = right + 1 inherit_node.save end end def ascents options={:include=>false} return [] if new_record? self.class.ascents_for self, options end def inherit_association name, options={:include=>false} association_table_name = association(name).aliased_table_name r = ascents(options).joins(name).select("#{association_table_name}.*").uniq r.map(&name).flatten end def descents return [] if new_record? self.class.descents_for self end def ascent_ids ascents.map(&:id) end def descent_ids descents.map(&:id) end end end