Since then, I've been thinking that a nice alternative might be a constructor that takes one argument without a name, but any additional arguments must be named (via a hash). Yes, I basically stole this concept from Smalltalk.
initialize_with :employee_id, :first_name, :last_name
attr_reader :employee_id, :first_name, :last_name
end
initialize_with :amount
attr_reader :amount
end
Below is a solution to making the above code execute as expected.
first_arg = args.shift
define_method :initialize do |*arg|
instance_variable_set "@", arg.shift
required_args = args.inject(first_arg.to_s) {|result, attribute| result << ", " }
raise ArgumentError.new("initialize requires ") if args.any? && arg.empty?
args.each do |attribute|
raise ArgumentError.new("initialize requires ") unless arg.first.has_key?(attribute)
instance_variable_set "@", arg.first[attribute]
end
end
end
end
One other small change that you might notice is that I've made all the arguments required. That's a preference that I might blog about later, but I think it should be easy enough to remove the raises if you want your options arguments to be optional.
For those interested, here's the code with tests.
first_arg = args.shift
define_method :initialize do |*arg|
instance_variable_set "@", arg.shift
required_args = args.inject(first_arg.to_s) {|result, attribute| result << ", " }
raise ArgumentError.new("initialize requires ") if args.any? && arg.empty?
args.each do |attribute|
raise ArgumentError.new("initialize requires ") unless arg.first.has_key?(attribute)
instance_variable_set "@", arg.first[attribute]
end
end
end
end
initialize_with :employee_id, :first_name, :last_name
attr_reader :employee_id, :first_name, :last_name
end
initialize_with :amount
attr_reader :amount
end
unit_tests do
test "verify Person requires all options" do
assert_raises ArgumentError do
Person.new(2, :first_name => 'mike')
end
end
test "verify Person requires first arg" do
assert_raises ArgumentError do
Person.new(:first_name => 'mike', :last_name => 'ward')
end
end
test "verify Person employee_id" do
mike = Person.new(2, :first_name => 'mike', :last_name => 'ward')
assert_equal 2, mike.employee_id
end
test "verify Person first_name" do
mike = Person.new(2, :first_name => 'mike', :last_name => 'ward')
assert_equal 'mike', mike.first_name
end
test "verify Person last_name" do
mike = Person.new(2, :first_name => 'mike', :last_name => 'ward')
assert_equal 'ward', mike.last_name
end
test "verify Money amount" do
money = Money.new(10)
assert_equal 10, money.amount
end
end