[英]Rails 4: Has_one polymorphic association not working
我有两个模型Purchase
和Address
。 我正在尝试使Address
多态,以便可以在has_one :billing_address
和has_one :shipping_address
Purchase
模型中重用它。 这是我的架构:
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 <top (required)>'
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 <top (required)>'
似乎没有检测到关联是多态的。 即使在我的控制台中:
irb(main):001:0> p = Purchase.new
=> #<Purchase id: nil, user_id: nil, order_date: nil, total_cents: 0, total_currency: "USD", shipping_cents: 0, shipping_currency: "USD", tax_cents: 0, tax_currency: "USD", subtotal_cents: 0, subtotal_currency: "USD", created_at: nil, updated_at: nil, status: nil>
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
我认为您误解了多态关联的含义。 他们允许它属于一个以上的模型,这里的地址始终将属于Purchase。
您所做的操作允许地址属于购物篮或购买。 addressable_type总是要购买。 不会是ShippingAddress或BillingAddress,我认为您会的。
p.build_shipping_address
不起作用,因为没有送货地址模型。
添加class_name: 'Address'
,它将允许您执行此操作。 但是,它仍然无法按您期望的方式工作。
我认为您真正想要的是单表继承。 在地址上只有一个类型列
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,其类名告诉它在地址表中查找。
将多态关系命名为以“ able”结尾的东西在某种程度上是一种约定。 如果它传达某种动作,这是有道理的。 例如,图像是“可见的”,帐户是“可支付的”,等等。但是,有时很难想出以“ able”结尾的术语,当被迫找到这样的术语时,它甚至可能导致有点混乱。
多态关联通常显示所有权。 特别是在这种情况下,我将定义地址模型如下:
class Address < ActiveRecord::Base
belongs_to :address_owner, :polymorphic => true
...
end
使用:address_owner
作为关系名称应该有助于清除任何混淆,因为它可以清楚地表明地址属于某物或某人。 它可以属于用户,个人,客户,订单或企业等。
我反对单表继承,因为发运和开票地址仍然是最后一天,只是地址,即没有变体。 相比之下,一个人,一个订单或一家企业显然在本质上有很大的不同,因此是多态性的一个很好的例子。
对于订单,我们仍然希望地址address_owner_type
和address_owner_id
列反映该订单属于客户。 因此问题仍然存在:我们如何显示订单具有账单地址和送货地址?
我要使用的解决方案是将外键添加到送货地址和开票地址的订单表中。 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,因为Order既反映了交易的业务方面,又反映了客户方面,而购买是客户所做的事情。
如果需要,可以为地址定义关系的另一端:
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模型上的各个设置器。 订单模型将如下所示:
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_one
和belongs_to
关系允许我们跟踪送货地址和账单地址,而polymorphic
关系表明送货地址和账单地址属于订单。 该解决方案使我们两全其美。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.