Reputation: 131
In the following code, the module is extended which means the method hash_initialized is treated as a class method, or an instance method of the eigen class. This is what we need since hash_initialized is called in the context of the eigen class.
What i don't understand is if the context for the module is the eigen class, then define_method should create a instance method called "initialize" of the eigen class, or in other words, a class method of class Cheese. Don't we need an instance method "initialize" here?
module HashInitialized
def hash_initialized(*fields)
define_method(:initialize) do |h|
missing = fields - h.keys
raise Exception, "Not all fields set: #{missing}" if missing.any?
h.each do |k,v|
instance_variable_set("@#{k}", v) if fields.include?(k)
end
end
end
end
class Cheese
extend HashInitialized
attr_accessor :color, :odor, :taste
hash_initialized :color, :odor, :taste
end
Upvotes: 2
Views: 2393
Reputation: 110685
When you encounter seeming conundrums such as this one, try salting your code with puts self
statements:
module HashInitialized
puts "self when parsed=#{self}"
def hash_initialized(*fields)
puts "self within hash_initialized=#{self}"
define_method(:initialize) do |h|
missing = fields - h.keys
raise ArgumentError, "Not all fields set: #{missing}" if missing.any?
fields.each { |k| instance_variable_set("@#{k}", h[k]) }
end
private :initialize
end
end
#-> self when parsed=HashInitialized
class Cheese
extend HashInitialized
attr_accessor :color, :odor, :taste
hash_initialized :color, :odor, :taste
end
#-> self within hash_initialized=Cheese
As you see, self
is the class Cheese
, not Cheese
's singleton_class. Hence, the receiver for Module#define_method is Cheese
, so the method obligingly creates the instance method initialize
on Cheese
.
Cheese.instance_methods(false)
#=> [:color, :color=, :odor, :odor=, :taste, :taste=]
initialize
is not among the instance methods created on Cheese
because I modified the code slightly to make it a private method:
Cheese.private_instance_methods(false)
#=> [:initialize]
I also slightly altered the code that assigns values to the instance variables, and made the type of exception more specific.
If appropriate, you could change your argument test to:
raise ArgumentError, "Fields #{fields} and keys #{h.keys} don't match" if
(fields-h.keys).any? || (h.keys-fields).any?
You may wish to have initialize
create the assessors:
module HashInitialized
def hash_initialized(*fields)
define_method(:initialize) do |h|
missing = fields - h.keys
raise ArgumentError, "Not all fields set: #{missing}" if missing.any?
fields.each do |k|
instance_variable_set("@#{k}", h[k])
self.class.singleton_class.send(:attr_accessor, k)
end
end
private :initialize
end
end
class Cheese
extend HashInitialized
hash_initialized :color, :odor, :taste
end
Cheese.new :color=>'blue', odor: 'whew!', taste: "wow!"
=> #<Cheese:0x007f97fa07d2a0 @color="blue", @odor="whew!", @taste="wow!">
Upvotes: 1
Reputation: 131
I've made this clear by simplifying the example above and added a few printouts.
class Test
def self.define_something
define_method(:inside_class_method){puts "method defined inside a class method"}
puts "self inside class method "+self.to_s
proc = Proc.new{puts "method defined using send inside class method"}
self.send(:define_method, :define_using_send_inside_class_method, proc)
end
class << self
puts "self inside eigen class "+self.to_s
end
def test
puts "self inside of instance method "+self.to_s
end
puts "self outside of class method "+self.to_s
define_method(:outside_class_method){puts "method defined outside a class method"}
define_something
end
Test.new().inside_class_method
Test.new().outside_class_method
Test.new().test
Test.define_using_send_inside_class_method
This code produce the following outputs:
self inside eigen class #
self outside of class method Test
self inside class method Test
method defined inside a class method
method defined outside a class method
self inside of instance method #
test.rb:26:in <main>': undefined method
define_using_send_inside_class_method' for Test:Class (NoMethodError)
This code:
self.send(:define_method, :define_using_send_inside_class_method, proc)
It is also defining an instance method since it is called on self, and self is referring to the class Test.
If we need to define a class method, send needs to be called on the eigen class like this:
class << self
self.send(:define_method, :define_using_send_inside_class_method, proc)
end
Upvotes: 0
Reputation: 10662
Calling extend
technically puts the module in the lookup chain of the eigen object it is called on, which in this case is the same as the class object. So you are correct that the context for hash_initialized
is the class. Additionally, you are correct that the context for define_method
is the class. However, your final step is incorrect. When define_method
is called in that context it defines an instance method, not a singleton method.
IOW, when you call define_method
in a context, it defines the method in the same place that def
would define it at that context.
Upvotes: 3