[英]Rails 4: Has_one polymorphic association not working
I have two models Purchase
and Address
. 我有两个模型
Purchase
和Address
。 I'm trying to make Address
polymorphic so I can reuse it in my Purchase
model for has_one :billing_address
and has_one :shipping_address
. 我正在尝试使
Address
多态,以便可以在has_one :billing_address
和has_one :shipping_address
Purchase
模型中重用它。 Here's my schema: 这是我的架构:
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
address model: 地址模型:
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
...
end
purchase model: 购买模式:
class Purchase < ActiveRecord::Base
has_one :shipping_address, as: :addressable
has_one :billing_address, as: :addressable
...
end
Everything looks fine to me, but my Rspec tests fail: 一切对我来说都很好,但是我的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)>'
It doesn't seem to be detecting that the association is polymorphic. 似乎没有检测到关联是多态的。 Even in my console:
即使在我的控制台中:
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
What am I doing wrong?? 我究竟做错了什么??
You need to specify the :class_name
option for the has_one
association, as the class name can't be inferred from the association name ie, :shipping_address
and :billing_address
in your case doesn't give an idea that they refer to class Address
. 您需要为
has_one
关联指定:class_name
选项,因为无法从关联名称中推断出类名称,即:shipping_address
和:billing_address
在您的情况下并不能提供它们引用了Address
类的想法。
Update the Purchase
model as below: 如下更新
Purchase
模型:
class Purchase < ActiveRecord::Base
has_one :shipping_address, class_name: "Address", as: :addressable
has_one :billing_address, class_name: "Address", as: :addressable
## ...
end
I think you've misunderstood what polymorphic associations are for. 我认为您误解了多态关联的含义。 They allow it to belong to more than one model, Address here is always going to belong to Purchase.
他们允许它属于一个以上的模型,这里的地址始终将属于Purchase。
What you've done allows an Address to belong to say, Basket or Purchase. 您所做的操作允许地址属于购物篮或购买。 The addressable_type is always going to be Purchase.
addressable_type总是要购买。 It won't be ShippingAddress or BillingAddress which I think you think it will be.
不会是ShippingAddress或BillingAddress,我认为您会的。
p.build_shipping_address
doesn't work because there isn't a shipping address model. p.build_shipping_address
不起作用,因为没有送货地址模型。
Add class_name: 'Address'
and it will let you do it. 添加
class_name: 'Address'
,它将允许您执行此操作。 However it still won't work the way you expect. 但是,它仍然无法按您期望的方式工作。
I think what you actually want is single table inheritance. 我认为您真正想要的是单表继承。 Just having a type column on 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
This should be fine because shipping and billing address will have the same data, if you've got lots of columns that are only in one or the other it's not the way to go. 这应该没问题,因为送货和帐单邮寄的地址将具有相同的数据,如果您只有很多列,那么就不行了。
Another implementation would be to have shipping_address_id and billing_address_id on the Purchase model. 另一种实现是在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
The belongs_to :shipping_address
will tell rails to look for shipping_address_id with the class name telling it to look in the addresses table. belongs_to :shipping_address
将告诉Rails查找shipping_address_id,其类名告诉它在地址表中查找。
It is somewhat of a convention to name the polymorphic relation something that ends in "able". 将多态关系命名为以“ able”结尾的东西在某种程度上是一种约定。 This makes sense if it conveys some sort of action.
如果它传达某种动作,这是有道理的。 For example and image is "viewable", an account is "payable", etc. However, sometimes it is hard to come up with a term that ends in "able" and when forced to find such a term, it can even cause a bit of confusion.
例如,图像是“可见的”,帐户是“可支付的”,等等。但是,有时很难想出以“ able”结尾的术语,当被迫找到这样的术语时,它甚至可能导致有点混乱。
Polymorphic associations often show ownership. 多态关联通常显示所有权。 For this case in particular, I would would define the Address model as follows:
特别是在这种情况下,我将定义地址模型如下:
class Address < ActiveRecord::Base
belongs_to :address_owner, :polymorphic => true
...
end
Using :address_owner
for the relation name should help clear up any confusion as it makes it clear that an address belongs to something or someone. 使用
:address_owner
作为关系名称应该有助于清除任何混淆,因为它可以清楚地表明地址属于某物或某人。 It could belong to a user, a person, a customer, an order, or a business, etc. 它可以属于用户,个人,客户,订单或企业等。
I would argue against single table inheritance as shipping and billing addresses are still at the end of the day, just addresses, ie, there is no morphing going on. 我反对单表继承,因为发运和开票地址仍然是最后一天,只是地址,即没有变体。 In contrast, a person, an order, or a business are cleary very different in nature and therefore make a good case for polymorphism.
相比之下,一个人,一个订单或一家企业显然在本质上有很大的不同,因此是多态性的一个很好的例子。
In the case of an order, we still want the columns address_owner_type
and address_owner_id
to reflect that an order belongs to a customer. 对于订单,我们仍然希望地址
address_owner_type
和address_owner_id
列反映该订单属于客户。 So the question remains: How do we show that an order has a billing address and a shipping address? 因此问题仍然存在:我们如何显示订单具有账单地址和送货地址?
The solution that I would go with is to add foreign keys to the orders table for the shipping address and billing address. 我要使用的解决方案是将外键添加到送货地址和开票地址的订单表中。 The Order class would look something like the following:
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
Note: I am using Order for the class name in favor of Purchase, as an order reflects both the business and customer sides of a transaction, whereas, a purchase is something that a customer does. 注意:我正在使用Order作为类名,而不是Purchase,因为Order既反映了交易的业务方面,又反映了客户方面,而购买是客户所做的事情。
If you want, you can then define the opposite end of the relation for 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
The one other bit of code to clear up yet, is how to set the shipping and billing addresses? 需要清除的另一段代码是如何设置送货地址和帐单地址? For this, I would override the respective setters on the Order model.
为此,我将覆盖Order模型上的各个设置器。 The Order model would then look like the following:
订单模型将如下所示:
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
This solution solves the problem by defining two relations for an address. 该解决方案通过为地址定义两个关系来解决该问题。 The
has_one
and belongs_to
relation allows us to track shipping and billing addresses, whereas the polymorphic
relation shows that the shipping and billing addresses belong to an order. has_one
和belongs_to
关系允许我们跟踪送货地址和账单地址,而polymorphic
关系表明送货地址和账单地址属于订单。 This solution gives us the best of both worlds. 该解决方案使我们两全其美。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.