2

Je suis en train de mettre en œuvre le modèle composite typique GOF:mise en œuvre du modèle composite en Ruby on Rails

example class diagram

Je suis un peu perdu quand il vient à interroger plus tard. Par exemple, y aurait-il un bon moyen d'interroger tous les composites sans aucun ancêtre?

Mon idée initiale était de créer quelque chose comme ça avec 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 

Est-ce que ça marche? Comment puis-je construire une telle liste (dans la vue plus tard p.ex) ?:

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

Je suis juste un peu perdu quand il vient à la requête. Existe-t-il des meilleures pratiques pour ce problème?

Répondre

6

Il y a deux aspects avant que la solution réelle:

  • Le diagramme et votre exemple diffèrent dans un aspect très critique. Le diagramme suggère que la relation entre le conteneur et les enfants est un-plusieurs. Cependant, votre exemple montre qu'il est beaucoup-à-plusieurs.
  • Il peut être résolu dans les deux cas en utilisant principalement un seul modèle.

Many-to-many

Il peut être résolu en utilisant un grand nombre à plusieurs avec elle-même.

Modèle

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 

Exemple de 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

Il est beaucoup plus simple i Dans ce cas. Utilisez Ancestry (https://github.com/stefankroes/ancestry) dans le modèle de composant. Il fournira toutes les opérations nécessaires. Alternativement, acts_as_tree peut être utilisé à la place d'Ancestry. Faites-moi savoir si vous avez besoin d'un code d'échantillon pour cela.