[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

argument hash to string to proc to instance variables

Nasir Khan

5/14/2007 4:15:00 AM

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

1 Answer

Brian Candler

5/14/2007 5:40:00 AM

0

On Mon, May 14, 2007 at 01:14:54PM +0900, Nasir Khan wrote:
> 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?

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.