Brian Candler
5/14/2007 5:40:00 AM
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.