[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

assigning hash to attributes

Daniel Liebig

6/12/2007 11:58:00 AM

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 ;)
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 :)
D.

8 Answers

Stefano Crocco

6/12/2007 12:11:00 PM

0

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 ;)
> 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 :)
> 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 Lifson

6/12/2007 12:12:00 PM

0

On 12/06/07, Daniel Liebig <daniel.liebig@wevin.de> 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

Daniel Liebig

6/12/2007 12:26:00 PM

0

Farrel Lifson wrote:
> On 12/06/07, Daniel Liebig <daniel.liebig@wevin.de> 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?

>
> Farrel
>

Daniel Liebig

6/12/2007 1:13:00 PM

0

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

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.

> Or is it just better readable code?
>
>>
>> Farrel
>>

Daniel Martin

6/12/2007 1:14:00 PM

0

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

Stefano Crocco

6/12/2007 1:25:00 PM

0

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

Daniel Liebig

6/12/2007 1:55:00 PM

0

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.
>

Robert Klemme

6/12/2007 5:41:00 PM

0

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

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