Let's say I have a module that declares a module method (not an instance method):
module M
def self.foo
puts 'foo'
end
end
Now let's say I want to mix-in M.foo
into another class C
such that C.foo
is defined.
Finally, I want to do this without changing the way M.foo
is defined and without just creating a method in C
that calls M.foo
. (i.e. rewriting foo
as an instance method doesn't count. Neither does using module_function
.)
Is this impossible in Ruby?
I want to do this without changing the way
M.foo
is defined
Unfortunately, that's not possible. Ruby only allows to include modules, not classes. foo
however is defined on M
's singleton class which is a class. Therefore, you can't include
it. The same restriction applies to extend
. Trying to do so results in a TypeError
:
module M
def self.foo
puts 'foo'
end
end
class C
extend M.singleton_class # TypeError: wrong argument type Class (expected Module)
end
You can however achieve what you want by defining foo
as an instance method in a separate module which can then be mixed into both, M
and C
via extend
: (that module doesn't have to be nested under M
)
module M
module SingletonMethods
def foo
puts 'foo'
end
end
extend SingletonMethods # <- this makes foo available as M.foo
end
class C
extend M::SingletonMethods # <- this makes foo available as C.foo
end
Or with some metaprogramming magic using Ruby's included
callback:
module M
module SingletonMethods
def foo
puts 'foo'
end
end
extend SingletonMethods
def self.included(mod)
mod.extend(SingletonMethods)
end
end
class C
include M
end
That's a simplified version of how ActiveSupport::Concern
works in Rails.