【问题标题】:Implementing the composite pattern in Ruby on Rails在 Ruby on Rails 中实现复合模式
【发布时间】:2013-07-11 20:43:38
【问题描述】:

我正在尝试实现典型的 gof 复合模式:

example class diagram

稍后查询它时,我有点迷茫。 例如,是否有一种很好的方法来查询没有任何祖先的所有 Composite?

我最初的想法是用 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  

当谈到查询时,我有点迷茫。有没有解决这个问题的最佳做法?

【问题讨论】:

    标签: ruby-on-rails design-patterns polymorphism rails-activerecord composite


    【解决方案1】:

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

    • 图表和您的示例在一个非常关键的方面有所不同。该图表明 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
    

    一对多

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

    如果您需要此示例代码,请告诉我。

    【讨论】:

    • 这是一个非常有用的答案!
    猜你喜欢
    • 2021-06-11
    • 1970-01-01
    • 1970-01-01
    • 2015-04-23
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多