簡體   English   中英

Rails 4:Has_one多態關聯不起作用

[英]Rails 4: Has_one polymorphic association not working

我有兩個模型PurchaseAddress 我正在嘗試使Address多態,以便可以在has_one :billing_addresshas_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_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,因為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_onebelongs_to關系允許我們跟蹤送貨地址和賬單地址,而polymorphic關系表明送貨地址和賬單地址屬於訂單。 該解決方案使我們兩全其美。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM