Mi guía para especificar las relaciones en los modelos: Active Record – RubyOnRails


Beer on RailsEn esta guía de referencia, quiero hablar sobre las relaciones en los modelos de RubyOnRails, que además considero que hay que tener las ideas y los conceptos muy claros para poder trabajar con soltura. Nuestro protagonista de esta sesión es Active Record. Cuando venimos de otros lenguajes y modelados de Bases de Datos en los que uno mismo tiene que pensar en muchos detalles y como todo, en un primer encuentro, incluso en alguno que otro más con Active Record, tienes que cambiar la forma de pensar y de aprender. Pues como digo, hay que cambiar la forma de pensar y enfocar las relaciones para que no tengas confusiones y esto te lleve a problemas, hay pensar que estamos tratando con objetos de modelo y sus relaciones y no con filas y columnas ya que parte de la magia de ORM es que puede convertir las relaciones de clave externa hacia otra tabla referenciada Foreign Key en mapeos entre objetos de alto nivel o un lenguaje natural y ayudar a que Active Record entienda las relaciones que queremos abordar en nuestra Base de Datos.

Para eso tenemos las relaciones mediante claves externas que hacen que ORM entienda como se ven entra ellas y como podemos nosotros circular entre esas relaciones. Pongamos un ejemplo, si tenemos una tabla Productos y otra Categorías en la que necesitamos una tercera tabla denominada “Tabla de Unión”, para relacionar ambos modelos de Productos y Categorías en la que tendremos por convención las claves id de ambas tablas, product_id y category_id, para que pueda relacionarlas RubyOnRails con ORM. La convención de nomenclatura Active Record dice que la columna clave externa debería llamarse después del nombre de la tabla destino y añadiendo el guión bajo más el id, tal como hemos visto anteriormente.

Es importante pensar en los índices que creemos para no ralentizar las búsquedas y por otro lado, en el caso de que utilicemos otros nombres que no sea capaz de reconocer automáticamente, deberemos declarar las Foreign Key, para que Rails las pueda encontrar y facilitar el trabajo. Vamos a los tipos de relaciones

  • belongs_to: declara que la clase tiene una relación de padre con la clase que contiene la declaración.
  • has_one: declara que una clase determinada es un hijo de su clase con una clave externa foreign key
  • has_many: define un atributo que se comporta como una colección de los objetos hijo, accediendo a lo hijos como un array, para encontrar o añadirlo.
  • has_one: through define una conexión uno-a-uno a través de un moldeo que hace de conexión entre otros dos modelos
  • has_many: through define una conexión uno-a-uno a través de un moldeo que hace de conexión entre otros dos modelos
  • has_and_belongs_to_many: crea un atributo que es una colección.

Para mostrar los ejemplos de las relaciones utilizo las fuentes de las Guías de Rails

Declaración belongs_to

La asociación belongs_to establece una relación uno-a-uno con otro modelo, una relación de padre con la que contiene la declaración. Podemos pensar en un momento determinado que podemos necesitar cambiar el comportamiento y adoptar un nuevo comportamiento pasando a belongs_to una hash de con aquellos nuevos comportamientos, por ejemplo:

class LineItem < ActiveRecord::Base belongs_to

belongs_to :paid_order

:class_name => “Order“,

:foreign_key => “order_id“,

:conditions => “paid_on is not null

end

Declaración has_one

Declara que una clase determinada es hijo de su clase, es decir, que la tabla que se corresponde con la clase del hijo tendrá referencia de la clave externa a la clase que contiene la declaración, por ejemplo la Clase Order tiene un declaración  has_one hacia invoice, siendo el padre Order y el hijo Invoice con una clave order_id, además define el mismo conjunto de métodos, con una clase destino Invoice y una tabla destino invoices.

class Order < ActiveRecord::Basehas_one

has_one :invoice

end

