简体   繁体   English

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

[英]Rails 4: Has_one polymorphic association not working

I have two models Purchase and Address . 我有两个模型PurchaseAddress 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_addresshas_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_typeaddress_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_onebelongs_to关系允许我们跟踪送货地址和账单地址,而polymorphic关系表明送货地址和账单地址属于订单。 This solution gives us the best of both worlds. 该解决方案使我们两全其美。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM