ruby on rails - relaciones - Múltiples claves foráneas que hacen referencia a la misma tabla en RoR
relaciones de modelos en rails (4)
Quiero que un Cliente haga referencia a dos modelos de Dirección, uno para la dirección de facturación y otro para la dirección de envío. Como lo entiendo, la clave foránea está determinada por su nombre, como _id. Obviamente no puedo nombrar dos filas address_id (para hacer referencia a la tabla de direcciones). ¿Cómo haría esto?
create_table :customers do |t|
t.integer :address_id
t.integer :address_id_1 # how do i make this reference addresses table?
# other attributes not shown
end
Descubrí cómo hacerlo gracias a Toby:
class Address < ActiveRecord::Base
has_many :customers
end
class Customer < ActiveRecord::Base
belongs_to :billing_address, :class_name => ''Address'', :foreign_key => ''billing_address_id''
belongs_to :shipping_address, :class_name => ''Address'', :foreign_key => ''shipping_address_id''
end
La tabla de clientes incluye las columnas shipping_address_id y billing_address_id.
Esto es esencialmente una relación has_two. He encontrado este hilo útil también.
Esto puede ser un poco confuso para las personas nuevas en Rails (como era recientemente), porque algunas partes de la respuesta tienen lugar en sus Migraciones y otras en sus Modelos. Además, realmente quieres modelar dos cosas separadas:
Una dirección pertenece a un solo cliente y cada cliente tiene muchas direcciones. En su caso, esto sería 1 o 2 direcciones, pero le animo a considerar la posibilidad de que un cliente pueda tener más de una dirección de envío. Como ejemplo, tengo 3 direcciones de envío separadas con Amazon.com.
Por separado, queremos modelar el hecho de que cada cliente tiene una dirección de facturación y una dirección de envío, que en su lugar podría ser la dirección de envío predeterminada si permite más de una dirección de envío.
Así es como harías eso:
Migraciones
class CreateCustomers < ActiveRecord::Migration
create_table :customers do |t|
def up
t.references :billing_address
t.references :shipping_address
end
end
end
Aquí está especificando que hay dos columnas en esta tabla que se denominarán: billing_address y: shipping_address y que contienen referencias a otra tabla. Rails realmente creará columnas llamadas ''billing_address_id'' y ''shipping_address_id'' para ti. En nuestro caso, cada una hará referencia a las filas en la tabla de Direcciones, pero nosotros especificamos eso en los modelos, no en las migraciones.
class CreateAddresses < ActiveRecord::Migration
create_table :addresses do |t|
def up
t.references :customer
end
end
end
Aquí también está creando una columna que hace referencia a otra tabla, pero está omitiendo el "_id" al final. Rails se encargará de eso por usted porque ve que tiene una tabla, ''clientes'', que coincide con el nombre de la columna (conoce la pluralidad).
La razón por la que agregamos "_id" a la migración de Clientes es porque no tenemos una tabla "billing_addresses" o "shipping_addresses", por lo que necesitamos especificar manualmente el nombre completo de la columna.
Modelos
class Customer < ActiveRecord::Base
belongs_to :billing_address, :class_name => ''Address''
belongs_to :shipping_address, :class_name => ''Address''
has_many :addresses
end
Aquí está creando una propiedad en el modelo del cliente llamado: billing_address, y luego especifica que esta propiedad está relacionada con la clase de dirección. Los rieles, al ver el ''belongs_to'', buscarán una columna en la tabla de clientes llamada ''billing_address_id'', que definimos anteriormente, y usaremos esa columna para almacenar la clave externa. Entonces estás haciendo exactamente lo mismo para la dirección de envío.
Esto le permitirá acceder a su Dirección de facturación y Dirección de envío, ambas instancias del modelo de Dirección, a través de una instancia del modelo de Cliente, como esto:
@customer.billing_address # Returns an instance of the Address model
@customer.shipping_address.street1 # Returns a string, as you would expect
Como nota al margen: la nomenclatura ''pertenece a'' es algo confusa en este caso, ya que las Direcciones pertenecen a los Clientes, no al revés. Ignora tu intuición sin embargo; el ''belongs_to'' se utiliza en cualquier cosa que contenga la clave externa que, en nuestro caso, como verá, son ambos modelos. Jah ¿Cómo es eso para confundir?
Finalmente, estamos especificando que un Cliente tiene muchas direcciones. En este caso, no necesitamos especificar el nombre de clase con el que está relacionada esta propiedad porque Rails es lo suficientemente inteligente como para ver que tenemos un modelo con un nombre coincidente: ''Dirección'', al que llegaremos en un segundo. Esto nos permite obtener una lista de todas las direcciones del Cliente haciendo lo siguiente:
@customer.addresses
Esto devolverá una serie de instancias del modelo de dirección, independientemente de si se trata de direcciones de facturación o de envío. Hablando del modelo de Dirección, esto es lo que parece:
class Address < ActiveRecord::Base
belongs_to :customer
end
Aquí está logrando exactamente lo mismo que con las líneas ''pertenece_ a'' en el modelo del Cliente, excepto que Rails hace algo de magia por usted; al mirar el nombre de la propiedad (''cliente''), ve el ''correspondiente a'' y asume que esta propiedad hace referencia al modelo con el mismo nombre (''Cliente'') y que hay una columna coincidente en la tabla de direcciones (''customer_id'') .
Esto nos permite acceder al cliente al que pertenece una dirección como esta:
@address.customer # Returns an instance of the Customer model
@address.customer.first_name # Returns a string, as you would expect
Esto suena como una relación has_many para mí: coloque el customer_id en la tabla de direcciones en su lugar.
Customer
has_many :addresses
Address
belongs_to :customer
También puede proporcionar una clave externa y una clase en la declaración assoc
Customer
has_one :address
has_one :other_address, foreign_key => "address_id_2", class_name => "Address"
Tuve el mismo problema y resolví haciendo esto:
create_table :customers do |t|
t.integer :address_id, :references => "address"
t.integer :address_id_1, :references => "address"
# other attributes not shown
end