Dynamically assigning instance variables (redux)

I need a modern answer to Daniel Berger's 2002 post,

  http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/39534

  Is there a way to dynamically assign instance variables?

  class SomeClass
    def initialize(hash)
      hash.each do |key,val|
        @#{key} = val # Doesn't work
      end
    end
  end

Actually, I'd like the assignment and make them attr_readers too.

Nearly meta-programming clueless,

···

--
Bil Kleb
http://fun3d.larc.nasa.gov

Here's one way:
http://rubyquiz.com/quiz67.html

If you just want to set instance variables, you can do:
def initialize(has)
  hash.each do |key,val|
    instance_variable_set "@#{key}", val
  end
end

···

On 10/16/06, Bil Kleb <Bil.Kleb@nasa.gov> wrote:

I need a modern answer to Daniel Berger's 2002 post,

  http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/39534

  Is there a way to dynamically assign instance variables?

  class SomeClass
    def initialize(hash)
      hash.each do |key,val|
        @#{key} = val # Doesn't work
      end
    end
  end

Is there a way to dynamically assign instance variables?

class SomeClass
   def initialize(hash)
     hash.each do |key,val|
       @#{key} = val # Doesn't work
     end
   end
end

Replace:

@#{key} = val

with:

self.instance_variable_set(:@"#{key}", val)

Actually, I'd like the assignment and make them attr_readers too.

Nearly meta-programming clueless,

Not sure on this one, havn't gone too deep into metaprogramming in ruby.

···

On 06-10-16, at 22:35, Bil Kleb wrote:

--
Jeremy Tregunna
jtregunna@blurgle.ca

Bil Kleb wrote:

  Is there a way to dynamically assign instance variables?

[...snip...]

Actually, I'd like the assignment and make them attr_readers too.

class Foo
  def initialize( hash={} )
    hash.each{ |key,value|
      var_name = "@#{key}"
      instance_variable_set( var_name, value )
      singleton_class = class << self; self; end
      singleton_class.class_eval{
        define_method( key ){
          instance_variable_get( var_name )
        }
        define_method( "#{key}=" ){ |new_value|
          instance_variable_set( var_name, new_value )
        }
      }
    }
  end
end

f = Foo.new :name=>'whee', :age=>12
p f, f.age
#=> #<Foo:0x32b900 @age=12, @name="whee">
#=> 12
f.age=32
p f
#=> #<Foo:0x32b900 @age=32, @name="whee">

Phrogz wrote:

class Foo
  def initialize( hash={} )
    hash.each{ |key,value|
      var_name = "@#{key}"
      instance_variable_set( var_name, value )
      singleton_class = class << self; self; end
      singleton_class.class_eval{
        define_method( key ){
          instance_variable_get( var_name )
        }
        define_method( "#{key}=" ){ |new_value|
          instance_variable_set( var_name, new_value )
        }
      }
    }
  end
end

Note that the above defines distinct readers/writers for each instance:

  f1 = Foo.new :name=>'whee', :age=>12
  f2 = Foo.new :chicken=>'gorkbo'
  p f1, f2
  #=> #<Foo:0x32b900 @age=12, @name="whee">
  #=> #<Foo:0x32b360 @chicken="gorkbo">

  p f1.name
  #=> "whee"

  p f2.name
  #=> NoMethodError: undefined method `name' for #<Foo:0x32b34c
@chicken="gorkbo">
  #=> at top level in tmp.rb at line 26

You can also open up the main class (instead of the singleton class) to
define the methods shared by all instances, or store the hash as an
instance variable and use method_missing to dynamically catch and
report values as appropriate.

Here's an example of that for Hash objects in general:

class Hash
  # Causes *all* Hash objects to behave similar to Object literals in
JS,
  # with their keys accessible via dot notation.
  # foo = { 'name'=>'Gavin', 'age'=>33 }
  # foo.weight = 180
  # p foo.name, foo.weight
  # #=> "Gavin"
  # #=> 180

···

#
  # p foo.zzzz
  # #=> NoMethodError: undefined method `zzzz' for
  # #=> {"name"=>"Gavin", "weight"=>180, "age"=>33}:Hash
  alias_method :__mMm__, :method_missing
  def method_missing(meth,*args)
    if /=$/ =~ (meth=meth.id2name)
      # Name ends with an equals sign; shove value to the hash
      self[ meth[0...-1] ] = (args.length<2 ? args[0] : args)
    else
      # Retrieve the value
      if self.key?( meth )
        self[ meth ]
      else
        # Let the real method_missing handle/pass on up
        __mMm__( meth.intern, *args )
      end
    end
  end
end

TIMTOWTDI.

Wow, that was way cool. I think we can make it shorter (esp since he
only wants attr_readers) and faster (no point generating the singleton
class every time thru the loop):

class Foo
  def initialize( hash={} )
    singleton_class = class << self; self; end
    hash.each do |key,value|
      instance_variable_set( "@" + key, value )
      singleton_class.class_eval { attr_reader key }
    end
  end
end

f = Foo.new "name"=>'whee', "age"=>12
p f.age, f.name # => works
f.name = "no, it's a reader" # => error, as desired

