Rails contains a handy shortcut for adhering to The Principle of Least Knowledge. If your Person class has an address, and you want to get the person’s country, you can delegate it like so:
class Person < ActiveRecord::Base
...
delegate :country, :to => :address
...
end
This is all well and good if you can be sure that there will always be an address for any given person. If address is nil and country is called, however, an exception will be raised (because nil.country is undefined).
Sometimes it’s helpful to be able to specify a default value, so I extended the delegate method to allow you to write:
class Person < ActiveRecord::Base
...
delegate :country, :to => :address, :default => "United Kingdom"
...
end
A word of caution however: use this with restraint. In general, default values should be stored in the database, not in your code. A next step for this might be for delegate to be association-aware, so that it can look up defaults from the database automatically when it encounters a nil object.
Here’s the code (mostly based on the current Rails implementation):
class Module
def delegate(*methods)
options = methods.pop
unless options.is_a?(Hash) && to = options[:to]
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
end
methods.each do |method|
if options.has_key? :default
code = "(#{to} && #{to}.__send__(#{method.inspect}, *args, &block)) || #{options[:default].inspect}"
else
code = "#{to}.__send__(#{method.inspect}, *args, &block)"
end
module_eval(<<-EOS, "(__DELEGATION__)", 1)
def #{method}(*args, &block)
#{code}
end
EOS
end
end
end