This post originated from an RSS feed registered with Ruby Buzz
by Jay Fields.
Original Post: Move eval from Run-time to Parse-time
Feed Title: Jay Fields Thoughts
Feed URL: http://blog.jayfields.com/rss.xml
Feed Description: Thoughts on Software Development
You need to use eval, but want to limit the number of times eval is necessary.
classPerson defself.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
classPerson defself.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
Motivation
premature 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.
classPerson defself.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.
classPerson defself.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