Mark Needham

Thoughts on Software Development

Ruby: Returning hashes using merge! and merge

with 7 comments

We came across an interesting problem today with some code which was unexpectedly returning nil.

The code that we had looked like this…

class SomeClass
	def our_method	
		a_hash = { :a => 2 }
		a_hash.merge!({:b => 3}) unless some_condition.nil?
	end
end

…and we didn’t notice the ‘unless’ statement on the end which meant that if ‘some_condition’ was nil then the return value of the method would be nil.

One way around it is to ensure that we explicitly return a_hash at the end of the method…

class SomeClass
	def our_method	
		a_hash = { :a => 2 }
		a_hash.merge!({:b => 3}) unless some_condition.nil?
		a_hash
	end
end

…but I think that looks a bit ugly.

Luckily Rails provides a method called ‘returning’ which I first learnt about from Reg Braithwaite’s blog post about the kestrel combinator.

That method is defined like so:

  def returning(value)
    yield(value)
    value
  end

And we can use it in our code like this:

class SomeClass
	def our_method	
		a_hash = { :a => 2 }
		returning a_hash do |h|
			h..merge!({:b => 3}) unless some_condition.nil?
		end
	end
end

Another way to return the merged hash without mutating the original would be to use the ‘merge’ method rather than ‘merge!’:

class SomeClass
	def our_method	
		a_hash = { :a => 2 }
		a.hash.merge(!some_condition.nil? ? {:b => 3} : {})
	end
end

We could use that approach with ‘merge!’ as well but I’m not sure that it reads as nicely as the version which uses the ‘unless’ way.

Another approach that I started messing around with could be this…

class SomeClass
  def our_method
    a_hash = { :a => 2 }
    merge_unless(a_hash, {:b => 3}, proc { some_condition.nil? })
  end
end
 
def merge_unless(hash, other_hash, condition)
  if condition.call()
    hash
  else
    hash.merge(other_hash)
  end 
end

…although that’s probably a bit over the top seeing the collection of other ways we already have.

Written by Mark Needham

September 21st, 2010 at 8:24 pm

Posted in Ruby

Tagged with

  • WT

    Okay, let me show you the Ruby 1.9 way:

    {:a => 2}.tap do |h|
    ! some_condition.nil? and h.merge!({:b => 3})
    end

    Thanks for the post, I hadn’t really thought about what to use Object#tap for until your overengineered methods reminded me of it.

  • Wayne Conrad

    tap has been backported to MRI 1.8.7 (yay!).

    Another good use for tap is to insert debug code in a long method chain:

    foo.collect {…}.sort.uniq.find_all {…}

    Suppose you want to know what the collect is returning:

    foo.collect {…}.tap { |o| p o }.sort.uniq.find_all {…}

  • http://blog.thepete.net Pete Hodgson

    I agree that merge_unless is probably a bit over-the-top, but I couldn’t resist adding another implementation:

    def merge_unless(hash, other_hash)
    yield ? hash : hash.merge(other_hash)
    end

    which you’d use like this:

    merge_unless(a_hash, {:b => 3}) { some_condition.nil? }

  • http://www.markhneedham.com Mark Needham

    @Pete – neat! I figured there should be a way to write a version like that but I didn’t know the syntax to write it as cleanly as you have, cool!

    @WT – that’s a pretty neat way too.

    I’m still intrigued how you know when to inverse a condition (!condition) and then do something as part of an and statement and when you should use an unless argument instead

  • WT

    Damian Conway, in Perl Best Practices, suggests that «unless» is always a bad idea. I just use «! if» instead.

  • Mamaliga

    one step at a time:

    0) working code samples would better than undefined methods and variables

    1) not
    a_hash.merge!({:b => 3}) unless some_condition.nil?
    but
    a_hash.merge!({:b => 3}) if some_condition

    2) not
    returning
    but
    tap
    which is backported in ruby 1.8.7

    3) proc is deprecated, use Proc.new or lambda if you need to

    4) there is no need to pass a proc to a non-block parameter, it could have sense if the last parameter is a hash, and you pass the condition as option in the hash

    5) you are wasting the time and polluting the mind of 1610 readers (1609 actually)

  • http://www.markhneedham.com Mark Needham

    @Mamaliga – Thanks for the feedback.

    I generally try to remove the parts of code examples which aren’t directly related to the concept that I’m exploring. I guess I didn’t do a good job on this post so I’ll try to fix that for the next few.

    I didn’t spot that we could use the if instead of unless so thanks for pointing that out.

    As I understand it ‘returning’ and ‘tap’ are pretty similar in how they work so does it matter that much which one you use?

    I don’t know all the Ruby conventions so I wasn’t sure whether or not it was necessary to specifically write the signature so that the client has to provide a block. I wasn’t aware that ‘proc’ is deprecated but thanks for pointing that out.

    It’s not my intention to waste people’s time – with Ruby I’m very much learning from people who comment on the posts pointing out better way things so for me at least it’s not totally wasted time.