Действует как дерево с несколькими моделями

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

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 мог иметь другого пользователя или группу в качестве своего родителя.

Какие-нибудь мысли?


person Joseph Weissman    schedule 26.01.2010    source источник


Ответы (2)


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

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

class User
  has_one :container :as => :containable
end
person Toby Hede    schedule 26.01.2010
comment
означает ли это, что у поста и группы также будет has_one :container :as =› :containable ? - person corroded; 10.03.2011

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

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

Я добавил две функции в 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, это просто базовый класс).

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

person Juno Woods    schedule 02.04.2010