2009-04-16 7 views
5

Creo que mi pregunta se describe mejor como un ejemplo. Digamos que tengo un modelo simple llamado "Cosa" y tiene algunos atributos que son tipos de datos simples. Algo como ...¿Mejores prácticas para asociaciones múltiples con la misma clase en Rails?

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

Eso no es difícil. La tabla db contendrá tres columnas con esos tres atributos y puedo acceder a ellos con algo como @ thing.foo o @ thing.bar.

Pero el problema que estoy tratando de resolver es ¿qué sucede cuando "foo" o "goo" ya no se pueden contener en un tipo de datos simple? Supongamos que foo y goo representan el mismo tipo de objeto. Es decir, ambas son instancias de "Whazit" solo con datos diferentes. Así que ahora cosa podría tener este aspecto ...

Thing 
    - bar:int 

Pero ahora hay un nuevo modelo llamado "Whazit" que tiene este aspecto ...

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

Hasta ahora todo esto es bueno. Ahora aquí es donde estoy atascado. Si tengo @thing, ¿cómo puedo configurarlo para referirme a mis 2 instancias de Whazit por nombre (para el registro, la "regla de negocios" es que cualquier Cosa siempre tendrá exactamente 2 Whazits)? Es decir, necesito saber si el Whazit que tengo es básicamente foo o goo. Obviamente, no puedo hacer @ thing.foo en la configuración actual, pero sería ideal.

Mi primer pensamiento es agregar un atributo de "nombre" a Whazit para que pueda obtener los Whatzits asociados con mi @thing y luego elegir el Whazit que quiero por su nombre de esa manera. Eso parece feo sin embargo.

¿Hay una manera mejor?

Respuesta

8

Hay varias formas de hacerlo. En primer lugar, podría configurar dos belongs_to/has_one relaciones:

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 

Otra opción que es un poco más limpio, sino también más de un dolor cuando se trata de plugins, etc., es la herencia de una sola mesa. En este caso, tiene dos clases, Foo y Goo, pero ambas se mantienen en la tabla de whazits con una columna de tipo que las 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 

En ambos casos se puede hacer cosas como @thing.foo y @thing.goo. Con el primer método, que había necesidad de hacer cosas como:

@thing.foo = Whazit.new 

mientras que con el segundo método que puede hacer cosas como:

@thing.foo = Foo.new 

STI tiene su propio conjunto de problemas, sin embargo, especialmente si está utilizando complementos y gemas más antiguos. Por lo general, es un problema con el código que llama al @object.class cuando lo que realmente quieren es @object.base_class. Es fácil parchar cuando sea necesario.

2

Su solución sencilla con la adición de un "nombre" no tiene que ser feo:

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

De hecho, es bastante similar a cómo funciona ITS, excepto que no es necesario una clase separada.

Lo único que tendrá que tener en cuenta es establecer este nombre cuando asocie un whazit. Eso puede ser tan simple como:

def foo=(assoc) 
    assos.name = 'foo' 
    super(assoc) 
end 
Cuestiones relacionadas