Skip to content

Instantly share code, notes, and snippets.

@natesalisbury
Last active December 18, 2015 01:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save natesalisbury/5702000 to your computer and use it in GitHub Desktop.
Save natesalisbury/5702000 to your computer and use it in GitHub Desktop.
Notes for Überdev talk about Value Objects

#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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment