Dynamically reference instance vars

G

Greg Willits

If I need to dynamically reference instance vars, is this the only way
to do it (var set example)?

my_object.send:)instance_variable_set, "@#{iname}", ivalue)

I expected something more elegant, but this is the only way I can get it
to work. No biggie, just curious.

More complete example below.

-- gw


class Shape
attr_accessor :size, :fill_color, :line_color, :line_width
def initialize
@size = ""
@fill_color = ""
@line_color = ""
@line_width = ""
end
end

my_shape = Shape.new

shape_details = {
:size => 'small',
:fill_color => 'red',
:line_color => 'black',
:line_width => '2'}

shape_details.each do |iname, ivalue|
my_shape.send:)instance_variable_set, "@#{iname}", ivalue)
end
 
J

Jeremy Bopp

If I need to dynamically reference instance vars, is this the only way
to do it (var set example)?

my_object.send:)instance_variable_set, "@#{iname}", ivalue)

I expected something more elegant, but this is the only way I can get it
to work. No biggie, just curious.

More complete example below.

-- gw


class Shape
attr_accessor :size, :fill_color, :line_color, :line_width
def initialize
@size = ""
@fill_color = ""
@line_color = ""
@line_width = ""
end
end

my_shape = Shape.new

shape_details = {
:size => 'small',
:fill_color => 'red',
:line_color => 'black',
:line_width => '2'}

shape_details.each do |iname, ivalue|
my_shape.send:)instance_variable_set, "@#{iname}", ivalue)
end

How about backing the accessors with a hash that also has an accessor?
That way you could merge in a hash of settings or set/get them neatly by
name:

class Shape
attr_reader :details

def initialize
@details = {
:size => "",
:fill_color => "",
:line_color => "",
:line_width => ""
}
end

def size
@details[:size]
end

def size=(size)
@details[:size] = size
end

def fill_color
@details[:fill_color]
end

def fill_color=(fill_color)
@details[:fill_color] = fill_color
end

def line_color
@details[:line_color]
end

def line_color=(line_color)
@details[:line_color] = line_color
end

def line_width
@details[:line_width]
end

def line_width=(line_width)
@details[:line_width] = line_width
end
end

my_shape = Shape.new

shape_details = {
:size => 'small',
:fill_color => 'red',
:line_color => 'black',
:line_width => '2'
}

my_shape.details.merge!(shape_details)


-Jeremy
 
A

Andrew Wagner

[Note: parts of this message were removed to make it a legal post.]

C'mon, this is ruby! Revel in it!

class Shape
attr_reader :details
def self.keys
[:size, :fill_color, :line_color, :line_color]
end

def initialize
@details = {}
self.class.keys.each {|key| @details[key] = "" }
end

keys.each do |key|
define_method key do
@details[key]
end

define_method "#{key}=" do |input|
@details[key] = input
end
end
end

my_shape = Shape.new

shape_details = {
:size => 'small',
:fill_color => 'red',
:line_color => 'black',
:line_width => '2'
}

my_shape.details.merge!(shape_details)


If I need to dynamically reference instance vars, is this the only way
to do it (var set example)?

my_object.send:)instance_variable_set, "@#{iname}", ivalue)

I expected something more elegant, but this is the only way I can get it
to work. No biggie, just curious.

More complete example below.

-- gw


class Shape
attr_accessor :size, :fill_color, :line_color, :line_width
def initialize
@size = ""
@fill_color = ""
@line_color = ""
@line_width = ""
end
end

my_shape = Shape.new

shape_details = {
:size => 'small',
:fill_color => 'red',
:line_color => 'black',
:line_width => '2'}

shape_details.each do |iname, ivalue|
my_shape.send:)instance_variable_set, "@#{iname}", ivalue)
end

How about backing the accessors with a hash that also has an accessor?
That way you could merge in a hash of settings or set/get them neatly by
name:

class Shape
attr_reader :details

def initialize
@details = {
:size => "",
:fill_color => "",
:line_color => "",
:line_width => ""
}
end

def size
@details[:size]
end

def size=(size)
@details[:size] = size
end

def fill_color
@details[:fill_color]
end

def fill_color=(fill_color)
@details[:fill_color] = fill_color
end

def line_color
@details[:line_color]
end

def line_color=(line_color)
@details[:line_color] = line_color
end

def line_width
@details[:line_width]
end

def line_width=(line_width)
@details[:line_width] = line_width
end
end

my_shape = Shape.new

shape_details = {
:size => 'small',
:fill_color => 'red',
:line_color => 'black',
:line_width => '2'
}

my_shape.details.merge!(shape_details)


-Jeremy
 
J

Jeremy Bopp

C'mon, this is ruby! Revel in it!

class Shape
attr_reader :details
def self.keys
[:size, :fill_color, :line_color, :line_color]
end

def initialize
@details = {}
self.class.keys.each {|key| @details[key] = "" }
end

keys.each do |key|
define_method key do
@details[key]
end

define_method "#{key}=" do |input|
@details[key] = input
end
end
end

my_shape = Shape.new

shape_details = {
:size => 'small',
:fill_color => 'red',
:line_color => 'black',
:line_width => '2'
}

my_shape.details.merge!(shape_details)

Excellent point! Thanks!

-Jeremy
 
G

Greg Willits

Appreciate the effort, but the point has been lost. Don't focus on the
hash or mass assignment, that was just a device to create an example.

The question is whether there's a shorter way to do this:

my_object.send:)instance_variable_set, "@#{iname}", ivalue)

In another language I used, I could simply do the equivalent of

myobject.iname = ivalue

that's because the syntax was like this
#myobject->#iname = ivalue

