Estas son algunas de las cosas que debe saber para hacer un uso eficiente de las asociaciones Active Record en sus aplicaciones de Rails:
- Control del almacenamiento en caché
- Evitar colisiones de nombres
- Actualización del esquema
- Controlar el alcance de la asociación
- Asociaciones bidireccionales
Todos los métodos de asociación se construyen alrededor del almacenamiento en caché, lo que mantiene el resultado de la consulta más reciente disponible para operaciones adicionales. La caché se comparte incluso entre los métodos. Por ejemplo:
author.books # recupera los libros de la base de datos
author.books.size # utiliza la copia en caché de libros
author.books.empty? # utiliza la copia en caché de libros
Pero ¿qué pasa si desea volver a cargar la caché, porque los datos podrían haber sido cambiados por alguna otra parte de la aplicación? Simplemente llame a "reload" en la asociación:
author.books # recupera los libros de la base de datos
author.books.size # utiliza la copia en caché de libros
author.books.reload.empty? # Descarta la copia en caché de libros
# Y vuelve a la base de datos
No eres libre de usar cualquier nombre para tus asociaciones. Debido a que crear una asociación agrega un método con ese nombre al modelo, es una mala idea darle a una asociación un nombre que ya está utilizado para un método de instancia de ActiveRecord::Base
. El método de asociación invalidaría el método base y rompería las cosas. Por ejemplo, attributes
o connection
son malos nombres para las asociaciones.
Las asociaciones son extremadamente útiles, pero no son mágicas. Usted es responsable de mantener el esquema de la base de datos para que coincida con sus asociaciones. En la práctica, esto significa dos cosas, dependiendo del tipo de asociaciones que está creando. Para asociaciones belongs_to
es necesario crear claves foráneas y para asociaciones has_and_belongs_to_many
es necesario crear la tabla de unión (join table) adecuada.
Cuando declara una asociación belongs_to
, debe crear claves foráneas según corresponda. Por ejemplo, considere este modelo:
class Book < ApplicationRecord
belongs_to :author
end
Esta declaración debe ser respaldada por la declaración de clave foránea adecuada en la tabla de libros:
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :books do |t|
t.datetime :published_at
t.string :book_number
t.integer :author_id
end
end
end
Si crea una asociación algún tiempo después de crear el modelo subyacente, debe recordar crear una migración add_column
para proporcionar la clave foránea necesaria.
Es una buena práctica agregar un índice en la clave foránea para mejorar el rendimiento de las consultas y una restricción de clave foránea para garantizar la integridad de los datos referenciales:
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :books do |t|
t.datetime :published_at
t.string :book_number
t.integer :author_id
end
add_index :books, :author_id
add_foreign_key :books, :authors
end
end
Si crea una asociación has_and_belongs_to_many
, debe crear explícitamente la tabla de unión. A menos que el nombre de la tabla de unión se especifique explícitamente utilizando la opción: join_table
, Active Record crea el nombre utilizando el léxico de los nombres de clase. Por lo tanto, una combinación entre los modelos autor y libros dará el nombre de tabla de unión predeterminada de authors_books
porque "a" supera a "b" en el orden alfabético.
La precedencia entre los nombres de modelo se calcula utilizando el operador <=>
para String. Esto significa que si las cadenas son de diferentes longitudes y las cadenas son iguales en comparación con la longitud más corta, entonces la cadena más larga se considera de mayor precedencia léxica que la más corta. Por ejemplo, se podría esperar que las tablas paper_boxes
y papers
generen un nombre de tabla de unión de papers_paper_boxes
debido a la longitud del nombre paper_boxes
, pero de hecho genera un nombre de tabla de unión de paper_boxes_papers
( Porque el subrayado '_' es lexicográficamente menor que 's' en codificaciones comunes).
Independientemente del nombre, debe generar manualmente la tabla de unión con una migración adecuada. Por ejemplo, considere estas asociaciones:
class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
Estos deben ser respaldados por una migración para crear la tabla assemblies_parts
. Esta tabla debe crearse sin una clave primaria:
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0]
def change
create_table :assemblies_parts, id: false do |t|
t.integer :assembly_id
t.integer :part_id
end
add_index :assemblies_parts, :assembly_id
add_index :assemblies_parts, :part_id
end
end
Pasamos id :false
a create_table
porque esa tabla no representa un modelo. Eso es necesario para que la asociación funcione correctamente. Si observas cualquier comportamiento extraño en una asociación has_and_belongs_to_many
como ID de modelo mutilado, o excepciones sobre IDs en conflicto, es probable que te hayas olvidado de ese detalle.
También puede utilizar el método create_join_table
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0]
def change
create_join_table :assemblies, :parts do |t|
t.index :assembly_id
t.index :part_id
end
end
end
De forma predeterminada, las asociaciones buscan objetos sólo dentro del ámbito del módulo actual. Esto puede ser importante cuando declara los modelos Active Record dentro de un módulo. Por ejemplo:
module MyApplication
module Business
class Supplier < ApplicationRecord
has_one :account
end
class Account < ApplicationRecord
belongs_to :supplier
end
end
end
Esto funcionará bien, ya que tanto la clase proveedor como la clase cuenta se definen dentro del mismo ámbito. Pero lo siguiente no funcionará, ya que el proveedor y la cuenta se definen en ámbitos diferentes:
module MyApplication
module Business
class Supplier < ApplicationRecord
has_one :account
end
end
module Billing
class Account < ApplicationRecord
belongs_to :supplier
end
end
end
Para asociar un modelo con un modelo en un namespace
diferente, debe especificar el nombre completo de la clase en la declaración de asociación:
module MyApplication
module Business
class Supplier < ApplicationRecord
has_one :account,
class_name: "MyApplication::Billing::Account"
end
end
module Billing
class Account < ApplicationRecord
belongs_to :supplier,
class_name: "MyApplication::Business::Supplier"
end
end
end
Es normal que las asociaciones trabajen en dos direcciones, requiriendo la declaración en dos modelos diferentes:
class Author < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :author
end
Active Record intentará identificar automáticamente que estos dos modelos comparten una asociación bidireccional basada en el nombre de la asociación. De esta manera, Active Record sólo cargará una copia del objeto Author
, haciendo que su aplicación sea más eficiente y evite datos incoherentes:
a = Author.first
b = a.books.first
a.first_name == b.author.first_name # => true
a.first_name = 'David'
a.first_name == b.author.first_name # => true
Active Record admite la identificación automática para la mayoría de las asociaciones con nombres estándar. Sin embargo, Active Record no identificará automáticamente asociaciones bidireccionales que contengan cualquiera de las siguientes opciones:
:conditions
:through
:polymorphic
:class_name
:foreign_key
Por ejemplo, considere las siguientes declaraciones de modelo:
class Author < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end
Active Record ya no reconocerá automáticamente la asociación bidireccional:
a = Author.first
b = a.books.first
a.first_name == b.writer.first_name # => true
a.first_name = 'David'
a.first_name == b.writer.first_name # => false
Active Record proporciona la opción: inverse_of
para que pueda declarar explícitamente asociaciones bidireccionales:
class Author < ApplicationRecord
has_many :books, inverse_of: 'writer'
end
class Book < ApplicationRecord
belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end
Al incluir la opción: inverse_of
en la declaración de asociación has_many
, Active Record reconocerá ahora la asociación bidireccional:
a = Author.first
b = a.books.first
a.first_name == b.writer.first_name # => true
a.first_name = 'David'
a.first_name == b.writer.first_name # => true
Existen algunas limitaciones con :inverse_of
- No trabajan con asociaciones
:through
. - No funcionan con asociaciones polimórficas.
- No trabajan con asociaciones
:as