Несколько внешних ключей, ссылающихся на одну и ту же таблицу в RoR - PullRequest
32 голосов
/ 30 января 2010

Я хочу, чтобы Клиент ссылался на две модели адресов: одну для адреса выставления счета и одну для адреса доставки Насколько я понимаю, внешний ключ определяется его именем, как _id. Очевидно, я не могу назвать две строки 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

Ответы [ 4 ]

58 голосов
/ 17 апреля 2012

Это может ввести в заблуждение людей, плохо знакомых с Rails (как я недавно), потому что некоторые части ответа имеют место в ваших Миграциях, а некоторые в ваших Моделях. Кроме того, вы действительно хотите смоделировать две разные вещи:

  1. Адрес принадлежит одному клиенту, и у каждого клиента есть много адресов. В вашем случае это будет 1 или 2 адреса, но я бы посоветовал вам рассмотреть возможность того, что клиент может иметь более одного адреса доставки. Например, у меня есть 3 отдельных адреса доставки на Amazon.com.

  2. Отдельно мы хотим смоделировать тот факт, что у каждого клиента есть адрес выставления счета и адрес доставки, который вместо этого может быть по умолчанию адрес доставки, если вы разрешите более одного адреса доставки.

Вот как бы вы это сделали:

Миграции

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'. В нашем случае они будут ссылаться на строки в таблице адресов, но мы указываем это в моделях, а не в миграциях.

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

Здесь вы также создаете столбец, который ссылается на другую таблицу, но в конце вы опускаете «_id». Rails позаботится об этом за вас, потому что видит, что у вас есть таблица 'users', соответствующая имени столбца (он знает о множественности).

Причина, по которой мы добавили «_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. Rails, увидев «own_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

В качестве дополнительного примечания: в данном случае номенклатура «принадлежат к кому-то» вводит в заблуждение, поскольку адреса принадлежат Заказчикам, а не наоборот. Не обращайте внимания на вашу интуицию, хотя; «own_to» используется в зависимости от того, что содержит внешний ключ, который, в нашем случае, как вы увидите, будет обеих моделей. Хах! как это за путаница?

Наконец, мы указываем, что у Клиента много адресов. В этом случае нам не нужно указывать имя класса, с которым связано это свойство, потому что Rails достаточно умен, чтобы увидеть, что у нас есть модель с подходящим именем: 'Address', к которому мы перейдем через секунду. Это позволяет нам получить список всех адресов Клиента, выполнив следующие действия:

@customer.addresses

Это вернет массив экземпляров модели Address, независимо от того, являются ли они адресами выставления счета или доставки. Говоря о модели адреса, вот как это выглядит:

class Address < ActiveRecord::Base
  belongs_to :customer
end

Здесь вы выполняете то же самое, что и со строками 'own_to' в модели Customer, за исключением того, что Rails делает для вас немного магии; просматривая имя свойства ('customer'), он видит 'own_to' и предполагает, что это свойство ссылается на модель с тем же именем ('Customer') и что в таблице адресов есть соответствующий столбец ('customer_id') .

Это позволяет нам получить доступ к Клиенту, которому принадлежит Адрес, например:

@address.customer # Returns an instance of the Customer model
@address.customer.first_name # Returns a string, as you would expect
26 голосов
/ 30 января 2010

Это звучит как отношение has_many для меня - вместо этого поместите customer_id в таблицу адресов.

Customer
  has_many :addresses

Address
  belongs_to :customer

Вы также можете указать внешний ключ и класс в объявлении ассоциации

Customer
   has_one :address
   has_one :other_address, foreign_key => "address_id_2", class_name => "Address"
11 голосов
/ 30 января 2010

Я понял, как это сделать, благодаря Тоби:

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

Таблица клиентов включает столбцы shipping_address_id и billing_address_id.

Это, по сути, отношение has_two. Я нашел эту тему также полезной.

4 голосов
/ 20 декабря 2012

У меня была та же проблема, и я решил это сделать:

create_table :customers do |t|
  t.integer :address_id, :references => "address"
  t.integer :address_id_1, :references => "address"
  # other attributes not shown
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...