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
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
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">
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
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
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
-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
--
my religion is very simple. my religion is kindness. -- the dalai lama
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).