But pls note that I'm just imitating; the self-returning singleton class
was totally beyond me! m.

···

Phrogz <gavin@refinery.com> wrote:

Bil Kleb wrote:
> Is there a way to dynamically assign instance variables?
[...snip...]
> Actually, I'd like the assignment and make them attr_readers too.

class Foo
  def initialize( hash={} )
    hash.each{ |key,value|
      var_name = "@#{key}"
      instance_variable_set( var_name, value )
      singleton_class = class << self; self; end
      singleton_class.class_eval{
        define_method( key ){
          instance_variable_get( var_name )
        }
        define_method( "#{key}=" ){ |new_value|
          instance_variable_set( var_name, new_value )
        }
      }
    }
  end
end

--
matt neuburg, phd = matt@tidbits.com, http://www.tidbits.com/matt/
Tiger - http://www.takecontrolbooks.com/tiger-customizing.html
AppleScript - http://www.amazon.com/gp/product/0596102119
Read TidBITS! It's free and smart. http://www.tidbits.com

Bil Kleb wrote:

  Is there a way to dynamically assign instance variables?

[...snip...]

Actually, I'd like the assignment and make them attr_readers too.

class Foo
  def initialize( hash={} )
    hash.each{ |key,value|
      var_name = "@#{key}"
      instance_variable_set( var_name, value )
      singleton_class = class << self; self; end
      singleton_class.class_eval{
        define_method( key ){
          instance_variable_get( var_name )
        }
        define_method( "#{key}=" ){ |new_value|
          instance_variable_set( var_name, new_value )
        }
      }
    }
  end
end

I find this much too complex with no real need for using singleton_classes (ever, really). Try:

class Foo
   def initialize( hash={} )
     hash.each do |ivar, val|
       self.class.send :attr_accessor, ivar unless respond_to? ivar
       send "#{ivar}=", val
     end
   end
end

The primary benefit of writing code like this is that you can know what it does in half a glance with no extra thinking. I should also point out that calling a define_method method is about 2x slower than an attr_* method. (not a big deal in this case but take a look at rails' primary_key and think about that impact)

···

On Oct 16, 2006, at 8:45 PM, Phrogz wrote:

f = Foo.new :name=>'whee', :age=>12
p f, f.age
#=> #<Foo:0x32b900 @age=12, @name="whee">
#=> 12
f.age=32
p f
#=> #<Foo:0x32b900 @age=32, @name="whee">

Ryan Davis wrote:

class Foo
   def initialize( hash={} )
     hash.each do |ivar, val|
       self.class.send :attr_accessor, ivar unless respond_to? ivar
       send "#{ivar}=", val
     end
   end
end

This is distinctly different, however, in that it adds the accessor
methods to the Foo class, not the instances. I have no idea what Mr.
Kleb wanted, but given that he's adding accessors based on hashes
passed to each instance, my guess was that he would want a unique set
per instance.

  f1 = Foo.new :name=>'whee'
  f2 = Foo.new :age=>12
  p f1.age
  #=> nil

Is the above result desirable, Bill?

matt neuburg wrote:

Wow, that was way cool. I think we can make it shorter (esp since he
only wants attr_readers) and faster (no point generating the singleton
class every time thru the loop):

class Foo
  def initialize( hash={} )
    singleton_class = class << self; self; end
    hash.each do |key,value|
      instance_variable_set( "@" + key, value )
      singleton_class.class_eval { attr_reader key }
    end
  end
end

/slaps forehead
Of course, very nice :slight_smile:

or, with attributes.rb:

     harp:~ > cat a.rb
     require 'rubygems'
     require 'attributes'

     class Foo
       def initialize(h={}) h.each{|k,v| attributes k => v} end
     end

     foo = Foo.new 'a' => 4, :k => 2
     p foo.a
     p foo.k

     harp:~ > ruby a.rb
     4
     2

:wink:

-a

···

On Tue, 17 Oct 2006, Phrogz wrote:

matt neuburg wrote:

Wow, that was way cool. I think we can make it shorter (esp since he
only wants attr_readers) and faster (no point generating the singleton
class every time thru the loop):

class Foo
  def initialize( hash={} )
    singleton_class = class << self; self; end
    hash.each do |key,value|
      instance_variable_set( "@" + key, value )
      singleton_class.class_eval { attr_reader key }
    end
  end
end

/slaps forehead
Of course, very nice :slight_smile:

--
my religion is very simple. my religion is kindness. -- the dalai lama

Phrogz wrote:

I have no idea what Mr.
Kleb wanted, but given that he's adding accessors based on hashes
passed to each instance, my guess was that he would want a unique set
per instance.

  f1 = Foo.new :name=>'whee'
  f2 = Foo.new :age=>12
  p f1.age #=> nil

Is the above result desirable, Bill?

I don't know about Mr. Kleb and Bill, but as far as Bil's
concerned, that's precisely what I'm after (although I
didn't understand this subtlety when I originally asked).

Regards,

···

--
Bil Kleb, PhD, MBA
http://fun3d.larc.nasa.gov