4

私は典型的な gof 複合パターンを実装しようとしています:

クラス図の例

後でそれを照会することになると、私はちょっと迷っています。たとえば、先祖なしですべてのコンポジットを照会する良い方法はありますか?

私の最初のアイデアは、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  

クエリに関しては、少し迷っています。この問題に対するベスト プラクティスはありますか?

4

1 に答える 1

7

実際のソリューションの前にいくつかの側面があります。

  • 図とあなたの例は、非常に重要な点で異なります。ダイアグラムは、コンテナーと子の間の関係が 1 対多であることを示しています。ただし、あなたの例では、多対多であることを示しています。
  • 主に単一のモデルを使用して、両方のケースで解決できます。

多対多

それ自体との多対多の関係を使用して解決できます。

モデル

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

一対多

この場合ははるかに簡単です。Component モデルでAncestry ( https://github.com/stefankroes/ancestry ) を使用します。必要なすべての操作を提供します。Ancestry の代わりに act_as_tree を使用することもできます。

このためのサンプル コードが必要な場合はお知らせください。

于 2013-07-19T22:59:34.397 に答える