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 node = nil roots = root converter = proc {|items, node| out = items.select { |item| node.nil? || (item.inherit_node.id != node.id && (node.parent_node.nil? || item.inherit_node.id != node.parent_node.id)) }.collect { |item| {:id=>item.id,:label=>item.label,:inherit_node_id=>item.inherit_node.id, :children=>converter.call(item.children, node)} } } converter.call(roots, node) 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 raise 'should not inherit descents' if self.descent_ids.include? parent.id raise 'should not inherit parent' if self.parent.try(: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} a = association(name) sql = ascents(options).joins(name).select("#{a.aliased_table_name}.#{a.klass.primary_key}").to_sql a.klass.from("(#{sql}) as subquery, #{a.klass.table_name}"). where("#{a.klass.table_name}.#{a.klass.primary_key} = subquery.#{a.klass.primary_key}") 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