::Rails.to_spanish

14/01/08

HABTM: De la teoría a la práctica.

ActiveRecord (AR) es la Capa de Mapeo Objeto-Relacional de Rails. Nos permite manejar todos los aspectos de las bases de dato de la aplicación: conexión a la(s) base(s) de dato, mapeo tabla-clase y manipulación de datos.

Con las presentaciones hechas, hago un salto para ocuparme de un aspecto particular de AR: has_and_belongs_to_many o HABTM. Mediante esta declaración en los modelos de la aplicación, AR automáticamente provee una serie de métodos para implementar la relación de muchos a muchos entre dos clases. Veamos un ejemplo:

Tenemos dos clases: Torneo y Equipo, y nos interesa registrar los torneos en los que participa cada equipo, y al mismo tiempo los equipos que participan en un torneo dado.

Veamos cómo:

1º Paso: definir los modelos y declarar la relación:

#app/models/torneo.rb

class Torneo < ActiveRecord::Base

has_and_belongs_to_many :equipos

...

end

#app/models/equipo.rb

class Equipo < ActiveRecord::Base

has_and_belongs_to_many :torneos

...

end


Como puede verse, la declaración se hace utilizando los nombres en plural. Hasta acá, sencillo. Ya hemos dicho a AR que "un torneo puede tener muchos equipos, y un equipo puede participar de muchos torneos".


2º paso: la tabla donde llevaremos la cuenta de que equipo(s) participa(n) en que torneo(s). Para eso, Rails define por convención una tabla de unión con las siguientes características:

1.Rails buscará una tabla con el nombre de cada clase, ordenados alfabéticamente y en plural, separados por un guión bajo (_).
2.La tabla sólo podra contener dos campos, correspondientes al id de cada objeto de las clases participantes. Esto puede resultar una limitación si queremos registrar información particular de cada instancia torneo-equipo, cosa que no esta soportada por HABTM.
3.Esta tabla no tiene campo "id", ya que esto rompería la integridad relacional. Las tuplas torneo-equipo deben ser únicas.


Nuestra migración luce mas o menos así:

class CreateEquiposTorneos < ActiveRecord::Migration
def self.up
create_table :equipos_torneos, :id => false do |t|
t.integer :equipo_id, :torneo_id
end
add_index :equipos_torneos, [:equipo_id]
add_index :equipos_torneos, [:torneo_id]
end

def self.down
drop_table :equipos_torneos
end
end


Algunos detalles:

La nueva sintaxis para las migraciones en Rails 2.0 hace que podamos definir en una sola linea los campos de la tabla (lindo!).
Definimos indices para la tabla ya que estaremos haciendo muchas consultas, y esto optimizara en rendimiento.
Como toda migración, debe ser reversible.


3º paso (final): Luego de aplicar la migración, tendremos la tabla vacía. Suponiendo que ya existen equipos y torneos en nuestra aplicación, registramos la relación, mediante el método append (<<).

Probamos en la consola, agregar los primeros 20 equipos al primer torneo de la BDD:


>> @torneo = Torneo.find(:first)
=> #<Torneo id=963...
>>@equipos = Equipo.find(:all, :limit => 20)
=> [#<Equipo id=5252... ]
>> @torneo.equipos << @equipos
=> [#<Equipo id=5252... ]
>>@torneo.save
true
>>@torneo.equipos
=> [#<Equipo id=5252... ]
>> @equipo = Equipo.find(:first)
=> #<Equipo id = 5412..
>>@equipo.torneos
=> [#<Torneo id=963...]


Eso es todo para empezar.

1 comentarios.:

Anónimo dijo...

Hola. Felicitaciones por el blog.
Soy nuevo en esto de Rails. Pero estoy trabajando en eso y siempre probando cosas nuevas, eso es lo magico de Rails :P

Estoy desarrollando una intranet para un consultorio, muy basica, donde su home page es una lista de pacientes lo cual tiene campos que provienen de otras entidades con sus abm's, como puede ser tipos de documentos, obras sociales, hospitales.

Estoy desarrollando un pequeño search engine. He encontrado tantas opciones pero no es para tanto lo que necesito. Ni ferret ni otro full-text-search. Solo necesito un campo para buscar por apellido y filtrar por obra social.

Ese campo esta en el index de pacientes. y el metodo "search" lo puse en su modelo. En el railscast episode 111 habla de generar un search resource, pero tampoco es lo que necesito ya que debe ser lo mas automatico posible: desde un listado tipear un apellido o seleccionar una obra social y filtrar automaticamente.
Tengo algunos problemas con el pasaje de parametros.
Pero antes sospecho que estoy encarando mal el tema de las relaciones.
Cree un habtm entre pacientes y obras sociales, y sospecho que es demasiado e innecesario. Cada paciente tiene una obra social, y las obras sociales no tienen por que tener asociados sus pacientes...ya que no lo necesito.

Mi pregunta es si me alcanza con un has_many o un has_one, y como seria la manera de pasarle al modelo Paciente los parametros de un select_tag en el index de pacientes listandome todas las obras sociales para buscar en mi tabla de pacientes todos aquellos que tengan el obra_social_id correspondiente.

desde ya gracias.
marcio