In the Ruby programming language, I am creating a class with a class-level macro, as follows:
class Timer
def self.add_time
def time
STDERR.puts Time.now.strftime("%H:%M:%S")
end
end
end
The class method add_time
, when executed, will generate a time
method.
Now, I can execute that class-level macro in another class Example
as follows:
class Example < Timer
add_time
end
When I now call time
on an instance of class Example
, the time
method is present there, as I intended:
ex = Example.new
ex.time
and prints the current time: 23:18:38
.
But now I would like to put the add_time
macro in a module and still have the same overall effect. I tried with an include
like this:
module Timer
def self.add_time
def time
STDERR.puts Time.now.strftime("%H:%M:%S")
end
end
end
class Example
include Timer
add_time
end
ex = Example.new
ex.time
but then I receive an error that the method add_time
is not defined on the class Example
: NameError: undefined local variable or method ‘add_time’ for Example:Class
. So then I tried with an extend
instead like this:
class Example
extend Timer
add_time
end
but it gives me a similar error.
So the question is: How can I get the same effect as in my original example where the Timer
was defined as a class, but using a module instead?
As @CarySwoveland pointed out, the method def self.add_time
in the module Timer
gets disregarded upon inclusion or extension in a class. Only the module's instance methods are added to the class as instance method of the class (in case of inclusion) or as class methods of the class (in case of extends).
module Timer
def add_time # INSTANCE METHOD !
def time
STDERR.puts Time.now.strftime("%H:%M:%S")
end
end
end
So the first step of the solution is to declare the method def add_time
as an instance method of the module. Next, we extend the class Example
with that module, so that the module's instance method gets added as a class method in the class Example
, and we call the add_time
method:
class Example
extend Timer # EXTEND INSTEAD OF INCLUDE
add_time
end
However, this doesn't quite work as desired yet as the time
method has now been generated as a class method: Example.time
prints the current time 01:30:37
, but an instance ex
of class Example
does not understand the method time
.
The solution is thus to generate the method def time
as an instance method rather than as a class method. This can be done using class_eval
, which leads us to the following working solution:
module Timer
def add_time # INSTANCE METHOD !
self.class_eval do # USE class_eval TO DEFINE AN INSTANCE METHOD !
def time
STDERR.puts Time.now.strftime("%H:%M:%S")
end
end
end
end
class Example
extend Timer # USE EXTEND TO ADD add_time AS A CLASS METHOD
add_time
end
ex = Example.new
ex.time