Можно ли описать эти отношения в Ruby on Rails?

Можно ли описать эту взаимосвязь в отношениях модели ActiveRecord в Ruby on Rails?

   Customer                          Address
   ===================               =========
   Billing_Address_Id  >------}
                              }---|- AddressId
   Shipping_Address_Id >------}

Чтобы у меня были данные, которые выглядят так:

Адрес:

   Id | Addr           | City     | State | Zip   |
   ================================================
   1  | 123 Main       | New York | NY    | 99999 |
   2  | 200 2nd Street | New York | NY    | 99999 |
   3  | 300 3rd Street | Albany   | NY    | 99998 |
   4  | PO Box 4       | Albany   | NY    | 99998 |

Покупатель:

   Id | Name | Billing_Address_Id | Shipping_Address_Id  |
   =======================================================
   1  | Bob  | 1                  | 1                    |
   2  | Al   | 2                  | 1                    |
   3  | Joe  | 3                  | 4                    |

Я хочу хранить адреса в отдельной таблице, потому что данные могут совместно использоваться клиентами (особенно адрес доставки). Но для каждого клиента было бы даже два адреса.

Я бы хотел избежать отношений «многие ко многим», если только нет другого пути.


person y0mbo    schedule 14.11.2009    source источник


Ответы (3)


Приведены такие определения таблиц:

create_table :addresses do |t|
  t.string :street
  t.string :city
  t.string :state
  t.string :zip
  t.timestamps
end

create_table :customers do |t|
  t.string     :name
  t.references :shipping_address
  t.references :billing_address
  t.timestamps
end

Вы можете связать платежный адрес и адрес доставки со своим клиентом следующим образом:

class Customer < ActiveRecord::Base
  belongs_to :shipping_address, :class_name => "Address"
  belongs_to :billing_address,  :class_name => "Address"
end
person Ryan McGeary    schedule 14.11.2009
comment
Поможет дизайн стола. Я не знал о типе данных .references. - person y0mbo; 15.11.2009

Да, это вполне возможно. Для таблицы customers с двумя внешними ключами shipping_address_id и billing_address_id к таблице addresses ваша модель Customer может выглядеть так:

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

Это позволит клиенту ссылаться на одну и ту же адресную строку для адресов доставки и выставления счетов, а также позволит нескольким клиентам совместно использовать адреса.

Обновление. При публикации ссылок на подобные адреса вы, вероятно, захотите тщательно продумать, как обрабатывать обновления адресов. В вашем примере у Боба и Ал один и тот же адрес доставки. Теперь, если Боб обновляет свой адрес доставки, вы, вероятно, захотите создать новую запись Address для нового адреса Боба, а не обновлять существующую запись, чтобы избежать изменения адреса Ала. Иногда вам может потребоваться обновить адреса обоих клиентов в этой ситуации, но в большинстве случаев вы, вероятно, этого не сделаете.

person Pär Wieslander    schedule 14.11.2009
comment
Я согласен с вашей оценкой дизайна. Мой пример немного упрощен по сравнению с тем, что я на самом деле буду делать; клиенты фактически будут частью одного и того же счета в одном домохозяйстве. У них может быть один и тот же адрес или может потребоваться доставка в другое место. - person y0mbo; 15.11.2009

В документации для ассоциаций ActiveRecord есть раздел, посвященный has_one и belongs_to. В Кроме того, в разделе на has_one упоминается, что это следует использовать только если другой класс имеет внешний ключ. Итак, чтобы смоделировать то, что вы хотите, вы должны использовать.

class Address < ActiveRecord::Base
  has_one :shipto_customer, :class_name => "Customer", :foreign_key => "shipping_address_id"
  has_one :billto_customer, :class_name => "Customer", :foreign_key => "billing_address_id"
end

class Customer < ActiveRecord::Base
  belongs_to :shipping_address, :class_name => "Address"
  belongs_to :billing_address,  :class_name => "Address"
end

Пример использования:

>> customer = Customer.new(:name => "John Smith",
?>     :shipping_address => Address.new(:address => "123 M St",
?>       :city => "Phoenix", :state => "AZ", :zip => "85015"),
?>     :billing_address => Address.new(:address => "555 W Main Dr",
?>       :city => "Phoenix", :state => "AZ", :zip => "85015")
>>   )
=> #<Customer id: nil, name: "John Smith", billing_address_id: nil, shipping_address_id: nil, created_at: nil, updated_at: nil>
>> customer.save
  Address Create (0.8ms)   INSERT INTO "addresses" ("address", "city", "zip", "created_at", "updated_at", "state") VALUES('555 W Main Dr', 'Phoenix', '85015', '2009-11-14 17:03:28', '2009-11-14 17:03:28', 'AZ')
  Address Create (0.2ms)   INSERT INTO "addresses" ("address", "city", "zip", "created_at", "updated_at", "state") VALUES('123 M St', 'Phoenix', '85015', '2009-11-14 17:03:28', '2009-11-14 17:03:28', 'AZ')
  Customer Create (0.2ms)   INSERT INTO "customers" ("name", "billing_address_id", "shipping_address_id", "created_at", "updated_at") VALUES('John Smith', 1, 2, '2009-11-14 17:03:28', '2009-11-14 17:03:28')
=> true
>> customer.shipping_address
=> #<Address id: 2, address: "123 M St", city: "Phoenix", state: "AZ", zip: "85015", created_at: "2009-11-14 17:03:28", updated_at: "2009-11-14 17:03:28">
>> customer.billing_address
=> #<Address id: 1, address: "555 W Main Dr", city: "Phoenix", state: "AZ", zip: "85015", created_at: "2009-11-14 17:03:28", updated_at: "2009-11-14 17:03:28">
>> 
person Matt Haley    schedule 14.11.2009
comment
OP хотел иметь возможность обмениваться адресами между клиентами, поэтому отношения в модели Address должны быть has_many, а не has_one отношениями. - person Pär Wieslander; 14.11.2009
comment
Это кажется не совсем ясным из сообщения OP, в примере данных он показывает общие адреса, то есть: has_many, в примере кода, который он предлагает has_one. Затем он заявляет, что не хотел бы иметь много ко многим. На мой взгляд, использование общих адресов было бы плохой идеей. Что происходит, когда клиент А обновляет свой текущий адрес, добавляя zip + 4 или номер Apt? - person Matt Haley; 14.11.2009
comment
Да, общие адреса определенно доставляют неудобства. Хранение адресов и клиентов в отдельных таблицах, вероятно, является хорошей идеей для ясности, но совместное использование ссылок на строки адресов, скорее всего, вызовет проблемы при принятии решения о том, как обрабатывать обновления адресов. - person Pär Wieslander; 14.11.2009
comment
Я попытался упростить свой пример для своего вопроса. Рассматриваемые клиенты будут буквально частью одной семьи, поэтому изменения одного адреса должны обновляться для всех. - person y0mbo; 15.11.2009