Neat Ruby Tricks: Automatically Raising Exceptions With Hash
Hashes are a fundamental and useful data type in Ruby. In their
default state if a key is not found in the hash the hash returns
nil:
h = { :a => 1, :b => 2 } h[ :a ] # => 1 h[ :c ] # => nil
Hashes have also becoming a common idiom for passing dynamic named arguments to methods:
def foo( parameters ) puts parameters[ :bar ] if parameters[ :bar ] puts paramsters[ :baz ] if paramsters[ :baz ] end # outputs 'hello' foo( :bar => 'hello' ) # outpus 'hello' and 'world' foo( :bar => 'hello', :baz => 'world' )
The need to check whether a parameter is present as we did above is one of the drawbacks of using a hash. Using the often overlooked Hash.new initialisation method for hashes allows us to define a block that is called whenever a key has no value defined:
h = Hash.new do |hash,key| "No value defined for key: #{ key }" end hash[ :a ] = 1 # Returns 1 h[ :a ] # Returns "No value defined for key: b" h[ :b ]
If we raise an exception in that block then that exception will only be raised if a key is not found:
h = Hash.new do |hash,key| raise( "Key #{ key } is not valid" ) end h[ :a ] = 1 # Returns 1 h[ :a ] # Raises a RuntimeError with "Key b is not valid" h[ :b ]
Instead of forcing your users to pass through a specially constructed hash you can just do it transparently in your method (or in a utility method called from your method) using Hash#update. This provides a convenient way of checking that all the keys in use in a method or block of code are defined without having to manually check each key or forcing hte user to do it for you. You can even ‘segregate’ your keys into keys which are required and keys which are not by creating a hash with a raise block and only using it for the required keys. Putting everything together with some exception handling and you’d end up with
def create_book( parameters ) required = Hash.new do |h,k| raise( "Attribute #{k} is required" ) end required.update( parameters ) begin Book.new( required[ :title ], # Title required required[ :author ], # Author required required[ :isbn ], # ISBN required parameters[ :num_pages ], # Optional parameters[ :cover_artist ]) # Optional rescue RuntimeError => error nil # Return nil if required parameter missing end end
which doesn’t look half bad. No ‘if parameter…’ expressions all over the place checking if values actually exist in the hash.
Farrel Lifson is a lead developer at Aimred.