Assigning hash to attributes

Hi,

as i mentioned before, i’m pretty new to ruby. So i’m trying to optimize
my code and learn the advantages of this nice language :).

What i’m trying this time is to pass a hash of parameters (passed by a
HTML form i.e.) to an new Object and then automatically assign the
values of this hash to the attributes of that object named like the keys
of that hash.

After a lot of research and even more trail & error i ended up with
this:

class Example

def initialize ( params = {} )
assign_params params unless params.nil?
end

def assign_params params
params.each { | key, value | eval “@#{key} = ‘#{value}’” }
end

end

At least, it works :wink:
Are there any suggestions for smarter or more efficient ways to
accomplish this? Escpecially the usage of eval() to assign variable
variables seems pretty workedaround to me.

Thy for any hint :slight_smile:
D.

On 12/06/07, Daniel L. [email protected] wrote:

def assign_params params
params.each { | key, value | eval “@#{key} = ‘#{value}’” }
end

def assign_parmas(params)
params.each {|key,variable| instance_variable_set(“@{key}”,value)}
end

Farrel

Alle martedì 12 giugno 2007, Daniel L. ha scritto:

After a lot of research and even more trail & error i ended up with this:

end

At least, it works :wink:
Are there any suggestions for smarter or more efficient ways to
accomplish this? Escpecially the usage of eval() to assign variable
variables seems pretty workedaround to me.

Thy for any hint :slight_smile:
D.

You can use instance_variable_set:

def assign_params params
params.each_pair{|k, v| instance_variable_set( “@#{k}”, v)}
end

I hope this helps

Stefano

Farrel L. wrote:

On 12/06/07, Daniel L. [email protected] wrote:

def assign_params params
params.each { | key, value | eval “@#{key} = ‘#{value}’” }
end

def assign_parmas(params)
params.each {|key,variable| instance_variable_set(“@{key}”,value)}
end

Thank you both!

Do you know if instance_variable_set() behaves different or is more
performant in any way than eval()?
Or is it just better readable code?

Daniel L. wrote:

Do you know if instance_variable_set() behaves different or is more
performant in any way than eval()?

Found the answer myself:
with instance_variable_set the variable remains it’s type (which is
actually much better!), the eval() version casts it to string.

Daniel L. [email protected] writes:

What i’m trying this time is to pass a hash of parameters (passed by a
HTML form i.e.) to an new Object and then automatically assign the
values of this hash to the attributes of that object named like the
keys of that hash.

Well, your initial attempt made all my internal security alarms go
“ACK!” - since a web user can pass pretty much arbitrary name/value
pairs in, that code lets a malicious user execute arbitrary ruby code
on your system by passing in a specially crafted name.

How are you intending to use this object? You might find something
like this more useful:

class HashAttrib
def initialize(parms={})
@parameters=parms
end
def method_missing(sym, *args)
return @parameters[sym.to_s] if args.empty?
if sym.to_s =~ /=$/ and args.size == 1 then
return @parameters[sym.to_s[0…-2]] = args[0]
end
return super.method_missing(sym, *args)
end
end

Then, here’s how you can use this class:

irb(main):013:0> a = HashAttrib.new(Hash[*%w{color red string 1 jump
yes}])
=> #<HashAttrib:0xb7c013b0 @parameters={“jump”=>“yes”, “color”=>“red”,
“string”=>“1”}>
irb(main):014:0> a.jump
=> “yes”
irb(main):015:0> a.color
=> “red”
irb(main):016:0> a.string
=> “1”
irb(main):017:0> a.colour
=> nil
irb(main):018:0> a.colour=a.color
=> “red”
irb(main):019:0> a.colour
=> “red”

Now, this doesn’t make the attributes (which is the word you seem to
be using for instance variables) equal to what’s in the hash, really,
it just fakes attributes by turning a.whatever into a hash lookup for
“whatever” in @parameters.

You might want to consider also adding an attr_accessor declaration
for “parameters” to HashAttrib to let you get at the underlying hash.

Alle martedì 12 giugno 2007, Daniel L. ha scritto:

Do you know if instance_variable_set() behaves different or is more
performant in any way than eval()?
Or is it just better readable code?

Here’s a small benchmark to test performances:

require ‘benchmark’

$h = {}
n.times{|i| $h["@v_#{i}"] = i}

class C
def initialize
$h.each_pair{|k, v| instance_variable_set(k,v)}
end
end

class D
def initialize
$h.each_pair{|k, v| eval “@#{k} = ‘#{v}’”}
end
end

Benchmark.bm(‘instance_variable_set:’.size) do |x|
x.report(‘instance_variable_set:’){C.new}
x.report(‘eval:’){D.new}
end

The results are:

  • n = 500:
    user system total real
    instance_variable_set: 0.010000 0.000000 0.010000 ( 0.015151)
    eval: 0.030000 0.010000 0.040000 ( 0.032303)

  • n = 1000:
    user system total real
    instance_variable_set: 0.020000 0.000000 0.020000 ( 0.027947)
    eval: 0.060000 0.000000 0.060000 ( 0.072785)

It’s clear that instance_variable_set is more performant. I’m not sure,
but I
think this happens because with eval, the interpreter also needs to
parse the
string you pass to eval.

Stefano

On 12.06.2007 15:12, Daniel L. wrote:

Thank you both!

Do you know if instance_variable_set() behaves different or is more
performant in any way than eval()?

Eval does not accidentally rhyme on evil. There are serious security
implications of using eval - sometimes the interpreter may actually
prevent evaling a string.

Btw the solution with instance_eval walways works but you might not get
at those values unless you also have attr accessors defined.

Here’s something else that you can do:

irb(main):001:0> require ‘ostruct’
=> true
irb(main):002:0> o=OpenStruct.new(:foo=>1, :bar=>2)
=> #
irb(main):003:0> o.foo
=> 1
irb(main):004:0> o.bar
=> 2

I.e. use OpenStruct. You could as well leave parameters in the Hash…

Kind regards

robert

Daniel M. wrote:

Daniel L. [email protected] writes:

[…]
def assign_params params
params.each { | key, value | eval “@#{key} = ‘#{value}’” }
end
[…]

Well, your initial attempt made all my internal security alarms go
“ACK!” - since a web user can pass pretty much arbitrary name/value
pairs in, that code lets a malicious user execute arbitrary ruby code
on your system by passing in a specially crafted name.

basically i’ll agree.
But using the method instance_variable_set() should also fix this issue,
right? Code may be passed but won’t be interpreted any more, as far as i
see it now.