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。

讓我知道你是否需要這樣的示例代碼。