Thursday, April 13, 2006

Ruby Initialization Chain Module

The Ruby Initialization Chain module comes from my current project where I was using the Initialization Chain pattern. The Initialization Chain module allows you to call setter methods that set a value and return the instance. This chaining of method calls allows you to initialize an object with one line of code.
foo = Foo.new.set_bar("baz).set_cat("dog")
If you were to define the setter methods, they would all follow the same basic pattern
def bar=(value)
@bar = value
end

def set_bar(value)
bar=value
end
The Initialization Chain module assumes you will follow this pattern and removes the need to define them.

Code and Tests:
module InitializationChain
alias :pre_init_chain_method_missing :method_missing
def method_missing(sym, *args, &block)
if matches_set_and_writer_exists?(sym)
self.send convert_set_to_setter(sym), *args, &block
return self
end
pre_init_chain_method_missing(sym, *args, &block)
end

def matches_set_and_writer_exists?(sym)
matches_set?(sym) && writer_exists?(sym)
end

def match_regex
/^set_/
end

def convert_set_to_setter(sym)
"#{sym.to_s.sub(match_regex,'')}=".to_sym
end

def writer_exists?(sym)
self.respond_to?(convert_set_to_setter(sym))
end

def matches_set?(sym)
sym.to_s =~ match_regex
end
end

class Foo
include InitializationChain

attr_accessor :bar

def cat
@cat
end

def cat=(value)
@cat=value
end
end

require 'test/unit'

class InitializationChainTest < Test::Unit::TestCase
def test_matches_set
assert(Foo.new.matches_set?(:set_foo))
end

def test_writer_exists
assert(Foo.new.writer_exists?(:bar))
end

def test_add_dynamic_set_for_attr
foo = Foo.new.set_bar("baz")
assert_equal "baz", foo.bar
end

def test_add_dynamic_set_for_defined_setter
foo = Foo.new.set_cat("dog")
assert_equal "dog", foo.cat
end
end
Post a Comment