I'm trying to implement the typical gof composite pattern:
example class diagram
I'm kind of lost when it comes to querying it later on. For example would there be a nice way to query all Composites without any ancestors?
My initial idea was to create something like that with 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  
Would that work? How would I build a list like that (in the View later on f.ex.)?:
CompositeA  
  ->Item
  ->CompositeB
    ->ItemA
  ->CompositeC
    ->ItemA
    ->ItemB  
I'm just a bit lost when it comes to the query. Are there any best practices for this problem?
There are couple of aspects before the actual solution:
Many-to-many
It can be solved using a many-to-many relationship with itself.
Model
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
Migration
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
Sample code
["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
One-to-many
It is much simpler in this case. Use Ancestry (https://github.com/stefankroes/ancestry) in Component model. It will provide all the operations that are needed. Alternatively acts_as_tree can be used instead of Ancestry.
Let me know if you need sample code for this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With