多个外键引用RoR中的同一个表

时间:2021-06-03 11:29:19

I want a Customer to reference two Address models, one for the billing address and one for the shipping address. As I understand it, the foreign key is determined by its name, as _id. Obviously I can't name two rows address_id (to reference the Address table). How would I do this?

我希望客户引用两个地址模型,一个用于帐单地址,另一个用于送货地址。据我了解,外键由其名称决定,如_id。显然我不能命名两行address_id(引用Address表)。我该怎么办?

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

4 个解决方案

#1


23  

This sounds like a has_many relationship to me - put the customer_id in the Address table instead.

这听起来像是一个has_many关系 - 把customer_id放在Address表中。

Customer
  has_many :addresses

Address
  belongs_to :customer

You can also provide a foreign key and class in the assoc declaration

您还可以在assoc声明中提供外键和类

Customer
   has_one :address
   has_one :other_address, foreign_key => "address_id_2", class_name => "Address"

#2


50  

This can be kind of confusing to people new to Rails (as I was recently), because some parts of the answer take place in your Migrations and some in your Models. Also, you actually want to model two separate things:

对于刚接触Rails的人来说,这可能会让人感到困惑(正如我最近的那样),因为答案的某些部分发生在您的迁移中,而某些部分发生在您的模型中。此外,您实际上想要模拟两个单独的事物:

  1. An address belongs to a single customer and each customer has many addresses. In your case this would be either 1 or 2 addresses, but I would encourage you to consider the possibility that a customer can have more than one shipping address. As an example, I have 3 separate shipping addresses with Amazon.com.

    地址属于单个客户,每个客户都有许多地址。在您的情况下,这将是1或2个地址,但我鼓励您考虑客户可能有多个送货地址的可能性。例如,我在Amazon.com上有3个单独的送货地址。

  2. Separately, we want to model the fact that each customer has a billing address and a shipping address, which might instead be the default shipping address if you allow more than one shipping address.

    另外,我们希望模拟每个客户都有帐单邮寄地址和送货地址这一事实,如果您允许多个送货地址,则可能改为默认送货地址。

Here's how you would do that:

这是你如何做到这一点:

Migrations

class CreateCustomers < ActiveRecord::Migration
  create_table :customers do |t|
    def up
      t.references :billing_address
      t.references :shipping_address
    end
  end
end

Here you are specifying that there are two columns in this table that will be referred to as :billing_address and :shipping_address and which hold references to another table. Rails will actually create columns called 'billing_address_id' and 'shipping_address_id' for you. In our case they will each reference rows in the Addresses table, but we specify that in the models, not in the migrations.

在这里,您指定此表中有两列将被称为:billing_address和:shipping_address,其中包含对另一个表的引用。 Rails实际上会为您创建名为“billing_address_id”和“shipping_address_id”的列。在我们的例子中,他们将每个引用Addresses表中的行,但我们在模型中指定,而不是在迁移中。

class CreateAddresses < ActiveRecord::Migration
  create_table :addresses do |t|
    def up
      t.references :customer
    end
  end
end

Here you are also creating a column that references another table, but you are omitting the "_id" at the end. Rails will take care of that for you because it sees that you have a table, 'customers', that matches the column name (it knows about plurality).

在这里,您还要创建一个引用另一个表的列,但最后省略了“_id”。 Rails将为您处理这个问题,因为它会看到您有一个表“客户”,它与列名相匹配(它知道多个)。

The reason we added "_id" to the Customers migration is because we don't have a "billing_addresses" or "shipping_addresses" table, so we need to manually specify the entire column name.

我们向客户迁移添加“_id”的原因是因为我们没有“billing_addresses”或“shipping_addresses”表,因此我们需要手动指定整个列名。

Models

class Customer < ActiveRecord::Base
  belongs_to :billing_address, :class_name => 'Address'
  belongs_to :shipping_address, :class_name => 'Address'
  has_many :addresses
end

Here you are creating a property on the Customer model named :billing_address, then specifying that this property is related to the Address class. Rails, seeing the 'belongs_to', will look for a column in the customers table called 'billing_address_id', which we defined above, and use that column to store the foreign key. Then you're doing the exact same thing for the shipping address.

在这里,您将在Customer模型上创建一个名为:billing_address的属性,然后指定此属性与Address类相关。看到'belongs_to'的Rails将在customers表中查找名为'billing_address_id'的列,我们在上面定义了该列,并使用该列存储外键。然后你正在为送货地址做同样的事情。

This will allow you to access your Billing Address and Shipping Address, both instances of the Address model, through an instance of the Customer model, like this:

这将允许您通过Customer模型的实例访问“地址”模型的两个实例的“帐单地址”和“送货地址”,如下所示:

@customer.billing_address # Returns an instance of the Address model
@customer.shipping_address.street1 # Returns a string, as you would expect

As a side note: the 'belongs_to' nomenclature is kind of confusing in this case, since the Addresses belong to the Customers, not the other way around. Ignore your intuition though; the 'belongs_to' is used on whichever thing contains the foreign key which, in our case, as you will see, is both models. Hah! how's that for confusing?

作为旁注:在这种情况下,'belongs_to'命名法有点令人困惑,因为地址属于客户,而不是相反。但是,忽略你的直觉; 'belongs_to'用于包含外键的任何东西,在我们的例子中,正如您将看到的,这两种模型都是。哈!如何混淆?

Finally, we are specifying that a Customer has many addresses. In this case, we don't need to specify the class name this property is related to because Rails is smart enough to see that we have a model with a matching name: 'Address', which we'll get to in a second. This allows us to get a list of all of Customer's addresses by doing the following:

最后,我们指定客户有许多地址。在这种情况下,我们不需要指定与此属性相关的类名,因为Rails足够聪明,可以看到我们有一个匹配名称的模型:'Address',我们将在一秒钟内得到它。这允许我们通过执行以下操作获取所有客户地址的列表:

@customer.addresses

This will return an array of instances of the Address model, regardless of whether they are billing or shipping addresses. Speaking of the Address model, here's what that looks like:

这将返回Address模型的一组实例,无论它们是计费还是送货地址。说到地址模型,这是看起来像:

class Address < ActiveRecord::Base
  belongs_to :customer
end

Here you're accomplishing the exact same thing as with the 'belongs_to' lines in the Customer model, except that Rails does some magic for you; looking at the property name ('customer'), it sees the 'belongs_to' and assumes that this property references the model with the same name ('Customer') and that there is a matching column on the addresses table ('customer_id').

在这里,你完成了与Customer模型中'belongs_to'行完全相同的事情,除了Rails为你做了一些魔术;查看属性名称('customer'),它会看到'belongs_to'并假设此属性引用具有相同名称的模型('Customer')并且地址表上有匹配的列('customer_id') 。

This allows us to access the Customer that an Address belongs to like this:

这允许我们访问地址所属的客户,如下所示:

@address.customer # Returns an instance of the Customer model
@address.customer.first_name # Returns a string, as you would expect

#3


11  

I figured out how to do it thanks to 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

The customers table includes shipping_address_id and billing_address_id columns.

customers表包括shipping_address_id和billing_address_id列。

This is essentially a has_two relationship. I found this thread helpful as well.

这基本上是一个has_two关系。我发现这个帖子也很有帮助。

#4


3  

I had the same problem and solved doing this:

我有同样的问题并解决了这个问题:

create_table :customers do |t|
  t.integer :address_id, :references => "address"
  t.integer :address_id_1, :references => "address"
  # other attributes not shown
end

#1


23  

This sounds like a has_many relationship to me - put the customer_id in the Address table instead.

这听起来像是一个has_many关系 - 把customer_id放在Address表中。

Customer
  has_many :addresses

Address
  belongs_to :customer

You can also provide a foreign key and class in the assoc declaration

您还可以在assoc声明中提供外键和类

Customer
   has_one :address
   has_one :other_address, foreign_key => "address_id_2", class_name => "Address"

#2


50  

This can be kind of confusing to people new to Rails (as I was recently), because some parts of the answer take place in your Migrations and some in your Models. Also, you actually want to model two separate things:

对于刚接触Rails的人来说,这可能会让人感到困惑(正如我最近的那样),因为答案的某些部分发生在您的迁移中,而某些部分发生在您的模型中。此外,您实际上想要模拟两个单独的事物:

  1. An address belongs to a single customer and each customer has many addresses. In your case this would be either 1 or 2 addresses, but I would encourage you to consider the possibility that a customer can have more than one shipping address. As an example, I have 3 separate shipping addresses with Amazon.com.

    地址属于单个客户,每个客户都有许多地址。在您的情况下,这将是1或2个地址,但我鼓励您考虑客户可能有多个送货地址的可能性。例如,我在Amazon.com上有3个单独的送货地址。

  2. Separately, we want to model the fact that each customer has a billing address and a shipping address, which might instead be the default shipping address if you allow more than one shipping address.

    另外,我们希望模拟每个客户都有帐单邮寄地址和送货地址这一事实,如果您允许多个送货地址,则可能改为默认送货地址。

Here's how you would do that:

这是你如何做到这一点:

Migrations

class CreateCustomers < ActiveRecord::Migration
  create_table :customers do |t|
    def up
      t.references :billing_address
      t.references :shipping_address
    end
  end
end

Here you are specifying that there are two columns in this table that will be referred to as :billing_address and :shipping_address and which hold references to another table. Rails will actually create columns called 'billing_address_id' and 'shipping_address_id' for you. In our case they will each reference rows in the Addresses table, but we specify that in the models, not in the migrations.

