Argument hash to string to proc to instance variables

It is idiomatic to pass a number of related arguments (usually config
related) as the last (braceless argument) to a method ( heavily used
in Rails ) e.g .

def my_method(arg1, arg2)
end

where arg1 is anything and arg2 is a hash

my_method(“hello”, :a=>“nasir”, :b => 30, :c=“khan”) etc.

It is also quite popular to instance_eval a block to initialize a
number of attributes rather than pass them all upfront to “new”. e.g.

class A
attr_accessor :a, :b, :c
def initialize(&block)
instance_eval(&block) if block_given?
end
end

x = A.new do
self.a = “hello”;
self.b = 1;
self.c = “bye”;
end

It is also trivial to use these two techniques together i.e. take the
hash argument in the method and use it to create a proc which can be
evaled as a block e.g.

def my_method(arg1, arg2)
str = “lambda { "
arg2.each_pair {|k,v| str << sprintf(“self.%s=%s;”, k, v) }
str << " }”
p = eval str
x = A.new &p
end
end

Here “my_method” returns the instance of class A initialized with the
attributes given
as hash which is converted to block and instance_evaled in the
initialize method of class A.

All this works but I am doing two evals (one for string to proc in
my_method and one the instance_evaling of block in initialize, not to
mention constructing the string for proc.

Is there is a simpler/faster alternative that I have overlooked?

  • Nasir

On Mon, May 14, 2007 at 01:14:54PM +0900, Nasir K. wrote:

x = A.new do
str = "lambda { "
initialize method of class A.

All this works but I am doing two evals (one for string to proc in
my_method and one the instance_evaling of block in initialize, not to
mention constructing the string for proc.

Is there is a simpler/faster alternative that I have overlooked?

Don’t worry about instance_eval with a block. All it does it change
‘self’
and run the existing block; you are not running the compiler.

It’s the string form of eval which should concern you, as (a) you’re
compiling code on the fly, which is inefficient, and (b) you have to be
very
careful about injection attacks, e.g.

my_method(nil, :foo => ‘system(“rm -rf /*”)’)

You could use ‘send’ or ‘instance_variable_set’ instead. i.e.

send("#{k}=", v) # invoke the #{k}= method
or
instance_variable_set("@#{k}", v) # set @{k} to v

You’ll see the first form in ActiveRecord quite a lot.

Regards,

Brian.