Reputation: 16202
I would like to dynamically create method_missing on an object, while still allowing it to accept blocks. At the same time, I need to use a closure in the dynamically created method_missing. Here's the problem:
define_singleton_method
to create method_missing
, passing it a block to create the closure.method_missing
can't accept a block.Simple demo, for clarification only (ie, I care about solving the general case, not this specific problem):
close_over_me = "99"
o = Object.new
o.define_singleton_method(:method_missing) do |*all_args|
meth, *args = all_args
"The args passed to #{meth}: #{args}. Special code: #{close_over_me}"
end
p o.hi(42) #=> "The args passed to hi: [42]. Special code: 99"
Now let's say I wanted to pass in a block, which changed the special code somehow, eg, by doubling it. That is, I'd like to write:
p o.hi(42) {|x| 2*x}
Keep in mind I want to achieve all of the following:
method_missing
must create a closure when it's dynamically defined. Hence cannot do def o.method_missing...
Is there any metaprogramming magic that can achieve all these at once?
Upvotes: 0
Views: 124
Reputation: 18762
Here is modified code - you can expect a block
along with all_args
.
Block presence has to be validated before issuing a call to it.
Also note that since close_over_me
is a string, so doubling it by multiplying by 2 results in "9999"
.
close_over_me = "99"
o = Object.new
o.define_singleton_method(:method_missing) do |*all_args, &block|
meth, *args = all_args
"The args passed to #{meth}: #{args}. Special code: #{block ? block.call(close_over_me) : close_over_me}"
end
p o.hi(42) {|x| x * 2}
#=> "The args passed to hi: [42]. Special code: 9999"
p o.hi(42)
#=> "The args passed to hi: [42]. Special code: 99"
Upvotes: 0
Reputation: 369428
With the define_method
family of methods, the parameter list of the block passed to define_method
becomes the parameter list of the method defined by define_method
, so all you need to do to take a block parameter is to add a block parameter to the block's parameter list:
close_over_me = "99"
o = Object.new
o.define_singleton_method(:method_missing) do |meth, *args, &blk|
blk ||= -> x { x }
"The args passed to #{meth}: #{args}. Special code: #{blk.(close_over_me)}"
end
p o.hi(42) {|x| x*2 } #=> "The args passed to hi: [42]. Special code: 9999"
Upvotes: 2
Reputation: 121000
Though it is definitely possible:
close_over_me = "99"
o = Object.new
o.define_singleton_method(:method_missing) do |*all_args, &cb|
meth, *args = all_args
yielded = cb.call(13) if cb
"Args #{meth}: #{args}. Code: #{close_over_me}. Yielded: #{yielded}."
end
p o.hi(42)
#=> "The args passed to hi: [42]. Special code: 99. Block yielded:."
p o.hi(42) { |x| 2 * x }
#=> "The args passed to hi: [42]. Special code: 99, Block yielded: 26."
I can not get how it is supposed to work with any amount of arguments, since inside the singleton we should call it explicitly.
It seems to me you are misdesigning things.
Upvotes: 0