How to override static class method using module in Ruby?
The simplest way to override a class method is to prepend
the module to the singleton
class of Some
:
module Imodule
def imethod
puts "overrided"
end
end
Some.singleton_class.prepend(Imodule)
Some.imethod
# => "overrided"
Explanation: extend
works for class methods in the same way as include
does for instance methods. Both extend
and include
are resolved before the methods declared in the class body, hence they never override.
To override an instance method we use prepend
however there is no alternative of prepend
for class methods. As everything is an object in Ruby, what you can do is to use prepend
while you're treating the class as an object instance. Because class methods are essentially object instance methods declared in the object singleton class, you can open the corresponding singleton class and prepend
the methods.
You can do the SAME thing inside the class too:
class Some
class << self
prepend Imodule
end
end
Some.imethod
# => "overrided"
What if you need to be able to call the original method that you just overrode, from within your new method?
In other words, what if you want to be able to call super
from an overridden class method the same way you might call super
when overriding an instance method?
Here's the solution I finally arrived upon, in case anyone else finds it useful:
class Klass
def self.say
puts 'original, '
end
end
module FooModule
def self.included base
orig_method = base.method(:say)
base.define_singleton_method :say do |*args|
orig_method.call(*args)
puts "module"
end
end
end
class Klass
include FooModule
end
Klass.say # => original, module
We have to use define_method
instead of def
so that we create a closure and have access to local variables (in this case, the saved version of the original method) from within the new method definition.
By the way,
base.define_singleton_method :say do
is equivalent to doing
(class << base; self; end).send :define_method, :say do
.
Special thanks to Ruby singleton methods with (class_eval, define_method) vs (instance_eval, define_method) and http://yugui.jp/articles/846 for educating me and pointing me in the right direction.
Ok, here's a working code. Note that you don't even have to touch target class! :)
class Klass
def self.say
puts 'class'
end
end
module FooModule
def self.included base
base.instance_eval do
def say
puts "module"
end
end
end
end
Klass.send(:include, FooModule)
Klass.say
Explanation
Now classic way of mixing in class methods is this (and it doesn't solve the problem, of course).
module FooModule
def self.included base
base.extend ClassMethods
end
module ClassMethods
def bar
puts "module"
end
end
end
class Klass
include FooModule
def self.bar
puts 'class'
end
end
Klass.bar #=> class
When modules are included or extended into a class, its methods are placed right above this class' methods in inheritance chain. This means that if we were to call super
in that class method, it would print "module". But we don't want to touch original class definition, we want to alter it from outside.
So, can we do something?
Good for us, ruby has a concept of "open classes". This means that we can change virtually everything in the app, even some 3rd-party libraries. Every class can "opened" and new methods can be added to it, or old methods can be redefined. Let's look how it works.
class Klass
def self.bar
puts 'class'
end
end
class Klass
def self.bar
puts 'class 2'
end
end
Klass.bar #=> class 2
The second class definition does not overwrite previous one, it opens and alters it. In this case, it happened to define a method with the same name. This resulted in old method being overwritten by the new one. This works with any classes, even base library classes.
puts [1, 2, 3].to_s #=> [1, 2, 3]
class Array
def to_s
"an array: #{join ', '}"
end
end
puts [1, 2, 3].to_s #=> an array: 1, 2, 3
Or the same code can be rewritten as
puts [1, 2, 3].to_s #=> [1, 2, 3]
Array.class_eval do
def to_s
"an array: #{join ', '}"
end
end
puts [1, 2, 3].to_s #=> an array: 1, 2, 3
Applying the knowledge
Let's start with simpler things, like overriding an instance method.
class Klass
def say
puts 'class'
end
end
module FooModule
def self.included base
base.class_eval do
def say
puts "module"
end
end
end
end
Klass.send(:include, FooModule)
Klass.new.say #=> module
Modules have a special callback that gets called every time a module is included in a class. We can use that to call class_eval on that class and redefine a method.
Replacing a class method is done in a similar way.
class Klass
def self.say
puts 'class'
end
end
module FooModule
def self.included base
base.instance_eval do
def say
puts "module"
end
end
end
end
Klass.send(:include, FooModule)
Klass.say #=> module
The only difference here is that we call instance_eval instead of class_eval. This can be a very confusing part. In short, class_eval creates instance methods and instance_eval creates class methods.
This is taken from my blog post.