Tuesday 22 May 2007

Active Record = Join Tables

En uno de los primeros POSTs de este blog, hablaba de las relaciones entre tablas a través de los modelos de ActiveRecord e intentaba (conseguía?)explicar un modo para entender como definir correctamente estas relaciones. Me gustaría ampliar un poco esto para analizar una de las relaciones que creo que no he tratado correctamente, las relaciones N a N a través de Join tables.

Estas relaciones se dan cuando tenemos dos tablas relacionadas entre si a través de una tercera tabla. En esta tabla, se suele sustituir la clave primaria por la unión de los índices de las dos tablas relacionadas, si bien esto no es obligatorio.

Rails permite definir estas relaciones de una manera muy sencilla usando en ambos modelos la relación has_and_belongs_to_many :tabla (vaya nombrecito). Usando esta sencilla fórmula, seremos capaces de acceder desde un objeto de uno de los modelos a la lista de elementos del otro modelo de una forma muy sencilla. Por ejemplo, si relacionamos Authors y Articles a través de la Join Table Publication, podremos hacer algo así.

autor = Author.find(:first)
lista_articulos = autor.articles

En lista_articulos tendremos un array de objetos Article que se han asociado a Author a través de la tabla Publication. Tendremos que seguir ciertas convenciones que rails nos indica como que el nombre físico de la join table este compuesto por los nombres de las tablas enlazadas en minúscula y en orden alfabético. Si queréis ver la especificación, visitad el API. Facil no???

Pero hay un problema. Imaginemos que esta tabla Publication tiene campos adicionales que no son los índices de las tablas referenciadas. Rails no nos pone ninguna pega para acceder a esta información. De hecho, la incluye en el array que hemos obtenido anteriormente. El problema es que estos campos son de solo lectura. No se pueden modificar. La aproximación que se tomaba era la utilización de delete para borrar una entrada antigua y del método push_with_attributes(object, attributes) para introducir una tupla nueva en la join table. PERO ESTE METODO ESTA DEPRECATED. NO LO USEIS.

Afortunadamente, este problema ya esta resuelto. El truco (no es un truco, pero vaya) es usar una doble relación has_many en ambas tablas a relacionar usando el modificador :through. Ah, y creando un nuevo modelo que nos identifique la join table. Esto es muchísimo mejor, ya que recuperamos toda la funcionalidad que nos proporciona el tener una tabla identificada por un modelo. También eliminamos la necesidad de llamar a la tabla con nombres raros (autor_articulo en el caso anterior)

Bueno, como definiríamos esto en la relación anterior???? Marchando.

class Author << ActiveRecord::Base
has_many :publications
has_many :articles, :through => :publications
end

class Publication << ActiveRecord::Base
belongs_to :author
belongs_to :article
end

class Article<< ActiveRecord::Base
has_many :authors
has_many :authors, :through => :publications
end

Que os parece??? tenemos ahora acceso a tooooodos los métodos de inserción, etc... De la tabla Publication y podemos trabajar de un modo mucho más sencillo.

1 comment:

Josue said...

Creo que en la parte de:

class Article<< ActiveRecord::Base
has_many :authors
has_many :authors, :through => :publications
end

deberia ser:

class Article<< ActiveRecord::Base
has_many :publications #aca el cambio
has_many :authors, :through => :publications
end

O estoy mal??