Skip to content

Instantly share code, notes, and snippets.

@matthewrudy
Created February 4, 2009 10:48
Show Gist options
  • Save matthewrudy/58054 to your computer and use it in GitHub Desktop.
Save matthewrudy/58054 to your computer and use it in GitHub Desktop.
Rails "delegate" with a :default
class Module
# Provides a delegate class method to easily expose contained objects' methods
# as your own. Pass one or more methods (specified as symbols or strings)
# and the name of the target object as the final :to option (also a symbol
# or string). At least one method and the :to option are required.
#
# Delegation is particularly useful with Active Record associations:
#
# class Greeter < ActiveRecord::Base
# def hello() "hello" end
# def goodbye() "goodbye" end
# end
#
# class Foo < ActiveRecord::Base
# belongs_to :greeter
# delegate :hello, :to => :greeter
# end
#
# Foo.new.hello # => "hello"
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
#
# Multiple delegates to the same target are allowed:
# class Foo < ActiveRecord::Base
# belongs_to :greeter
# delegate :hello, :goodbye, :to => :greeter
# end
#
# Foo.new.goodbye # => "goodbye"
#
#
# Methods can be delegated to instance variables, class variables, or constants
# by providing the variable as a symbol:
# class Foo
# CONSTANT_ARRAY = [0,1,2,3]
# @@class_array = [4,5,6,7]
#
# def initialize
# @instance_array = [8,9,10,11]
# end
# delegate :sum, :to => :CONSTANT_ARRAY
# delegate :min, :to => :@@class_array
# delegate :max, :to => :@instance_array
# end
#
# Foo.new.sum # => 6
# Foo.new.min # => 4
# Foo.new.max # => 11
#
# A default can be added which will be used in the case that the target is not truthy
# class Foo
# belongs_to :greeter
# delegate :said_hello?, :to => :greeter, :default => false
# end
#
# Foo.new.greeter # => nil
# Foo.new.said_hello? # => false
#
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)
module_eval(<<-EOS, "(__DELEGATION__)", 1)
def #{method}(*args, &block)
if #{to}
#{to}.__send__(#{method.inspect}, *args, &block)
else
#{options[:default].inspect}
end
end
EOS
else
module_eval(<<-EOS, "(__DELEGATION__)", 1)
def #{method}(*args, &block)
#{to}.__send__(#{method.inspect}, *args, &block)
end
EOS
end
end
end
end
class Delegated
attr_accessor :delegate_to
delegate :defaulted_to_nil, :to => :delegate_to, :default => nil
delegate :defaulted_to_symbol, :to => :delegate_to, :default => :some_symbol
delegate :not_defaulted, :to => :delegate_to
end
describe Module, "delegation" do
before(:each) do
@it = Delegated.new
end
describe "with default" do
it "should default to nil if the delegated attribute does not exist" do
@it.delegate_to.should == nil
@it.defaulted_to_nil.should == nil
end
it "should default to a given symbol if the delegated attribute does not exist" do
@it.delegate_to.should == nil
@it.defaulted_to_symbol.should == :some_symbol
end
it "should delegate to the provided target if it exists" do
to = stub("that which it is delegated to")
to.should_receive(:defaulted_to_nil).and_return(:something)
to.should_receive(:defaulted_to_symbol).and_return(:something_else)
@it.delegate_to = to
@it.defaulted_to_nil.should == :something
@it.defaulted_to_symbol.should == :something_else
end
end
describe "without default" do
it "should raise an exception if the target is nil" do
@it.delegate_to.should == nil
lambda {@it.not_defaulted}.should raise_error(NoMethodError)
end
it "should delegate to the provided target if it exists" do
to = stub("that which it is delegated to")
to.should_receive(:not_defaulted).and_return(:something)
@it.delegate_to = to
@it.not_defaulted.should == :something
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment