In Ruby, is it possible to define a method that can be called directly as a class method and is also able to be mixed in as a class method? That is, without using self.included
or self.extended
to create an equivalent class or instance method.
Neither of these approaches works:
module A
def foo(s)
puts s
end
end
class One
extend A
end
One.foo("one")
#A.foo("a")
module B
def self.foo(s)
puts s
end
end
class Two
include B
end
#Two.foo("two")
B.foo("b")
There seems to be some confusion as to what is being asked. Here's a less abstract scenario. A is a mixin that can be used directly. B is a mixin, intended to be used independently of A, that "wraps" one of A's methods.
module A
# #foo has to be defined in order to be mixed in via `extend`.
# Being mixed in via `include` has the same issue but inverted.
def foo(s) A.foo(s) end
def self.foo(s) puts "A: " + s end
end
module B
def foo(s) A.foo("B: " + s) end
end
class One; extend A end
class Two; extend B end
One.foo("one")
Two.foo("two")
In order for this to work, both A#foo
and A::foo
have to be separately defined. Existing facilities such as Module#module_function
don't work under this scenario.
I will try not to be definitive but to my knowledge the answer to your question is No. If you would like to mixin both instance and class methods then the standard methodology would be something like:
module A
def self.included(base)
#this will extend the class you included A in
#using A::ClassMethods definition
base.extend(ClassMethods)
end
#these methods will be added as class_methods to any class
#that includes A
module ClassMethods
def foo(s)
"You fooed the class with #{s}"
end
end
#this will be added as an instance method as it would be in a standard include
def bar(s)
"You barred an instance with #{s}"
end
end
class Mixed
include A
end
Mixed.foo("Hello")
#=> "You fooed the class with Hello"
Mixed.new.bar("Hello")
#=> "You barred an instance with Hello"
I hope this answers your question as it was a bit unclear what your intentions were. Since you question does not seem to require instance methods you could also do this
module A
def foo(s)
"called foo with #{s}"
end
end
module B
include A
alias_method :a_foo, :foo
def foo(s)
"B called foo from A #{a_foo(s)}"
end
end
class Mixed
extend B
end
Mixed.foo("Mixed")
#=>"B called foo from A called foo with Mixed"
One more update
This is a strange pattern but it will work for your use case I believe
module A
def foo(s)
"fooed with #{s}"
end
def bar(s)
"barred with #{s}"
end
end
module B
include A
included_modules.each do |mod|
(mod.instance_methods - Object.methods).each do |meth|
alias_method "#{mod.name.downcase}_#{meth}", meth
end
end
end
class Mixed
extend B
end
Mixed.methods - Object.methods
#=> [:a_foo, :a_bar, :foo, :bar]
This way you can overwrite methods in B
and call the A
version but if you don't overwrite it will still call the A
version.
You could also monkey patch the Module
class if you'd like to make this functionality universal
class Module
def include_with_namespace(*mods)
#Module#include runs in reverse so to maintain consistency my patch does as well
mods.reverse.each do |mod|
include mod
(mod.instance_methods - Object.methods).each do |meth|
alias_method "#{mod.name.downcase}_#{meth}", meth
end
end
end
end
Then this will work
module C
def foo(s)
"C's foo with #{s}"
end
def see_me
"You can see C"
end
end
module B;include_with_namespace A, C; end
class Mixed;extend B;end
Mixed.methods - Object.methods
#=> [:a_foo, :a_bar, :c_foo,:c_see_me, :foo, :bar, :see_me]
Mixed.foo("name")
#=> "fooed with name"
Mixed.c_foo("name")
#=> "C's foo with name"