Where # denotes a local var, so it was obvious to the parser that #iname
was not a method name, whereas with Ruby it's not obvious.

I was hoping in Ruby that at least this was possible:

myobject.send(iname) = ivalue
--or--
myobject.send(iname, ivalue)

but that doesn't work. I could maybe force it to work by manually
creating setter methods, but that's not an elegant solution either, I'd
rather have the occassional use of instance_variable_set than craft
setters.

Anyway, not a biggie, just a curiosity.
 
J

Jesús Gabriel y Galán

Appreciate the effort, but the point has been lost. Don't focus on the
hash or mass assignment, that was just a device to create an example.

The question is whether there's a shorter way to do this:

=A0my_object.send:)instance_variable_set, "@#{iname}", ivalue)

In another language I used, I could simply do the equivalent of

=A0myobject.iname =3D ivalue

that's because the syntax was like this
=A0#myobject->#iname =3D ivalue

Where # denotes a local var, so it was obvious to the parser that #iname
was not a method name, whereas with Ruby it's not obvious.

I was hoping in Ruby that at least this was possible:

=A0myobject.send(iname) =3D ivalue
=A0--or--
=A0myobject.send(iname, ivalue)

but that doesn't work. I could maybe force it to work by manually
creating setter methods, but that's not an elegant solution either, I'd
rather have the occassional use of instance_variable_set than craft
setters.

Anyway, not a biggie, just a curiosity.

Crafting setters is pretty simple, though:

class A
attr_writer :a
end

a =3D A.new
a.a =3D 3

Jesus.
 
T

Tony Arcieri

[Note: parts of this message were removed to make it a legal post.]

The question is whether there's a shorter way to do this:

my_object.send:)instance_variable_set, "@#{iname}", ivalue)

In another language I used, I could simply do the equivalent of

myobject.iname = ivalue

This breaks the encapsulation that objects are supposed to provide for their
hidden state. I have to say I *like* the fact that
instance_variable_get/instance_variable_set are long and unwieldy. By using
them you hopefully remind yourself that you're doing something naughty by
touching an object's private parts.

I was hoping in Ruby that at least this was possible:

myobject.send(iname) = ivalue
--or--
myobject.send(iname, ivalue)

but that doesn't work. I could maybe force it to work by manually
creating setter methods, but that's not an elegant solution either, I'd
rather have the occassional use of instance_variable_set than craft
setters.


It's Ruby, a lot is possible! You could add some method_missing magic so:

myobj.iv_foobar = 42

thunks to:

instance_variable_set:)@foobar, 42)

I would strongly recommend against that sort of thing though.
 
R

Ryan Davis

I was hoping in Ruby that at least this was possible:
=20
myobject.send(iname) =3D ivalue
--or--
myobject.send(iname, ivalue)

You're confusing methods and variables. They're not the same, but you're =
on the right track with the code above, but you'd be calling the getter, =
not the setter. Check it:

class X
attr_accessor :x # creates x and x=3D methods
end

o =3D X.new
o.send("x=3D", 42)
p x
 
J

John Mair

You realize that instance_variable_set isn't private right?

So you can simply go:

my_object.instance_variable_set("@#{iname}", ivalue)

Greg Willits wrote in post #958159:
 
G

Greg Willits

Ryan Davis wrote in post #958245:
You're confusing methods and variables. They're not the same, but you're
on the right track with the code above, but you'd be calling the getter,
not the setter. Check it:

class X
attr_accessor :x # creates x and x= methods
end

o = X.new
o.send("x=", 42)
p x

I wasn't confusing them, I was hoping Ruby's send would be flexible
enough to work with both of them (in conjunction with the accessors
being defined). What I didn't think of in this context was Ruby's x=
being considered the setter method and not just x, which I knew, and
should have recognized -- so yep, that was the ticket. You win! :)
Sorry, no prizes :-(

Thanks.

-- gw
 
R

Robert Klemme

Ryan Davis wrote in post #958245:

I wasn't confusing them, I was hoping Ruby's send would be flexible
enough to work with both of them (in conjunction with the accessors
being defined). What I didn't think of in this context was Ruby's x=
being considered the setter method and not just x, which I knew, and
should have recognized -- so yep, that was the ticket. You win! :)
Sorry, no prizes :-(

Here are two other approaches using widely underused class Struct and
that differ in the way they deal with values not present in the Hash:

Shape = Struct.new :size, :fill_color, :line_color, :line_width do
def self.from_hash_1(h)
sh = new
h.each {|k,v| sh[k] = v}
sh
end

def self.from_hash_2(h)
sh = new
members.each {|m| sh[m] = h[m]}
sh
end
end

irb(main):015:0* s1 = Shape.from_hash_1(
irb(main):016:1* :size => 'small',
irb(main):017:1* :fill_color => 'red',
irb(main):018:1* :line_color => 'black',
irb(main):019:1* :line_width => '2'
irb(main):020:1> )
=> #<struct Shape size="small", fill_color="red", line_color="black",
line_width="2">
irb(main):021:0> s2 = Shape.from_hash_2(
irb(main):022:1* :size => 'small',
irb(main):023:1* :fill_color => 'red',
irb(main):024:1* :line_color => 'black',
irb(main):025:1* :line_width => '2'
irb(main):026:1> )
=> #<struct Shape size="small", fill_color="red", line_color="black",
line_width="2">

If you like that better you can as well define

def Shape(h)
sh = Shape.new
# logic from above
sh
end

Then you can do

s1 = Shape(
:size => 'small',
:fill_color => 'red',
:line_color => 'black',
:line_width => '2'
)

Kind regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top