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.

Alle martedì 12 giugno 2007, Daniel Liebig ha scritto:

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.

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

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

Farrel

···

On 12/06/07, Daniel Liebig <daniel.liebig@wevin.de> wrote:

   def assign_params params
     params.each { | key, value | eval "@#{key} = '#{value}'" }
   end

Daniel Liebig <daniel.liebig@wevin.de> 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.

···

--
s=%q( Daniel Martin -- martin@snowplow.org
       puts "s=%q(#{s})",s.to_a.last )
       puts "s=%q(#{s})",s.to_a.last

Farrel Lifson 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?

···

On 12/06/07, Daniel Liebig <daniel.liebig@wevin.de> wrote:

Farrel

Daniel Martin wrote:

Daniel Liebig <daniel.liebig@wevin.de> 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.

···

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.

Daniel Liebig wrote:

Farrel Lifson wrote:

[..]

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()?

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.

···

On 12/06/07, Daniel Liebig <daniel.liebig@wevin.de> wrote:

Or is it just better readable code?

Farrel

Alle martedì 12 giugno 2007, Daniel Liebig 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

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)
=> #<OpenStruct 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

···

On 12.06.2007 15:12, Daniel Liebig wrote:

Daniel Liebig wrote:

Farrel Lifson wrote:

On 12/06/07, Daniel Liebig <daniel.liebig@wevin.de> wrote:

[..]

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()?