web-dev-qa-db-ja.com

RoRの同じテーブルを参照する複数の外部キー

お客様に、請求先住所と配送先住所の2つの住所モデルを参照してもらいたい。私が理解しているように、外部キーは_idのようにその名前で決まります。明らかに、(Addressテーブルを参照するために)2つの行にaddress_idという名前を付けることはできません。どうすればいいですか?

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
36
titaniumdecoy

これは、私とのhas_many関係のように聞こえます。代わりに、customer_idをAddressテーブルに入れてください。

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"
27
Toby Hede

これは、Rails(最近のように)を初めて使用する人)を混乱させる可能性があります。回答の一部は、マイグレーションとモデルの一部で発生するためです。また、 2つの別個のものをモデル化します。

  1. 住所は単一の顧客に属しており、各顧客には多数の住所があります。あなたの場合、これは1つまたは2つの住所ですが、顧客が複数の配送先住所を持つ可能性を考慮することをお勧めします。例として、Amazon.comには3つの配送先住所があります。

  2. これとは別に、各顧客が請求先住所と配送先住所を持っているという事実をモデル化したいと思います。複数の配送先住所を許可する場合は、代わりにdefault配送先住所になる可能性があります。

これを行う方法は次のとおりです。

マイグレーション

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と呼ばれ、別のテーブルへの参照を保持する2つの列があることを指定しています。 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は、列名に一致する(複数形について知っている)テーブル 'customers'があることがわかるので、これを処理します。

「_id」をCustomersマイグレーションに追加した理由は、「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

ここでは、:billing_addressという名前のCustomerモデルにプロパティを作成し、このプロパティがAddressクラスに関連していることを指定しています。 Railsは、「belongs_to」を確認すると、上記で定義した「billing_address_id」という顧客テーブルの列を探し、その列を使用して外部キーを格納します。次に、配送先住所に対してまったく同じことを行います。

これにより、次のように、顧客モデルのインスタンスを介して、住所モデルの両方のインスタンスである請求先住所と配送先住所にアクセスできます。

@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'は、外部キーが含まれているものに使用されます。外部キーは、今回の例ではbothモデルです。はぁ!混乱するのはどうですか?

最後に、顧客に多数の住所があることを指定しています。この場合、このプロパティが関連付けられているクラス名を指定する必要はありません。Railsは、一致する名前のモデルがあることを確認するのに十分スマートです: 'Address'、これはこれにより、次のようにして顧客のすべてのアドレスのリストを取得できます。

@customer.addresses

これは、請求先住所か配送先住所かに関係なく、住所モデルのインスタンスの配列を返します。アドレスモデルといえば、次のようになります。

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
63
Richard Jones

私は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

Customersテーブルには、shipping_address_id列とbilling_address_id列が含まれています。

これは本質的にhas_two関係です。 this thread も参考になりました。

12
titaniumdecoy

私は同じ問題を抱えており、これを解決しました:

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

Rails 5.1以降では、次のように実行できます:

マイグレーション

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

これにより、フィールドaddress_idおよびaddress1_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
1
Toby 1 Kenobi