This idiom I have seen a little more in Rails than I have seen in Ruby, but I am putting it in this Ruby Idioms series anyway.
First off, most of you will know by know that you can "transparently" provide a method in Ruby a Hash. What do I mean by "transparently"? Well have a look at the code example below:
user = User.find(:first, :include => :preferences)
The last argument is actually a Hash even though you do not see the curly braces at all.
In my opinion it makes for more readable code. So let us look behind
the scenes at what the User.find method might do if it wasn't a magic
method from ActiveRecord
:
class User
# ... some stuff here
class << self
def find(type, params = {})
options = @@DEFAULTS.merge(params)
case type
when :first
options[:limit] = 1
when :all
# do stuff with options and query database
# bla bla bla
end
# rest of implementation
end
end
# ... some stuff here
end
This is quite useful, but one of the idioms in the same vain that I really appreciate is the following usage:
validates_length_of :name, :title, :company, :in => 2..128
This provides another example of supplying a transparent Hash
arguments at the end of a list of arguments, the length of which is
unknown. For example we may decide that the code needs to actually be
more like:
validates_length_of :name, :company, :in => 2..64
validates_length_of :title, :in => 2..128, :allow_nil => true # to cater for those ridiculously long job titles out there or those that do not have a job title at all
Now you will notice that the validates_length_of
method accepts a
list of variable size for the list of attribute symbols to check and
then at the end an option hash to specify length checking specifics.
How does the method know there is an option Hash at the end to use for
these things and/or how long the list of attributes is?
The secret is in the sauce. If we were to look in the source code (though I am not, because I have seen it so many times before), we would see something like:
def validates_length_of(*attrs)
options = @@DEFAULT_OPTIONS.merge(attrs.pop.symbolize_keys) if attrs.last.is_a?(Hash)
# then continue with rest of implementation.
end
All it is doing here is in the signature, specifying that there is a
list of unknown length to be supplied to the method. Then it pops the
last item off that list (Array
) if and only if the last element of
the list is a Hash
for use as the options that the implementation
will use.
Until the next time enjoy Ruby's "transparent" Hash
idioms.
If you enjoyed this content, please consider sharing this link with a friend, following my GitHub, Twitter/X or LinkedIn accounts, or subscribing to my RSS feed.