Rails 4:Has_one多态关联不起作用

我有两个型号PurchaseAddress 。 我正在尝试使Address变为多态,因此我可以在我的Purchase模型中重用has_one :billing_addresshas_one :shipping_address 。 这是我的架构:

 create_table "addresses", force: true do |t| t.string "first_name" t.string "last_name" t.string "street_address" t.string "street_address2" t.string "zip_code" t.string "phone_number" t.datetime "created_at" t.datetime "updated_at" t.integer "state_id" t.string "city" t.string "addressable_type" #<-- t.integer "addressable_id" #<-- end 

地址模型:

 class Address < ActiveRecord::Base belongs_to :addressable, polymorphic: true ... end 

购买型号:

 class Purchase < ActiveRecord::Base has_one :shipping_address, as: :addressable has_one :billing_address, as: :addressable ... end 

一切看起来都很好,但我的Rspec测试失败了:

  Failures: 1) Purchase should have one shipping_address Failure/Error: it { should have_one(:shipping_address) } Expected Purchase to have a has_one association called shipping_address (ShippingAddress does not exist) # ./spec/models/purchase_spec.rb:22:in `block (2 levels) in ' 2) Purchase should have one billing_address Failure/Error: it { should have_one(:billing_address) } Expected Purchase to have a has_one association called billing_address (BillingAddress does not exist) # ./spec/models/purchase_spec.rb:23:in `block (2 levels) in ' 

它似乎没有检测到该关联是多态的。 即使在我的控制台:

  irb(main):001:0> p = Purchase.new => # irb(main):002:0> p.shipping_address => nil irb(main):003:0> p.build_shipping_address NameError: uninitialized constant Purchase::ShippingAddress 

我究竟做错了什么??

您需要为has_one关联指定:class_name选项,因为无法从关联名称推断出类名,例如:shipping_address:billing_address在您的情况下不会认为它们引用了类Address

更新Purchase模型如下:

 class Purchase < ActiveRecord::Base has_one :shipping_address, class_name: "Address", as: :addressable has_one :billing_address, class_name: "Address", as: :addressable ## ... end 

我认为你误解了多态关联的含义。 它们允许它属于多个模型,此处的地址始终属于购买。

您所做的事情允许地址属于说,篮子或购买。 addressable_type始终为Purchase。 它不会是ShippingAddress或BillingAddress我认为你会认为它。

p.build_shipping_address不起作用,因为没有送货地址模型。

添加class_name: 'Address' ,它可以让你这样做。 但它仍然无法按照您的预期运作。

我认为你真正想要的是单表inheritance。 只是在地址上有一个类型列

 class Purchase < ActiveRecord::Base has_one :shipping_address has_one :billing_address end class Address < ActiveRecord::Base belongs_to :purchase ... end class ShippingAddress < Address end class BillingAddress < Address end 

这应该没问题,因为运输和账单地址将具有相同的数据,如果您有很多列只是在一个或另一个不适合的方式。

另一个实现是在Purchase模型上有shipping_address_id和billing_address_id。

 class Purchase < ActiveRecord::Base belongs_to :shipping_address, class_name: 'Address' belongs_to :billing_address, class_name: 'Address' end 

belongs_to :shipping_address将告诉rails查找具有类名的shipping_address_id,告诉它查看地址表。

将多态关系命名为以“能够”结束的某种程度的约定。 如果它传达某种行为,这是有道理的。 例如,图像是“可查看的”,帐户是“可支付的”等。但是,有时很难想出一个以“能够”结束的术语,当被迫找到这样一个术语时,它甚至可能导致有点混乱。

多态关联通常表明所有权。 特别是对于这种情况,我会将Address模型定义如下:

 class Address < ActiveRecord::Base belongs_to :address_owner, :polymorphic => true ... end 

使用:address_owner作为关系名称应该有助于消除任何混淆,因为它清楚地表明某个地址属于某个人或某人。 它可以属于用户,个人,客户,订单或企业等。

我会反对单表inheritance,因为运费和账单地址仍然在一天结束时,只是地址,即没有变形。 相比之下,一个人,一个订单或一个企业在性质上是非常不同的,因此为多态性提供了一个很好的例子。

在订单的情况下,我们仍然希望列address_owner_typeaddress_owner_id反映订单属于客户。 所以问题仍然存在:我们如何certificate订单有帐单邮寄地址和送货地址?

我要使用的解决方案是将外键添加到订单表中以获取送货地址和帐单地址。 Order类看起来如下所示:

 class Order < ActiveRecord::Base belongs_to :customer has_many :addresses, :as => :address_owner belongs_to :shipping_address, :class_name => 'Address' belongs_to :billing_address, :class_name => 'Address' ... end 

注意:我使用Order作为类名而支持Purchase,因为订单反映了交易的业务和客户方面,而购买是客户所做的事情。

如果需要,您可以定义Address的关系的另一端:

 class Address < ActiveRecord::Base belongs_to :address_owner, :polymorphic => true has_one :shipping_address, :class_name => 'Order', :foreign_key => 'shipping_address_id' has_one :billing_address, :class_name => 'Order', :foreign_key => 'billing_address_id' ... end 

要清除的另外一部分代码是如何设置送货地址和帐单地址? 为此,我将覆盖Order模型上的相应setter。 Order模型将如下所示:

 class Order < ActiveRecord::Base belongs_to :customer has_many :addresses, :as => :address_owner belongs_to :shipping_address, :class_name => 'Address' belongs_to :billing_address, :class_name => 'Address' ... def shipping_address=(shipping_address) if self.shipping_address self.shipping_address.update_attributes(shipping_address) else new_address = Address.create(shipping_address.merge(:address_owner => self)) self.shipping_address_id = new_address.id # Note of Caution: Replacing this line with "self.shipping_address = new_address" would re-trigger this setter with the newly created Address, which is something we definitely don't want to do end end def billing_address=(billing_address) if self.billing_address self.billing_address.update_attributes(billing_address) else new_address = Address.create(billing_address.merge(:address_owner => self)) self.billing_address_id = new_address.id end end end 

该解决方案通过为地址定义两个关系来解决该问题。 has_onebelongs_to关系允许我们跟踪送货地址和帐单地址,而polymorphic关系显示送货地址和帐单地址属于订单。 这个解决方案为我们提供了两全其美的优势。