#Value Objects and ActiveRecord
##Value Objects
###Properties
- A Measure or description of something
- Equality is dependent on values rather than an identity
- Immutable
###Examples: Time, Pathname, Currency, Address
##Reference Objects vs. Value Objects ###Reference Objects
-
Compared based on identity (ie primary key)
-
Have mutator methods
c1 = Customer.new(name: "George") c2 = Customer.new(name: "George") c1 == c2 #=> false c1.name = "Bob" #=> Updates name on customer object
###Value Objects
-
Compared based on values
-
Immutable
t1 = Time.new(2013, 06, 07) t2 = Time.new(2013, 06, 07) t1 == t2 #=> true t1 + 5.days #=> Returns new Time object
##ActiveRecord#composed_of
Note: it looks like composed_of
is going to be deprecated.
Example from ActiveRecord api:
Assuming a Customer
model with the attributes balance_amount
and balance_currency
we could create a Value Object representing the customer's balance:
class Customer < ActiveRecord::Base
composed_of :balance, class_name: "Money", mapping: [%w(balance_amount amount), %w(balance_currency currency)]
end
Money value object:
class Money
include Comparable
attr_reader :amount, :currency
DEFAULT_CURRENCY = "USD"
EXCHANGE_RATES = {
"USD_TO_SEK" => 6.5403,
"SEK_TO_USD" => 0.1529
}
def initialize(amount, currency = DEFAULT_CURRENCY)
@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 <=> other_money.amount
else
amount <=> other_money.exchange_to(currency).amount
end
end
end
##Benefits
-
Moves attribute logic to a separate class.
customer.balance.exchange_to("AUD")
-
Can provide a more expressive way to compare values:
customer.balance > Money.new(100, "SEK")
-
Side-effect free
- When a Value Object is going to be "changed," a new object should always be created instead.
##References