DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Luke has posted 4 posts at DZone. View Full User Profile

Using Ruby Hashes As Keyword Arguments, With Easy Defaults

07.27.2006
| 17276 views |
  • submit to reddit
        Similar to many Rails helpers/methods, a lot of the methods I write often use an optional hash of options, or sometimes just a hash only, to simulate keyword arguments (often using symbols).

The only downside to doing this is you lose out on easily setting default values using Ruby's default method argument values. You might use code something similar to the following to make up for this:

def some_method(opts={})
  my_foo =  opts[:foo] || 'mydefaultfoo'
end

However, as you have more and more keyword options, setting defaults in this way gets rather tedious. Fortunately, Ruby's Hash#merge comes to our rescue (almost) - it allows you to merge the contents of one hash with another. The only problem - any duplicate keys in the hash you are merging will overwrite your original hash values - when it comes to setting default values, we want this to work the other way around; we only want values in the defaults hash to be merged if they do not exist in the original hash. Again, Ruby comes to our rescue - Hash#merge takes a block as an argument and will pass any duplicate values that crop up into the block - we can use this block to decide which value to keep.

Using the simple monkey patch to the Hash class below, you will no longer have to set each default individually:

class Hash
  def with_defaults(defaults)
    self.merge(defaults) { |key, old, new| old.nil? ? new : old } 
  end

  def with_defaults!(defaults)
    self.merge!(defaults) { |key, old, new| old.nil? ? new : old }
  end
end

Of course, sticking with Ruby naming conventions, with_defaults() will return a new hash whilst with_defaults!() will change the original hash directly. 

See http://www.lukeredpath.co.uk/index.php/2006/07/27/using-ruby-hashes-as-keyword-arguments-with-easy-defaults/ for further discussion and alternatives.    

Comments

Gergely Nagy replied on Wed, 2009/07/15 - 6:07am

Should not we extract it defaults to a constant instead? (just because it is constant, and could in principle save some initialisation cost) OTOH, I would normally need more features, like detecting unknown keys, perhaps mark some required, and even say ':key1 requires :key2 to be specified' as well. Does anyone know some helper class for this purpose? Would it be possible wrap that in a 'macro' to make it easier to use: def_with_keywords(:some_method, keywords(:key1, :key2 =>"default1",:key2=>requires(:key1))) { |keywords, arg1, arg2|) puts keywords.key1 # accessor method on keywords object ... end .. some_method(1,2, :key1=>3) #ok some_method(1,2, :key2=>4) #error: key2 requires key1 some_method(1,2, :unknown_key=>-1) #error or something like that? (until, of course Ruby 2.0 gives us what we want)

Rob Sanheim replied on Tue, 2006/07/25 - 4:11pm

I gotta agree with fatgecko. The idiom I've been using is: def method(options = {}) options = { :key1 => default1, :key2 => default2 }.merge(options) .... if the defaults get to be too long where that line wraps, I'll extract it to a local.

Simon Baird replied on Mon, 2006/08/07 - 1:15am

Can't we just do this: class Hash def with_defaults(defaults) defaults.merge(self) end def with_defaults!(defaults) defaults.merge!(self) end end Or why not just this kind of thing? def some_method(opts={}) opts = default_opts.merge(opts) my_foo = opts[:foo] end