Reputation: 12209
There is the following code:
class MyOpenStruct
def initialize(initial_values = {})
@values = initial_values
end
def _singleton_class
class << self
self
end
end
def method_missing(name, *args, &block)
if name[-1] == "="
base_name = name[0..-2].intern
puts "add_method_to_set"
self.class.add_method_to_set(base_name)
@values[base_name] = args[0]
else
puts "add_method_to_get"
self.class.add_method_to_get(base_name)
@values[name]
end
end
def self.add_method_to_get(name)
define_method(name) do |value|
@values[name]
end
end
def self.add_method_to_set(name)
define_method(name) do |value|
@values[name] = value
end
end
end
obj1 = MyOpenStruct.new(name: "Dave")
obj1.address = "1"
obj2 = MyOpenStruct.new(name: "Dave")
obj2.address = "2"
I want to do the following thing: when I execute some method (obj1.address) and it's missing I want to add this method to my MyOpenStruct class. But when I execute my code I get 'missing' two times instead of one. Why? I don't understand. Please explain it to me. Thanks.
Upvotes: 1
Views: 1172
Reputation: 110755
@koffeinfrei identified one problem with you code, but I found a few others. Below I have what I believe to be a corrected version. I have also suggested an alternative way to structure the code. My main advice is to pull out the dynamic creation of instance methods, as that is quite generic. You might even put that in a module with other methods that you could include as needed.
Your code with repairs
class MyOpenStruct
def initialize(initial_values = {})
@values = initial_values
end
def method_missing(name, *args, &block)
puts "in mm, name = #{name}"
if name[-1] == "="
base_name = name[/\w+/]
puts "add_method_to_set: '#{name}'"
self.class.add_method_to_set(base_name)
@values[base_name.to_sym] = args[0]
else
puts "add_method_to_get: '#{name}'"
self.class.add_method_to_get(name)
@values[name.to_sym]
end
end
def self.add_method_to_get(name)
define_method(name.to_sym) do
@values[name.to_sym]
end
end
def self.add_method_to_set(name)
define_method((name+'=').to_sym) do |value|
@values[name.to_sym] = value
end
end
end
Alternative construction
def create_instance_eval(klass, method, &block)
klass.class_eval { define_method(method, &block) }
end
class MyOpenStruct
def initialize(initial_values = {})
@values = initial_values
end
def method_missing(name, *args, &block)
if name[-1] == "="
base_name = name[/\w+/]
method_name = (base_name+'=').to_sym
puts "create method '#{method_name}'"
method = create_instance_eval(self.class, method_name) do |value|
@values[base_name.to_sym] = value
end
send(method, args[0])
else
method_name = name.to_sym
puts "create method '#{method_name}'"
method = create_instance_eval(self.class, method_name) do
@values[method_name]
end
send(method)
end
end
end
Example
MyOpenStruct.instance_methods(false)
#=> [:method_missing]
obj1 = MyOpenStruct.new(name: "Dave")
#=> #<MyOpenStruct:0x00000102805b58 @values={:name=>"Dave"}>
obj1.address = "1"
# create method 'address='
#=> "1"
MyOpenStruct.instance_methods(false)
#=> [:method_missing, :address=]
obj2 = MyOpenStruct.new(name: "Mitzy")
#=> #<MyOpenStruct:0x00000101848878 @values={:name=>"Mitzy"}>
obj2.address = 2
#=> 2
obj2.address
# create method 'address'
# => 2
MyOpenStruct.instance_methods(false)
$#=> [:method_missing, :address=, :address]
obj1.instance_variable_get(:@values)
#=> {:name=>"Dave", :address=>"1"}
obj2.instance_variable_get(:@values)
#=> {:name=>"Mitzy", :address=>2}
Upvotes: 1
Reputation: 2065
The method name for the setter method needs to have the trailing =, so you need to define the method with the name
instead of the base_name
.
self.class.add_method_to_set(name)
Upvotes: 0