Value Objects implementation in Ruby

Hi,

I am reading currently one design pattern called the _value object_. To
understand it I am reading a blog post http://www.sitepoint.com/value-objects-explained-with-ruby/, as it contains a Ruby example.

The problem is the author of the post said, that, whenever you will try to
change the attribute of a value object, you should create a new value object.
Otherwise you will break the rule of _value object_ . But we know in Ruby, the
_setter_ method always return the value it sets.

#!/usr/bin/env ruby

class Money
  attr_reader :amount, :currency

  def initialize(amount, currency)
    @amount = amount
    @currency = currency
  end

  def amount=(other_amount)
    Money.new(other_amount, currency)
  end

  def ==(other_money)
    self.class == other_money.class && amount == other_money.amount &&
currency == other_money.currency
  end

  alias :eql? :==

  def hash
    [amount, currency].hash
  end
end

usd1 = Money.new(10, 'usd')
usd2 = Money.new(10, 'usd')

usd1.eql?(usd2) # => true
usd1 == usd2 # => true

usd = Money.new(10, 'USD')
p usd.inspect

other_usd = (usd.amount = 20)

p usd.inspect
p other_usd.inspect
# >> "#<Money:0xa12f354 @amount=10, @currency=\"USD\">"
# >> "#<Money:0xa12f354 @amount=10, @currency=\"USD\">"
# >> "20"

Look the output of other_usd.inspect which returns "20", as per the author it
should return a new Money object.

My question is how then we implement/correct this flaw, which author missed to
mention ?

···

--

Regards,
Arup Rakshit

Debugging is twice as hard as writing the code in the first place. Therefore,
if you write the code as cleverly as possible, you are, by definition, not
smart enough to debug it.

--Brian Kernighan

I’d refrain from putting setters on immutable value objects. With the Money object that has both a currency and amount, don’t modify the amount, instead add a Numeric or Money instance to it to get a new Money object.

m = Money.new 100, ‘USD’

m + 50 #=> #<Money 150 USD>

m + Money.new(90, ‘USD’) #=> #<Money 190 USD>

m + Money.new(100, ‘CND’) # raises Money::NonMatchingCurrencyError

···

On Aug 31, 2014, at 22:43, Arup Rakshit <aruprakshit@rocketmail.com> wrote:

The problem is the author of the post said, that, whenever you will try to
change the attribute of a value object, you should create a new value object.
Otherwise you will break the rule of _value object_ . But we know in Ruby, the
_setter_ method always return the value it sets.

Do not use an assignment method then. :slight_smile: As Bryce mentioned one
approach is to provide proper mathematical constructs. I wrote about
this once:
http://blog.rubybestpractices.com/posts/rklemme/019-Complete_Numeric_Class.html

For changing the currency you probably need an explicit conversion method, e.g.

def to_currency(target_currency, conversion_factor = 1)
  self.class.new[amount * conversion_factor, target_currency]
end

Btw. to make the approach more robust you can freeze the instance at
the end of #initialize.

Kind regards

robert

···

On Mon, Sep 1, 2014 at 4:43 AM, Arup Rakshit <aruprakshit@rocketmail.com> wrote:

I am reading currently one design pattern called the _value object_. To
understand it I am reading a blog post Value Objects Explained with Ruby — SitePoint, as it contains a Ruby example.

The problem is the author of the post said, that, whenever you will try to
change the attribute of a value object, you should create a new value object.
Otherwise you will break the rule of _value object_ . But we know in Ruby, the
_setter_ method always return the value it sets.

My question is how then we implement/correct this flaw, which author missed to
mention ?

--
[guy, jim].each {|him| remember.him do |as, often| as.you_can - without end}
http://blog.rubybestpractices.com/