2009-04-16 3 views
5

Je pense que ma question est mieux décrite comme un exemple. Disons que j'ai un modèle simple appelé "Chose" et il a quelques attributs qui sont de simples types de données. Quelque chose comme ...Meilleures pratiques pour plusieurs associations avec la même classe dans Rails?

Thing 
    - foo:string 
    - goo:string 
    - bar:int 

Ce n'est pas difficile. La table db contiendra trois colonnes avec ces trois attributs et je peux y accéder avec quelque chose comme @ thing.foo ou @ thing.bar.

Mais le problème que j'essaie de résoudre est ce qui se passe quand "foo" ou "goo" ne peut plus être contenu dans un simple type de données? Supposons que foo et goo représentent le même type d'objet. C'est-à-dire, ils sont les deux instances de "Whazit" juste avec des données différentes. Alors maintenant chose pourrait ressembler à ceci ...

Thing 
    - bar:int 

Mais maintenant il y a un nouveau modèle appelé « Whazit » qui ressemble à ceci ...

Whazit 
    - content:string 
    - value:int 
    - thing_id:int 

Jusqu'à présent, tout cela est bon. Maintenant, voici où je suis coincé. Si j'ai @ @, comment puis-je le configurer pour faire référence à mes 2 instances de Whazit par nom (Pour l'anecdote, la "règle métier" est que toute chose aura toujours exactement 2 Whazits)? C'est, j'ai besoin de savoir si le Whazit que j'ai est fondamentalement foo ou goo. Évidemment, je ne peux pas faire @ thing.foo dans la configuration actuelle, mais je dirais que c'est idéal. Ma première pensée est d'ajouter un attribut "name" à Whazit afin que je puisse obtenir les Whatzits associés à mon @thing et ensuite choisir le Whazit que je veux nommer ainsi. Cela semble moche cependant.

Y a-t-il un meilleur moyen?

Répondre

8

Il y a plusieurs façons de faire cela. Tout d'abord, vous pouvez configurer deux belongs_to/has_one relations:

things 
    - bar:int 
    - foo_id:int 
    - goo_id:int 

whazits 
    - content:string 
    - value:int 

class Thing < ActiveRecord::Base 
    belongs_to :foo, :class_name => "whazit" 
    belongs_to :goo, :class_name => "whazit" 
end 

class Whazit < ActiveRecord::Base 
    has_one :foo_owner, class_name => "thing", foreign_key => "foo_id" 
    has_one :goo_owner, class_name => "thing", foreign_key => "goo_id" 

    # Perhaps some before save logic to make sure that either foo_owner 
    # or goo_owner are non-nil, but not both. 
end 

Une autre option qui est un peu plus propre, mais aussi plus d'une douleur lorsqu'ils traitent avec des plugins, etc., est l'héritage unique table. Dans ce cas, vous avez deux classes, Foo et Goo, mais elles sont toutes deux conservées dans la table whazits avec une colonne de type qui les distingue.

things 
    - bar:int 

whazits 
    - content:string 
    - value:int 
    - thing_id:int 
    - type:string 

class Thing < ActiveRecord::Base 
    belongs_to :foo 
    belongs_to :goo 
end 

class Whazit < ActiveRecord::Base 
    # .. whatever methods they have in common .. 
end 

class Foo < Whazit 
    has_one :thing 
end 

class Goo < Whazit 
    has_one :thing 
end 

Dans les deux cas, vous pouvez faire des choses comme @thing.foo et @thing.goo. Avec la première méthode, vous aurez besoin de faire des choses comme:

@thing.foo = Whazit.new 

alors qu'avec la seconde méthode, vous pouvez faire des choses comme:

@thing.foo = Foo.new 

STI a son propre ensemble de problèmes, cependant, en particulier si vous utilisez des plugins et des gems plus anciens. Habituellement, c'est un problème avec le code appelant @object.class lorsque ce qu'ils veulent vraiment, c'est @object.base_class. Il est assez facile de corriger si nécessaire.

2

Votre solution simple avec l'ajout d'un « nom » n'a pas besoin d'être laid:

class Thing < ActiveRecord::Base 
    has_one :foo, :class_name => "whazit", :conditions => { :name => "foo" } 
    has_one :goo, :class_name => "whazit", :conditions => { :name => "goo" } 
end 

En fait, il est tout à fait semblable à la façon dont fonctionne STI, à moins que vous n'avez pas besoin d'une catégorie distincte.

La seule chose à surveiller est de définir ce nom lorsque vous associez un whazit. Cela peut être aussi simple que:

def foo=(assoc) 
    assos.name = 'foo' 
    super(assoc) 
end 
Questions connexes