You need to use eval, but want to limit the number of times eval is necessary.
class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
define_method attribute do
eval "@#{attribute} ||= #{default_value}"
end
end
end
attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end
becomes
class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
eval "def #{attribute}
@#{attribute} ||= #{default_value}
end"
end
end
attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end
Motivationpremature optimization is the root of all evil -- Knuth, Donald
I'll never advocate for premature optimization, but this refactoring can be helpful when you determine that eval is a source of performance pain. The Kernel#eval method can be the right solution in some cases; but it is almost always more expensive (in terms of performance) than it's alternatives. In the cases where eval is necessary, it's often better to move an eval call from run-time to parse-time.
Mechanics- Expand the scope of the string being eval'd.
- Test
Example
The following Person class uses eval to define the logic the readers rely upon for returning a default value if no value has previously been set.
class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
define_method attribute do
eval "@#{attribute} ||= #{default_value}"
end
end
end
attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end
The above example executes without issue, but it relies upon eval each time a reader is called. If multiple calls to eval are determined to be problematic the solution is to expand the eval to include defining the method itself.
class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
eval "def #{attribute}
@#{attribute} ||= #{default_value}
end"
end
end
attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end

Read: Move eval from Run-time to Parse-time