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

时间:2014-06-04 14:18:57

标签: ruby-on-rails

我有两个模型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 <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

我做错了什么?

3 个答案:

答案 0 :(得分:5)

您需要为:class_name关联指定has_one选项,因为无法从关联名称推断出类名,即:shipping_address和{{1}在你的情况下,并没有提出他们引用课程:billing_address

更新Address模型,如下所示:

Purchase

答案 1 :(得分:1)

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

您所做的事情允许地址属于说,篮子或购买。 addressable_type始终为Purchase。它不会是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,并使用类名告诉它查看地址表。

答案 2 :(得分:0)

将多态关系命名为&#34; able&#34;是一种惯例。如果它传达某种行为,这是有道理的。例如,图像是&#34;可查看&#34;,帐户是&#34;可支付&#34;等等。但是,有时候很难想出一个结束于&#34;能够&#34; ;当被迫找到这样一个术语时,它甚至会引起一些混乱。

多态关联通常显示所有权。特别是对于这种情况,我会按如下方式定义地址模型:

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

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

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

如果是订单,我们仍希望列address_owner_typeaddress_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,因为订单反映了交易的业务和客户方面,而购买是客户所做的事情。

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

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关系则表明送货地址和帐单地址属于订单。这个解决方案为我们提供了两全其美的优势。