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

时间:2010-01-30 02:39:47

标签: ruby-on-rails migration

我希望客户引用两个地址模型,一个用于帐单邮寄地址,另一个用于送货地址。据我了解,外键由其名称决定,如_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

5 个答案:

答案 0 :(得分:58)

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

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

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

  3. 以下是您将如何做到这一点:

    迁移

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

    此处指定此表中有两列将被称为: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
    

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

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

    模型

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

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

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

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

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

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

    @customer.addresses
    

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

    class Address < ActiveRecord::Base
      belongs_to :customer
    end
    

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

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

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

答案 1 :(得分:26)

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

Customer
  has_many :addresses

Address
  belongs_to :customer

您还可以在assoc声明

中提供外键和类
Customer
   has_one :address
   has_one :other_address, foreign_key => "address_id_2", class_name => "Address"

答案 2 :(得分:11)

感谢托比,我想出了怎么做:

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

customers表包含shipping_address_id和billing_address_id列。

这实际上是一个has_two关系。我发现this thread也很有帮助。

答案 3 :(得分:4)

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

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

答案 4 :(得分:1)

在Rails 5.1或更高版本中,您可以这样做:

迁移

create_table(:customers) do |t|
    t.references :address, foreign_key: true
    t.references :address1, foreign_key: { to_table: 'addresses' }
end

这将创建字段address_idaddress1_id,并在数据库级别引用addresses

模型

class Customer < ActiveRecord::Base
  belongs_to :address
  belongs_to :address1, class_name: "Address"
end

class Address < ActiveRecord::Base
  has_many :customers,
  has_many :other_customers, class_name: "Customer", foreign_key: "address1_id"
end

FactoryBot

如果您使用FactoryBot,则您的工厂可能看起来像这样:

FactoryBot.define do
  factory :customer do
    address
    association :address1, factory: :address
  end
end