跳轉到內容

Ruby on Rails/ActiveRecord/聚合

來自華夏公益教科書,開放書籍,開放世界

Active Record 透過類似宏的類方法實現聚合,稱為composed_of用於將屬性表示為值物件。它表達了諸如“帳戶[由]金錢[以及其他東西]組成”或“人[由][一個]地址組成”之類的關係。每次呼叫宏都會新增對如何從實體物件的屬性建立值物件(當實體作為新物件或從查詢現有物件初始化時)以及如何將其轉換回屬性(當實體儲存到資料庫時)的描述。

例子

  class Customer < ActiveRecord::Base
    composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
    composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
  end

客戶類現在具有以下方法來操作值物件

  * Customer#balance, Customer#balance=(money)
  * Customer#address, Customer#address=(address)

這些方法將使用下面描述的值物件進行操作

 class Money
   include Comparable
   attr_reader :amount, :currency
   EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
 
   def initialize(amount, currency = "USD")
     @amount, @currency = amount, currency
   end
 
   def exchange_to(other_currency)
     exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
     Money.new(exchanged_amount, other_currency)
   end
 
   def ==(other_money)
     amount == other_money.amount && currency == other_money.currency
   end
 
   def <=>(other_money)
     if currency == other_money.currency
       amount <=> amount
     else
       amount <=> other_money.exchange_to(currency).amount
     end
   end
 end

 class Address
   attr_reader :street, :city
   def initialize(street, city)
     @street, @city = street, city
   end
 
   def close_to?(other_address)
     city == other_address.city
   end
 
   def ==(other_address)
     city == other_address.city && street == other_address.street
   end
 end

現在可以透過值物件而不是屬性訪問資料庫中的屬性。如果您選擇將組合命名為與屬性名稱相同,這將是訪問該屬性的唯一方法。這適用於我們的餘額屬性。您可以像對待任何其他屬性一樣與值物件進行互動,儘管

 customer.balance = Money.new(20)     # sets the Money value object and the attribute
 customer.balance                     # => Money value object
 customer.balance.exchanged_to("DKK") # => Money.new(120, "DKK")
 customer.balance > Money.new(10)     # => true
 customer.balance == Money.new(20)    # => true
 customer.balance < Money.new(5)      # => false

值物件也可以由多個屬性組成,例如地址的情況。對映的順序將決定引數的順序。例子

 customer.address_street = "Hyancintvej"
 customer.address_city   = "Copenhagen"
 customer.address        # => Address.new("Hyancintvej", "Copenhagen")
 customer.address = Address.new("May Street", "Chicago")
 customer.address_street # => "May Street"
 customer.address_city   # => "Chicago"

編寫值物件

[編輯 | 編輯原始碼]

值物件是不可變的,可以互換的物件,它們表示給定的值,例如表示 5 美元的 Money 物件。兩個都表示 5 美元的 Money 物件應該相等(透過方法,例如 == 和 <=> 來自 Comparable 如果排名有意義)。這與實體物件不同,實體物件的相等性由標識決定。例如 Customer 這樣的實體類可以很容易地擁有兩個不同的物件,它們都具有在 Hyancintvej 上的地址。實體標識由物件或關係唯一識別符號(例如主鍵)決定。正常ActiveRecord::Base類是實體物件。

將值物件視為不可變也很重要。不要允許 Money 物件在建立後更改其金額。建立一個具有新值的新的貨幣物件。Money#exchanged_to 方法就是一個例子,該方法返回一個新的值物件,而不是更改其自己的值。Active Record 不會持久化透過除編寫器方法以外的其他方式更改的值物件。

Active Record 透過凍結任何分配為值物件的來強制執行不可變要求。之後嘗試更改它會導致 TypeError。

華夏公益教科書