Multiple foreign keys referencing the same table in RoR

前端 未结 5 1365
隐瞒了意图╮
隐瞒了意图╮ 2020-12-12 14:13

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 na

相关标签:
5条回答
  • 2020-12-12 14:53

    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
    
    0 讨论(0)
  • 2020-12-12 14:57

    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.

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

    0 讨论(0)
  • 2020-12-12 15:01

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

    Customer
      has_many :addresses
    
    Address
      belongs_to :customer
    

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

    Customer
       has_one :address
       has_one :other_address, foreign_key => "address_id_2", class_name => "Address"
    
    0 讨论(0)
  • 2020-12-12 15:14

    In Rails 5.1 or greater you can do it like this:

    Migration

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

    This will create the fields address_id, and address1_id and make the database level references to the addresses table

    Models

    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

    If you uses FactoryBot then your factory might look something like this:

    FactoryBot.define do
      factory :customer do
        address
        association :address1, factory: :address
      end
    end
    
    0 讨论(0)
  • 2020-12-12 15:19

    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:

    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.

    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.

    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).

    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.

    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.

    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.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?

    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:

    @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:

    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').

    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
    
    0 讨论(0)
提交回复
热议问题