Действует как дерево с несколькими моделями - PullRequest
2 голосов
/ 26 января 2010

У меня есть несколько моделей, которые я хотел бы связать вместе иерархически. Ради простоты, скажем, у меня есть эти три:

class Group < ActiveRecord::Base
  acts_as_tree
  has_many :users
end

class User < ActiveRecord::Base
  acts_as_tree
  belongs_to :group
  has_many :posts
end

class Post < ActiveRecord::Base
  acts_as_tree
  belongs_to :user
end

В соответствии с текущим act_as_tree каждый узел может по отдельности иерархически связываться с другими узлами при условии, что они одного типа. Я хотел бы снять это ограничение на идентичность типа, чтобы SomePost.parent мог иметь пользователя или сообщение в качестве своего «родителя», а SomeUser.parent мог иметь другого пользователя или группу в качестве родителя.

Есть мысли?

Ответы [ 2 ]

3 голосов
/ 27 января 2010

То, как я делал это в прошлом, - это использование полиморфного контейнера, который живет в дереве, для сопоставления с конкретными отдельными моделями.

class Container < ActiveRecord::Base
   acts_as_tree
   belongs_to :containable, :polymorphic => true 
end

class User
  has_one :container :as => :containable
end
0 голосов
/ 03 апреля 2010

Мне удалось сделать это немного по-другому, но это может не сработать в вашей ситуации. Я выполнял рефакторинг существующего кода и не хотел выполнять какую-либо серьезную миграцию базы данных.

Я хотел отдельные листовые и нодовые классы. Оба наследуют от класса дерева.

Я добавил две функции в ClassMethods в vendor/plugins/acts_as_tree/lib/active_record/acts/tree.rb:

    # Configuration options are:
    #
    # * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+)
    # * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
    # * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+).
    # * <tt>leaf_class_name</tt> - leaf class subtype of base tree class
    # * <tt>node_class_name</tt> - node class subtype of base tree class
    def acts_as_tree_node(options = {})
      configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil, :node_class_name => 'Node', :leaf_class_name => 'Leaf' }
      configuration.update(options) if options.is_a?(Hash)

      belongs_to :parent, :class_name => configuration[:node_class_name], :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
      #has_many :children, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy

      class_eval <<-EOV
        has_many :child_nodes, :class_name => '#{configuration[:node_class_name]}', :foreign_key => "#{configuration[:foreign_key]}", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}, :dependent => :destroy
        has_many :child_leaves, :class_name => '#{configuration[:leaf_class_name]}', :foreign_key => "#{configuration[:foreign_key]}", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}, :dependent => :destroy

        include ActiveRecord::Acts::Tree::InstanceMethods

        def self.roots
          find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
        end

        def self.root
          find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
        end
      EOV
    end

    # Configuration options are:
    #
    # * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+)
    # * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
    # * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+).
    # * <tt>node_class_name</tt> - the class name of the node (subclass of the tree base)
    def acts_as_tree_leaf(options = {})
      configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil, :node_class_name => 'Node' }
      configuration.update(options) if options.is_a?(Hash)

      belongs_to :parent, :class_name => configuration[:node_class_name], :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]

      class_eval <<-EOV
        include ActiveRecord::Acts::Tree::InstanceMethods

        def self.roots
          find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
        end

        def self.root
          find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
        end
      EOV
    end

Затем в InstanceMethods я просто добавил одну функцию:

    # Returns list of children, whether nodes or leaves.
    #
    # NOTE: Will not return both, because that would take two queries and
    # order will not be preserved.
    def children
      child_leaves.count == 0 ? child_nodes : child_leaves
    end

Это что-то вроде хака, но это работает для меня, так как у каждого узла есть все подводные лодки одного типа. Вы можете поиграть с функцией children, чтобы получить другое поведение, например:

def children
  child_nodes | child_leaves
end

Но все равно требуется дополнительный запрос, и вы теряете свой заказ, границы и прочее.

Наконец, в моем классе Node у меня есть

acts_as_tree_node :node_class_name => 'NodeMatrix', :leaf_class_name => 'LeafMatrix'

и в моем классе Leaf, следующее:

acts_as_tree_leaf :node_class_name => 'NodeMatrix'

Оба они наследуются от TreeMatrix, который является чисто виртуальным (на самом деле ничего не создается как TreeMatrix, это просто базовый класс).

Опять же, это очень зависит от приложения. Но это дает вам представление о том, как вы можете изменить acts_as_tree.

...