También podemos modificar las opciones de comportamiento con :class_name => “Order“, :foreign_key => “order_id“, :conditions => “paid_on is not null“, pero ahora echa un vistazo a la parte en la que le podemos decir a Active Record que hay que hacer con aquellas filas hijo cuando se eliminen las filas  de la tabla padre. Para eso tenemos la opción :dependent en las que tendremos :destroy en la que al mismo tiempo se eliminan la fila hijo y el del padre, :delete en la se puede eliminar la fila del hijo sin hacer una llamada al método de destrucción el mismo tiempo que se destruye la fila del padre y finalmente :nullify que la fila hijo se queda sin padre al mismo tiempo que se destruye la fila padre.

Declaración has_many

La declaración has_many define un atributo de tiene un comportamiento como una colección de objetos hijo,has_many por ejemplo una clase Order con una declaración has_many :line_items se podría acceder a los hijos como un array, encontrar a un hijo y añadir nuevos. Podemos igualmente modificar los valores de comportamiento de Active Record con los mismos parámetros que hemos utilizado en las otras declaraciones anteriores, con :class_name => “Order“, :foreign_key => “order_id“, :conditions => “paid_on is not null” así como :dependent para decir a Active Record que debe hacer, con los mismos valores de :destroy, :nullify y delete_all, pero la aplicación en este caso lo realiza sobre todas las filas hijo.

Veamos las relaciones has_one / belongs_to

Una asociación una a una o una a ninguna, se implementa con una clave externa en una fila de una tabla para referenciar como mucho una fila a otra tabla. Por ejemplo si tenemos una tabla de clientes (clients) y otra de cuentas (orders) en las que ambas no podemos tener más que una única asociación, debemos declarar en los modelos considerando quién va a ser el objeto padre y quién el objeto hijo, pero ¿cómo definimos esta premisa entonces?, pues muy sencillo, lo que tenemos que responder es a la pregunta ¿quién tendrá la clave foreign key de ambas? si la repuesta es nuestro modelo de clientes (Client) tendrá un campo order_id que será nuestra clave foreign key.

Entonces este modelo padre class Client < ActiveRecord::Base tendrá la declaración belong_to : order ya que tinen también la clave foreign_key y en el modelo hijoclass Order < ActiveRecord::Base tendrá la declaración de has_one :client

Relaciones belongs_to / has_many

Las relaciones de una a varias nos permite representar una colección de objetos, es decir un pedido order, podría tener varios objetos en línea line_itmes. También en este caso tendremos que preguntar y obtener una respuesta para colocar los elementos relacionados correctamente, es decir, ¿quién contiene la colección de objetos hijo? en este caso el objeto padre será orders por lo que en la declaración será:

modelo padre class Order < ActiveRecord::Base tendrá has_many :line_items y en el modelo hijo class LineItem < ActiveRecord::Base tendrá belongs_to :line_items y la declaración de la clave externa foreign_key con order_id.

Relaciones varias a varias has_and_belongs_to_many

Para hacer una clasificación de los productos de la que puede pertenecer a varias categorías y cada categoría puede contener varios productos, en la que debemos declarar en los modelos lo siguiente:

en el modelo Category <  ActiveRecord::Base contiene has_and_belongs_to_many :products y en el modelo class Product < ActiveRecord::Base contiene has_and_belongs_to_many :categories, pero para ello necesitaremos una tabla de unión que contiene pareja de claves externas que vincula ambas tablas, con lo que Active Record asume que la unión, es la concatenación de los nombres de ambas tablas en orden alfabético categories y products con lo que podrá buscar una tabla de unión que se llame categories_products.

Las asociaciones has_one y has_many through

