_\[This is [a DZone Snippet I created in August 2007][dzone-snippet]. DZone Snippets is now a defunct site, so I’m reposting it here.\]_ Dependency injection in Ruby is as easy as falling off a log. As [Jamis Buck has pointed out][jamis-buck-net-ssh-revisited], DI is a good thing, but DI frameworks for Ruby are overkill. The language makes them unnecessary. Here's how to enhance _[Module#attr_reader][ruby-module-attr-reader]_ and _[#attr_accessor][ruby-module-attr-accessor]_ so that they can receive an options hash for specifying the default value of an attribute. ```ruby module AttrWithDefaultExtension module ClassMethods def attr_accessor(*args) attrs, attrs_with_defaults = split_for_last_hash(args) attrs_with_defaults.each do |name, default| attr_reader_with_default name, default attr_writer name end super(*attrs) end def attr_reader(*args) attrs, attrs_with_defaults = split_for_last_hash(args) attrs_with_defaults.each do |name, default| attr_reader_with_default name, default end super(*attrs) end private def attr_reader_with_default(name, default) define_method(name) do unless instance_variable_defined?("@#{name}") default = default.call(self) if default.kind_of?(Proc) instance_variable_set "@#{name}", default end instance_variable_get "@#{name}" end end def split_for_last_hash(args) if args.last.kind_of?(Hash) [args[0...-1], args.last] else [args, {}] end end end def self.included(other_module) other_module.extend ClassMethods end end class Object; include AttrWithDefaultExtension; end ``` Here are the unit tests. They demonstrate not only the enhanced behavior of _#attr_reader_ and _#attr_accessor_, but also that the standard behavior remains unbroken. ```ruby require 'test/unit' module AttrWithDefaultExtensionTest class TwoStandardAttrReaders < Test::Unit::TestCase attr_reader :foo, :baz def test_should_not_define_first_instance_variable assert_equal false, instance_variable_defined?(:@foo) end def test_should_return_first_instance_variable_when_sent_first_attr_reader @foo = 'bar' assert_equal 'bar', foo end def test_should_not_define_second_instance_variable assert_equal false, instance_variable_defined?(:@baz) end def test_should_return_second_instance_variable_when_sent_second_attr_reader @baz = 'bat' assert_equal 'bat', baz end end class TwoStandardAttrAccessors < Test::Unit::TestCase attr_accessor :foo, :baz def test_should_not_define_first_instance_variable assert_equal false, instance_variable_defined?(:@foo) end def test_should_return_first_instance_variable_when_sent_first_attr_reader @foo = 'bar' assert_equal 'bar', foo end def test_should_set_first_instance_variable_when_sent_first_attr_writer self.foo = 'bar' assert_equal 'bar', @foo end def test_should_not_define_second_instance_variable assert_equal false, instance_variable_defined?(:@baz) end def test_should_return_second_instance_variable_when_sent_second_attr_reader @baz = 'bat' assert_equal 'bat', baz end def test_should_set_second_instance_variable_when_sent_second_attr_writer self.baz = 'bat' assert_equal 'bat', @baz end end class TwoStandardAttrReadersAndADefault < Test::Unit::TestCase attr_reader :foo, :baz, :blit => 'blat' def test_should_not_define_first_instance_variable assert_equal false, instance_variable_defined?(:@foo) end def test_should_return_first_instance_variable_when_sent_first_attr_reader @foo = 'bar' assert_equal 'bar', foo end def test_should_not_define_second_instance_variable assert_equal false, instance_variable_defined?(:@baz) end def test_should_return_second_instance_variable_when_sent_second_attr_reader @baz = 'bat' assert_equal 'bat', baz end def test_should_not_define_third_instance_variable assert_equal false, instance_variable_defined?(:@blit) end def test_should_set_third_instance_variable_when_sent_third_attr_reader blit assert_equal 'blat', @blit end def test_should_not_set_third_instance_variable_if_already_set_when_sent_third_attr_reader @blit = 'splat' assert_equal 'splat', blit end end class TwoStandardAttrAccessorsAndADefault < Test::Unit::TestCase attr_accessor :foo, :baz, :blit => 'blat' def test_should_not_define_first_instance_variable assert_equal false, instance_variable_defined?(:@foo) end def test_should_return_first_instance_variable_when_sent_first_attr_reader @foo = 'bar' assert_equal 'bar', foo end def test_should_set_first_instance_variable_when_sent_first_attr_writer self.foo = 'bar' assert_equal 'bar', @foo end def test_should_not_define_second_instance_variable assert_equal false, instance_variable_defined?(:@baz) end def test_should_return_second_instance_variable_when_sent_second_attr_reader @baz = 'bat' assert_equal 'bat', baz end def test_should_set_second_instance_variable_when_sent_second_attr_writer self.baz = 'bat' assert_equal 'bat', @baz end def test_should_not_define_third_instance_variable assert_equal false, instance_variable_defined?(:@blit) end def test_should_set_third_instance_variable_when_sent_third_attr_reader blit assert_equal 'blat', @blit end def test_should_not_set_third_instance_variable_if_already_set_before_sent_third_attr_reader @blit = 'splat' assert_equal 'splat', blit end def test_should_set_third_instance_variable_when_sent_third_attr_writer self.blit = 'splat' assert_equal 'splat', @blit end end class AProcDefault < Test::Unit::TestCase attr_accessor :my_object_id => Proc.new { |obj| obj.object_id } def test_should_not_define_instance_variable assert_equal false, instance_variable_defined?(:@my_object_id) end def test_should_set_instance_variable_to_result_of_proc_when_sent_attr_reader my_object_id assert_equal object_id, @my_object_id end def test_should_not_set_instance_variable_if_already_set_before_sent_attr_reader @my_object_id = 'foo' assert_equal 'foo', my_object_id end def test_should_set_instance_variable_when_sent_attr_writer self.my_object_id = 'foo' assert_equal 'foo', @my_object_id end end class AProcWithinAProcDefault < Test::Unit::TestCase attr_accessor :my_proc => Proc.new { Proc.new { } } def test_should_not_define_instance_variable assert_equal false, instance_variable_defined?(:@my_proc) end def test_should_set_instance_variable_to_result_of_outer_proc_when_sent_attr_reader my_proc assert_kind_of Proc, @my_proc end def test_should_not_set_instance_variable_if_already_set_before_sent_attr_reader @my_proc = 'foo' assert_equal 'foo', my_proc end def test_should_set_instance_variable_when_sent_attr_writer self.my_proc = 'foo' assert_equal 'foo', @my_proc end end end ``` [dzone-snippet]: http://web.archive.org/web/20070807142358/http://snippets.dzone.com/posts/show/4382 [jamis-buck-net-ssh-revisited]: http://web.archive.org/web/20070807142358/http://weblog.jamisbuck.org/2007/7/29/net-ssh-revisited#comment-2734 "“Net::SSH revisited” by Jamis Buck, July 29, 2007" [ruby-module-attr-reader]: http://ruby-doc.org/core/Module.html#method-i-attr_reader [ruby-module-attr-accessor]: http://ruby-doc.org/core/Module.html#method-i-attr_accessor