2

我试图实现典型GOF组成图案:on Rails的实现在Ruby中的复合模式

example class diagram

当谈到以后查询它我有种丢失。 例如,是否有一个很好的方法来查询所有没有祖先的复合材料?

我最初的想法是创建类似的东西用ActiveRecord

class Component < ActiveRecord::Base 
    belongs_to :childrenable, :polymorphic => true 
    has_and_belongs_to_many: composites 
end 

class Leaf < ActiveRecord::Base 
    has_many: components, :as => :childrenable 
end 

class Composite < ActiveRecord::Base 
    has_many: components, :as => :childrenable 
    has_and_belongs_to_many :components 
end 

将这项工作?我将如何建立这样一个列表(在视图以后f.ex.)?:

CompositeA 
    ->Item 
    ->CompositeB 
    ->ItemA 
    ->CompositeC 
    ->ItemA 
    ->ItemB 

我只是有点当涉及到查询丢失。这个问题有什么最佳实践吗?

回答

6

有实际的解决方案之前几个方面:

  • 图表和你的例子非常关键的方面有所不同。该图表明,Container和Children之间的关系是一对多关系。然而你的例子表明它是多对多的。
  • 它可以在主要使用单个模型的情况下解决。

许多一对多

它可以使用带本身许多一对多的关系来解决。

型号

class Component < ActiveRecord::Base 
    # Add as many attributes you need 
    attr_accessible :name 

    has_and_belongs_to_many :children, 
    :class_name => "Component", 
    :join_table => "children_containers", 
    :foreign_key => "container_id", 
    :association_foreign_key => "child_id" 

    has_and_belongs_to_many :containers, 
    :class_name => "Component", 
    :join_table => "children_containers", 
    :foreign_key => "child_id", 
    :association_foreign_key => "container_id" 

    # All Components that do not belong to any container 
    scope :roots, -> {where("not exists (select * from children_containers where child_id=components.id)")} 

    # All Components that have no children 
    scope :leaves, -> {where("not exists (select * from children_containers where container_id=components.id)")} 

    # Is this Component at root level 
    def root? 
    self.containers.empty? 
    end 

    # Is this Component at leaf level 
    def leaf? 
    self.children.empty? 
    end 

    # Notice the recursive call to traverse the Component hierarchy 
    # Similarly, it can be written to output using nested <ul> and <li>s as well. 
    def to_s(level=0) 
    "#{' ' * level}#{name}\n" + children.map {|c| c.to_s(level + 1)}.join 
    end 
end 

迁移

class CreateComponents < ActiveRecord::Migration 
    def change 
    create_table :components do |t| 
     t.string :name 

     t.timestamps 
    end 

    create_table :children_containers, :id => false do |t| 
     t.references :child 
     t.references :container 
    end 

    add_index :children_containers, :child_id 
    add_index :children_containers, [:container_id, :child_id], :unique => true 
    end 
end 

示例代码

["R1", "R2", "L1", "L2", "C1", "C2", "C3"].each {|n| Component.create(:name => n)} 

[ 
    ["R1", "C1"], 
    ["R2", "C2"], 
    ["R1", "C3"], 
    ["R2", "C3"], 
    ["C1", "L1"], 
    ["C2", "L2"], 
    ["C3", "L1"], 
    ["C3", "L2"] 
].each {|pair| p,c=pair; Component.find_by_name(p).children << Component.find_by_name(c)} 

puts Component.roots.map(&:name).to_s 
# ["R1", "R2"] 

puts Component.leaves.map(&:name).to_s 
# ["L1", "L2"] 

puts Component.find_by_name("R1").to_s 
# R1 
# C1 
#  L1 
# C3 
#  L1 
#  L2 

一个一对多

这是很简单的我在这种情况下。在组件模型中使用Ancestry(https://github.com/stefankroes/ancestry)。它将提供所有需要的操作。或者可以使用acts_as_tree来代替Ancestry。

让我知道你是否需要这样的示例代码。