Reputation: 1339
I need a way to send a message from stuff method (via metaprogramming) which executes my_method on the object scope. There's a good way of doing that without inserting more code on Dummy class?
class Dummy
stuff :final_value do
value :my_method
end
def my_method
10.5
end
def final_value
0
end
end
Expected return:
dummy = Dummy.new
dummy.final_value
=> 10.5
The idea is to take the methods on value args and find a way to map those values from the object scope, which is this example is 10.5.
Posting a temporary solution down there.
Upvotes: 0
Views: 361
Reputation: 1339
A temporary way of dealing with it was executing code on define_method scope, which is the object scope itself. The solution is not fully tested but the idea is pretty much that:
def stuff(method_name, &block)
subject = Stuff.new(block)
# Defining the final_value method and executing code on object scope.
define_method method_name do
# making the sum on object scope (demand is a list of *value* args,
# which is a list of methods to execute on object scope)
#
# The logic here is to sum them to return as final_value result
subject.total = subject.demand.map do |method|
self.send(method)
end.reduce(:+)
subject.total
end
subject.evaluate!
end
class Stuff
attr_accessor :demand, :total
def initialize(stuff_block)
@stuff_block = stuff_block
@total = 0
end
# Brings all stuff block into Stuff class
def evaluate!
@stuff_block.call
end
# The main problem was this.
# The solution was mapping those demanded methods into a demand local variable,
# then accessing this variable on define_method scope inside *stuff* method, which is the
# object scope itself. Bahn!
def value(*args)
@demand = args
end
end
Upvotes: 0
Reputation: 110665
This should do it.
class Dummy
def stuff
self.class.send(:define_method, :final_value) do
my_method
end
end
def my_method
10.5
end
def final_value
0
end
end
dummy = Dummy.new
dummy.stuff
dummy.final_value #=> 10.5
The purpose of the method stuff
is to change the method final_value
to:
def final_value
my_method
end
so we need to do, dynamically, the equivalent of:
class Dummy
def final_value
my_method
end
end
Here, when final_value
is created, self
is Dummy
. We therefore need to instruct Dummy
to define the method final_value
as above, thereby replacing its earlier definition. Fortunately, there's a method that does just that: Module#define_method.
To execute :define_method
, we need only send it, together with its argument :final_value
and a block that is to be the body of :final_value
, to Dummy
, using the method Object#send:
def stuff
Dummy.send(self.class.send(:define_method, :final_value) do
my_method
end
end
That works fine, but suppose we were to rename the class to, say, Smartie
? We'd have to remember to change Dummy
to Smartie
in the method stuff
. It's better to replace Dummy
with self.class
. By the way, this is one of the few instances where you do need to include self.
, as class
alone is interpreted as the keyword class
, as in class Dummy
.
Edit: I may have misunderstood the question. (See comments below.) The following may be what you want:
class Dummy
def self.stuff(method, &block)
send(:define_method, method, &block)
end
def my_method
10.5
end
def final_value
0
end
stuff :final_value do
my_method
end
end
Dummy.new.final_value #=> 10.5
Note, because Ruby parses the lines sequentially, building the class as it goes,
stuff :final_value do
my_method
end
must appear after the other methods.
Upvotes: 1