Auto referencia de modelos (:has_many , :through=> )

•agosto 8, 2008 • 1 comentario

En la programación Orientada a Objetos es normal toparse con una composición entre objetos de la misma clase. Por ejemplo un persona tiene padres y tiene hijos, una clase se relaciona con sus super y sub clases, artículos cientificos tienen referencias a otros articulos científicos, etc, etc.

A la hora de realizar este mapeo en Ruby on Rails, las referencias de muchos a muchos sobre el mismo modelo presentan algunos dolores de cabeza. Estos dolores los pasé después de varias horas de googlear y leer algunos libros interesantes.

Para aquellos que tienen que realizar este problema, aca van los trucos para que puedan llevar a cabo lo más rápido posible la solución.

Diseño a resolver:

Clase autoreferenciada

Código


class CategoryRelationship < ActiveRecord::Base
belongs_to :super, :class_name =>»Category»
belongs_to :sub, :class_name =>»Category»
end

class CreateCategoryRelationships < ActiveRecord::Migration
def self.up
create_table :category_relationships do |t|
t.column :super_id, :integer
t.column :sub_id, :integer
t.timestamps
end
end

def self.down
drop_table :category_relationships
end
end

class Category < ActiveRecord::Base

has_many :generics,  :foreign_key=> ‘super_id’,
:class_name => ‘CategoryRelationship’,
:dependent => :destroy

has_many :subs, :through => :generics

has_many :specifics, :foreign_key => ‘sub_id’,
:class_name => ‘CategoryRelationship’,
:dependent => :destroy
has_many :supers, :through => :specifics

end

Explicación

Las relaciones quedan modeladas utilizando otro modelo. En este caso lo llamé CategoryRelationship y su correspondiente migration.

A tener en cuenta:

En CategoryRelationship, notar que los belongs_to hacen referencia a super y sub. En la migración las columnas se llaman igual con el «_id» al final. Como de alguna forma los nombres de las columnas de la tabla son puestas ad-hoc, utilizamos «:class_name=>»Category» » para indicar a que clase nos estamos refiriendo en los elementos de la migración.

En Category utilizamos el «has_many, :through=>» con un poco más de información. Siguiendo el ejemplo:

  • has_many :generics definimos un alias que nos representa a las super categorias.
  • :foreing_key => ‘super_id» con esto indicamos la clave foránea que hace referencia a nuestras super categorias. Tenemos que hacer esto porque como utilizamos alias, Rails no puede darse cuenta que super_id referencia a Categorias (recuerden que esa id son los id de categorias).
  • :class_name =>’CategoryRelationship’ con esto indicamos que trabajamos sobre los elementos del mapeo de nuestra relacion CategoryRelationship.
  • :dependent => ‘destroy’ indica que en el momento de borrar algun elemento de :subs, se elimina la entrada en la tabla donde está referenciado.
  • has_many :subs, :through: generics Ahora si podemos hacer el has_many necesario para referenciar a nuestros subs (subcategorias). Notar que el nombre subs tiene relacion con el belongs_to de CategoryRelationship, y con el sub_id.
  • Para los sup