Dynamically reference instance vars

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

···

--
Posted via http://www.ruby-forum.com/.

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

···

On 10/29/2010 09:14 PM, Greg Willits wrote:

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

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.

···

--
Posted via http://www.ruby-forum.com/.

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:

···

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

--
Posted via http://www.ruby-forum.com/\.

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)

···

On Fri, Oct 29, 2010 at 10:31 PM, Jeremy Bopp <jeremy@bopp.net> wrote:

On 10/29/2010 09:14 PM, Greg Willits wrote:
> 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

Crafting setters is pretty simple, though:

class A
attr_writer :a
end

a = A.new
a.a = 3

Jesus.

···

On Sat, Oct 30, 2010 at 9:27 PM, Greg Willits <lists@gregwillits.ws> wrote:

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.

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.

···

On Sat, Oct 30, 2010 at 1:27 PM, Greg Willits <lists@gregwillits.ws> wrote:

--
Tony Arcieri
Medioh! A Kudelski Brand

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

···

On Oct 30, 2010, at 12:27 , Greg Willits wrote:

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

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

Excellent point! Thanks!

-Jeremy

···

On 10/30/2010 07:28 AM, Andrew Wagner wrote:

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)

Ryan Davis wrote in post #958245:

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

myobject.send(iname) = 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= 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! :slight_smile:
Sorry, no prizes :frowning:

Thanks.

-- gw

···

On Oct 30, 2010, at 12:27 , Greg Willits wrote:

--
Posted via http://www.ruby-forum.com/\.

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

···

On 30.10.2010 22:11, Greg Willits wrote:

Ryan Davis wrote in post #958245:

On Oct 30, 2010, at 12:27 , Greg Willits wrote:

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

  myobject.send(iname) = 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= 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! :slight_smile:
Sorry, no prizes :frowning:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/