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