Este tipo de asociaciones corresponden a los casos en los que tenemos dos modelos y necesitamos tener una relación a través de un tercer modelo. Imagina que tenemos una serie de artículos que leen dichos artículos o publicaciones, los usuarios, si declaramos en el modelo Article como has_many: readings, en el modelo User como has_many:readings y el modelo Reading con dos belongs_to hacia :article y :user, hemos perdido algo con respecto a una declaración has_and_belongs_to_many ya que si intentamos preguntar por un artículo y el usuarios que lo ha leído, no podremos, aquí es cuando through entra en juego, ya que le indica a Rails que puede usar la tabla readings para poder acceder desde un artículo a varios usuarios que lo han leído y además Rails crea la sentencia SQL por nosotros que hace falta para que devuelva el resultado de todas las referencias de usuarios de la tabla readers en las que se referencia al artículo. Por tanto cuando en el modelo Article tenemos las declaraciones de has_many tanto de :readings como de :users, siendo esta última con through => :readings, le estamos indicando a Active Record que haga uso de la asociación has_many :readings para encontrar un modelo llamado Reading. El nombre que le damos de :users le indica a Active Record que a través del atributo user_id es el que tiene que usar o lo podemos modificar utilizando :source. Podemos hacer un pequeño cambio para hacerlo un poco más claro y es llamarles readers y lo hacemos así:

antes teníamos en el Modelo Article: has_many :users, :through => :readings

ahora podemos hacer en el modelo Article: has_many :readers, :through => readings, :source => user

Pero como sigue siendo una declaración has_many podemos dar un paso más en este sentido y declarar: has_many : happy_users, :through => :readings, source => :users, :conditions => ‘readings.rating >= 6 ‘ para que nos devuelva todos los usuarios que nos han otorgado en un artículo igual o más de 6 en las calificaciones positivas. Sabemos que has_many :through son colecciones que devuelve y es por la relación de unión ya que si un usuario lee 5 veces un artículo y preguntamos por la lista de usuarios que han leído ese artículo, recibiremos 5 copias. Por tanto podemos asociar al final :uniq => true para evitar esto o anular la parte de SQL que ha heredado Active Record añadiendo :select => “distint users.*”

Un ejemplo de asociación through:

has_many_through

Las asociaciones polimórficas

En estos casos las asociaciones polimórficas son aquellas a las que pertenecen a más de un modelo distinto, en una sola asociación. Por ejemplo, es posible tener un modelo comentario que pertenece tanto a un modelo servicio o un modelo de producto. Observa la siguiente asociación polimórfica:
polymorphic
Los Callbacks

Los callbacks son una parte muy interesante en la que conectar en el ciclo de vida de los objetos Active Record, esto no permite trabajar con los objetos en puntos concretos como before y after en casos de add y remove que se activan por eventos en el ciclo de vida de una colección.

En las asociaciones hay métodos y opciones que se soportan. en la guía de Rails se detalla todo muy bien y no voy a repetirlo, pero dejo reflejadas las opciones.

La asociación belongs_to soporta estas opciones disponibles:

:autosave
:class_name
:conditions
:counter_cache
:dependent
:foreign_key
:include
:inverse_of
:polymorphic
:readonly
:select
:touch
:validate

La asociación has_one association soporta estas opciones disponibles:

:as
:autosave
:class_name
:conditions
:dependent
:foreign_key
:include
:inverse_of
: order
:primary_key
:readonly
:select
:source
:source_type
:through
:validate

La asociación has_many soporta estas opciones disponibles:

:as
:autosave
:class_name
:conditions
:counter_sql
:dependent
:extend
:finder_sql
:foreign_key
:group
:include
:inverse_of
:limit
: offset
: order
:primary_key
:readonly
:select
:source
:source_type
:through
:uniq
:validate

La asociación has_and_belongs_to_many soporta estas opciones disponibles:

:association_foreign_key
:autosave
:class_name
:conditions
:counter_sql
:delete_sql
:extend
:finder_sql
:foreign_key
:group
:include
:insert_sql
:join_table
:limit
: offset
: order
:readonly
:select
:uniq
:validate

Quedan más temas por abordar de Active Record, pero ya lo haré en otros post. Aún así, como he dicho enteriormente, si quieres ampliar más información sobre todo lo aquí tratado, puedes siempre utilizar las guías de Rails

3 comentarios en “Mi guía para especificar las relaciones en los modelos: Active Record – RubyOnRails

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s