在这里,您指定此表中有两列将被称为:billing_address和:shipping_address,其中包含对另一个表的引用。 Rails实际上会为您创建名为“billing_address_id”和“shipping_address_id”的列。在我们的例子中,他们将每个引用Addresses表中的行,但我们在模型中指定,而不是在迁移中。

class CreateAddresses < ActiveRecord::Migration
  create_table :addresses do |t|
    def up
      t.references :customer
    end
  end
end

Here you are also creating a column that references another table, but you are omitting the "_id" at the end. Rails will take care of that for you because it sees that you have a table, 'customers', that matches the column name (it knows about plurality).

在这里,您还要创建一个引用另一个表的列,但最后省略了“_id”。 Rails将为您处理这个问题,因为它会看到您有一个表“客户”,它与列名相匹配(它知道多个)。

The reason we added "_id" to the Customers migration is because we don't have a "billing_addresses" or "shipping_addresses" table, so we need to manually specify the entire column name.

我们向客户迁移添加“_id”的原因是因为我们没有“billing_addresses”或“shipping_addresses”表,因此我们需要手动指定整个列名。

Models

class Customer < ActiveRecord::Base
  belongs_to :billing_address, :class_name => 'Address'
  belongs_to :shipping_address, :class_name => 'Address'
  has_many :addresses
end

Here you are creating a property on the Customer model named :billing_address, then specifying that this property is related to the Address class. Rails, seeing the 'belongs_to', will look for a column in the customers table called 'billing_address_id', which we defined above, and use that column to store the foreign key. Then you're doing the exact same thing for the shipping address.

在这里,您将在Customer模型上创建一个名为:billing_address的属性,然后指定此属性与Address类相关。看到'belongs_to'的Rails将在customers表中查找名为'billing_address_id'的列,我们在上面定义了该列,并使用该列存储外键。然后你正在为送货地址做同样的事情。

This will allow you to access your Billing Address and Shipping Address, both instances of the Address model, through an instance of the Customer model, like this:

这将允许您通过Customer模型的实例访问“地址”模型的两个实例的“帐单地址”和“送货地址”,如下所示:

@customer.billing_address # Returns an instance of the Address model
@customer.shipping_address.street1 # Returns a string, as you would expect

As a side note: the 'belongs_to' nomenclature is kind of confusing in this case, since the Addresses belong to the Customers, not the other way around. Ignore your intuition though; the 'belongs_to' is used on whichever thing contains the foreign key which, in our case, as you will see, is both models. Hah! how's that for confusing?

作为旁注:在这种情况下,'belongs_to'命名法有点令人困惑,因为地址属于客户,而不是相反。但是,忽略你的直觉; 'belongs_to'用于包含外键的任何东西,在我们的例子中,正如您将看到的,这两种模型都是。哈!如何混淆?

Finally, we are specifying that a Customer has many addresses. In this case, we don't need to specify the class name this property is related to because Rails is smart enough to see that we have a model with a matching name: 'Address', which we'll get to in a second. This allows us to get a list of all of Customer's addresses by doing the following:

最后,我们指定客户有许多地址。在这种情况下,我们不需要指定与此属性相关的类名,因为Rails足够聪明,可以看到我们有一个匹配名称的模型:'Address',我们将在一秒钟内得到它。这允许我们通过执行以下操作获取所有客户地址的列表:

@customer.addresses

This will return an array of instances of the Address model, regardless of whether they are billing or shipping addresses. Speaking of the Address model, here's what that looks like:

这将返回Address模型的一组实例,无论它们是计费还是送货地址。说到地址模型,这是看起来像:

class Address < ActiveRecord::Base
  belongs_to :customer
end

Here you're accomplishing the exact same thing as with the 'belongs_to' lines in the Customer model, except that Rails does some magic for you; looking at the property name ('customer'), it sees the 'belongs_to' and assumes that this property references the model with the same name ('Customer') and that there is a matching column on the addresses table ('customer_id').

在这里,你完成了与Customer模型中'belongs_to'行完全相同的事情,除了Rails为你做了一些魔术;查看属性名称('customer'),它会看到'belongs_to'并假设此属性引用具有相同名称的模型('Customer')并且地址表上有匹配的列('customer_id') 。

This allows us to access the Customer that an Address belongs to like this:

这允许我们访问地址所属的客户,如下所示:

@address.customer # Returns an instance of the Customer model
@address.customer.first_name # Returns a string, as you would expect

#3


11  

I figured out how to do it thanks to 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

The customers table includes shipping_address_id and billing_address_id columns.

customers表包括shipping_address_id和billing_address_id列。

This is essentially a has_two relationship. I found this thread helpful as well.

这基本上是一个has_two关系。我发现这个帖子也很有帮助。

#4


3  

I had the same problem and solved doing this:

我有同样的问题并解决了这个问题:

create_table :customers do |t|
  t.integer :address_id, :references => "address"
  t.integer :address_id_1, :references => "address"
  # other attributes not shown
end