How to define a class-level macro in a module in Ruby? - ruby

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_timemethod:
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

Related

Why can some classes and/or methods be called without instances of their parent class?

I'm near the finish of the Ruby track in Code Academy, and I'm curious about a peculiar thing: I was under the impression that a class is a repository of constants, methods, etc... and that in order to access most of them, you would first need to create an instance of that class or in some cases the methods of themselves can be invoked (as in they are all technically part of the global object). And then I saw something like this:
#Worked
Time.now
I understood as this as the method [now] of instance of class [Time] being invoked. I then tried to invoke the method on its own:
#Failed
now
and that failed, and I assumed that while a method can be created in the general scope [as part of the global object], if it relies on initialized variables of "parent" class, it cannot be called on its own, because it would not know which object to search for those initialized variables. Following that I created a test class:
class Clock
def initialize
#hours = 1
#minutes = 30
end
def showTime
puts "The time is: #{#hours}:#{#minutes}"
end
end
#this worked
watch = Clock.new
watch.showTime
#this failed
showTime
I then just created a basic method (assuming it's in the global level)
def mymethod
puts "The mighty METHOD!"
end
#Works
mymethod
and calling this method the way I did, without referencing the global object worked. So... the questions I have are as follows:
How can [Time.now] be called in this fashion? Shouldn't there be an instance of Time first created?
Why can't I call the method [now] on its own? Am I right that it relies on resources that it cannot find when called this way?
Why could I not call the method showTime on its own? But if I define any method on the "global" level I can access it without referencing the global object
First of all, your intuition is correct.
Every methods must be an instance method of some receiver.
Global methods are defined as private instance methods on Object class and hence seem to be globally available. Why? From any context Object is always in the class hierarchy of self and hence private methods on Object are always callable without receiver.
def fuuuuuuuuuuun
end
Object.private_methods.include?(:fuuuuuuuuuuun)
# => true
Class methods are defined as instance methods on the "singleton class" of their class instance. Every object in Ruby has two classes, a "singleton class" with instance methods just for that one single object and a "normal class" with method for all objects of that class. Classes are no different, they are objects of the Class class and may have singleton methods.
class A
class << self # the singleton class
def example
end
end
end
A.singleton_class.instance_methods.include?(:example)
# => true
Alternative ways of defining class methods are
class A
def self.example
end
end
# or
def A.example
end
Fun fact, you can define singleton methods on any object (not just on class objects) using the same syntax def (receiver).(method name) as follows
str = "hello"
def str.square_size
size * size
end
str.square_size
# => 25
"any other string".square_size
# => raises NoMethodError
Some programming language history — Singleton classes are taken from the Smalltalk language where they are called "metaclasses". Basically all object-oriented features in Ruby (as well as the functional-style enumerators on Enumerable) are taken from the Smalltalk language. Smalltalk was an early class-based object-oriented language created in the 70ies. It was also the language that invented graphical user interfaces like overlapping windows and menus et cetera. If you love Ruby maybe also take a look at Smalltalk, you might fall in love yet again.
This is known as a class method. If CodeAcademy didn't cover it, that's a shame. Here's some examples:
# basic way
class Foo
def self.bar; :ok; end
end
Foo.bar # => :ok
# alternate syntax
class Foo
class << self
def bar; :ok; end
end
end
# alternate syntax, if Foo class already exists
def Foo.bar; :ok; end
# alternate approach if Foo class already exists
Foo.class_exec do
def bar; :ok; end
end
# to define a class method on an anonymous 'class' for a single instance
# you won't need to use this often
Foo.new.singleton_class.class_exec do
def bar; :ok; end
end
# to define a class method on an instance's actual class
Foo.new.class.class_exec do
def bar; :ok; end
end
Another way to get class methods is to extend a module.
module FooMethods
def bar; :ok; end
end
module Foo
extend FooMethods
end
Foo.bar # => :ok
Note that with Modules, the methods are always defined as instance methods. This way they can be either extended into class scope or included into instance scope. Modules can also have class methods, using the exact same syntax / examples as shown above with classes. However there's not such as easy to load a module's class methods via include or extend.
How can [Time.now] be called in this fashion? Shouldn't there be an
instance of Time first created?
The Time.now method is a class method, not an instance method and therefore can be called directly on the Time class rather than an instance of it Time.new
Class methods are defined on the class themselves using the self keyword:
class Time
def self.now
# code
end
end
Time.now # works
Why can't I call the method [now] on its own? Am I right that it
relies on resources that it cannot find when called this way?
When you call a method "on its own" you're actually implicitly calling it on self:
self.now
The above is the same as just doing:
now
Why could I not call the method showTime on its own? But if I define
any method on the "global" level I can access it without referencing
the global object
You defined the showTime method on a specific class so you have to send that method to that class. When you define a method in the "global" scope you're implicitly defining it on self and the subsequent call to mymethod is actually self.mymethod so it will work.
Time.now is a class method.
To define a class method, you need to define the method with self. : def self.method_name
class Clock
#hours = 1
#minutes = 30
def self.showTime
puts "The time is: #{#hours}:#{#minutes}"
end
end
Clock.showTime
#=> The time is: 1:30
If you want to call now on its own, you can do so inside Time class :
class Time
puts now
#=> 2017-01-19 22:17:29 +0100
end

in ruby, is it possible to redefine the execution context of an added method?

For example
class Foo
def bar
end
end
In that code, bar would only be available within any instance of the class.
Is it possible to change the execution context of the method to the Eigenclass
without changing how the method itself is defined so that the method is now available as a singleton without ever needing to call self.new?
Preferably I would like to do it any of the code that doe this to code that is added in via a class that Foo could inherit from.
At the moment what I'm doing amounts to:
class Test
def method_added method
self.define_singleton_method method do
self.new.send method
end
end
end
and for what I need this doesn't work as I'm changing the execution context by calling new.
You can simply do:
class Test
def self.method_added method
module_function method
end
end
class A < Test
def foo
:hello
end
end
A.foo #=> :hello
I worked out how to do it just now >_<.
Here's the code:
class Test
def method_added method
m = self.new.method(method) #get method object
self.define_singleton_method(method) do #create method with same name within the singleton class
m.call #call the block which will now run the code of the added method within the context of the Eigenclass/Singleton
end
end
end
So what it does is it grabs a method object form an instance and then calls that method as a block within the context of the class.
So the first code example becomes:
class Foo < Test
def bar
end
end
and the method bar can now be accessed as
Foo.bar rather then Foo.new.bar which means no instance creation; besides the time it does within method added, but that's fine as it's the only way to get the method object as far as I'm aware.
Which is why it's probably best to create an instance only the once when the class is inherited (within def self.inherited), store it within the class and then just access that instead of calling self.new.

Call Class Methods to Initialize Static Class

I'm trying to create a singleton class that requires some sophisticated initialization. I've boiled my problem down to this test case:
class Dumb
attr_accessor :mything
#my_thing = 1 # this works
self.init_some_stuff # this gives undefined method
class << self
def init_some_stuff
#my_thing = 2
end
def spill_it
puts "My Thing: #{#my_thing}"
end
end
end
I can initialize simple variables, but want to call class methods to do it, and I get "undefined method". Since I intend it to be used as a singleton, a constructor would not get called. What am I missing?
A method is executed whenever it is met.
self.init_some_stuff
is placed before the definition of it. That is the problem. Place it after the definition.

Define instance method of a class after class already defined in ruby

I have some problem with extending class with instance method after separate module is included into separate class
module ActsAsCommentable
def self.included(commentable)
Thread.class_eval do
def commentable
p "disqusable is #{commentable}"
p "disqusable class is #{commentable}"
end
end
end
end
class Thread
#some code...
end
class Asset
include ActsAsCommentable
end
And now I want to call this method somelike this:
thread = Thread.new
thread.commentable
The problem is, of course is that there is no binding with include method for class eval, and I could save variables that I want to pass into class eval in ActsAsCommentable module, but I dont want to. Is there a better way?
I tried to do instead
module ActsAsCommentable
def self.included(commentable)
class << Thread
define_method :commentable do
p "disqusable is #{commentable}"
p "disqusable class is #{commentable}"
end
end
end
end
But As I guessed this creates instance method for singletone object of class and therefore I can call it only through
Thread.commentable
And again, no binding...
If I understand you correctly, you need to be able to access the commentable variable inside your Thread extension, right?
If so, just change this:
Thread.class_eval do
To this:
Thread.class_exec(commentable) do |commentable|
And it should work.

Why can't I call include from a class method in ruby?

You can call include to mixin a module with a class in ruby, but it must be done at the beginning of the class definition. Why can't it be done inside a class function? Is there an alternate syntax?
EX:
module UsefulThings
def a() puts "a" end
end
class IncludeTester
include UsefulThings
def initialize
end
end
n = IncludeTester.new
n.a()
^^ This works, but if I change IncludeTester to the following, I get the error "undefined method `include'"
class IncludeTester
def initialize
include UsefulThings
end
end
It can be done in a class method.
This works:
module UsefulThings
def a
puts "a"
end
end
class IncludeTester
def self.mix_in_useful_things
include UsefulThings
end
end
x = IncludeTester.new
IncludeTester.mix_in_useful_things
x.a # => a
But "initialize" is not a class method, it's an instance method.
"new" is a class method. You can think of new as allocating a new object and then calling initialize on it, passing initialize whatever arguments were passed to new.
You can't call include directly in initialize because include is a private method of Class (inherited from Module), not of the newly created IncludeTester instance.
If you want to include a module into a class from an instance method, you have to do something like this:
class IncludeTester
def initialize
self.class.send(:include, UsefulThings)
end
end
It's necessary to use "send" here because include is private method, which means it can only be directly invoked with an implicit receiver (of self).
When you call initialize normally in a class definition, you're actually calling it with an implicit receiver of "self", referring to the class being defined.
This is what is actually happening when you do this:
class IncludeTester
include UsefulThings
end
include is a method from Module, Module is the superclass of Class and so include is a method on Class and that makes it a class method in your IncludeTester. When you do this:
class IncludeTester
def initialize
include UsefulThings
end
end
you're trying to call a class method inside an instance method and Ruby says
`initialize': undefined method `include'
because there is no instance method called include. If you want to call a class method inside an instance method (such as initialize), you'd do this:
def initialize
self.class.include UsefulThings
end
But that won't work because include is a private method; you can get around that with class_eval though:
def initialize
self.class.class_eval {
include UsefulThings
}
end
You would be doing include UsefulThings every single time you instantiated an IncludeTester, aside from not making much sense, it could cause problems if UsefulThings had an included method.
It's actually fully possible to include a module from a class method, like so:
module Stuff
def say_hello
puts "hello"
end
end
class Foo
def self.i_am_a_class_method
include Stuff
end
def i_am_an_instance_method
end
end
You cannot however do that from an instance method, because the include method is only available as a private class method, and therefore not accessible from a Foo.new instance.
You want the extend method:
class IncludeTester
def initialize
extend UsefulThings
end
end
This need not be done within the a method either:
IncludeTester.new.tap { |newTester| newTester.extend(UsefulThings) }

Resources