How to Override Static Class Method Using Module in Ruby

How to override static class method using module in Ruby?

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.

How to override class methods

module SeleniumWebDriverExtension
def for(browser, *args)
...
end
end

Selenium::WebDriver.singleton_class.prepend(SeleniumWebDriverExtension)

How can I call a static method in a Ruby module from a class that includes the module?

Define your module like this (i.e. make exit an instance method in the module):

module Software
def exit
puts "exited"
end
end

and then use extend rather than include

class Windows
extend Software
# your self.start method as in the question
end

In use:

irb(main):016:0> Windows.start
started
exited
=> nil

Explanation

obj.extend(module, ...) adds to
obj the instance methods from each module given as a parameter

...so when used within the context of a class definition (with the class itself as the receiver) the methods become class methods.

Overriding instance methods of a class by mixing in a module

You could remove each of B's methods from A before including B.

class A
def method1
"A\#method1"
end

def method2
"A\#method2"
end

B.instance_methods(false).each { |method|
remove_method(method) if instance_methods(false).include?(method)
}
include B
end

Or from within B:

module B
def method1
"B\#method1"
end

def method2
"B\#method2"
end

def self.append_features(mod)
instance_methods(false).each { |method|
mod.send(:remove_method, method) if mod.instance_methods(false).include?(method)
}
super
end
end

Ruby hiding vs overriding

Java has three different kinds of "methods": instance methods, static methods and constructors. Ruby only has one: instance methods.

In Java, static methods must behave differently from instance methods, because classes aren't objects. They have no class, therefore no superclass, so there is nothing to override. In Ruby, classes are objects just like any other object, they have a class, which can have a superclass, and thus subclasses can override superclass methods.

Note: you may have heard about class methods or singleton methods in Ruby. That's a lie. Well, okay, not a lie. It's a convenient shorthand we use, because "class method" is easier to pronounce than "regular instance method of the class object's singleton class" … but that's precisely what it is. There are no class methods.

In Ruby, every object can have methods of its own. These are called "singleton methods". Classes are objects like any other object, so they can have singleton methods, too. When the object a singleton method belongs to is a class, we call that method a class method. But that's just what we call it, there is no difference between a class method and a singleton method.

Actually, in Ruby, every object has a singleton class. The singleton class is in a 1:1 relation with the object: the object has exactly one singleton class and each singleton class has exactly one instance, its object. So, when I said above that objects can have methods and those methods are called singleton methods? Well, that was a lie, too. Singleton methods are really just standard instance methods, which happen to be defined in the singleton class of an object, and thus can be called only on that object (because that object is the only instance of its singleton class).

So, when a method is defined in a singleton class, we call it a singleton method and when the singleton class belongs to a class, we call it a class method, but it's all just instance methods. (BTW: modules work the same way. In that case, they are called module methods or sometimes "module functions".)

The class pointer of an object always points to its singleton class. The actual class of an object is then the superclass of the singleton class, i.e. the singleton class's superclass pointer points to the actual class of the object. (Unless there are mixins, which become superclasses of the class they are mixed into, so if you mix a module into a singleton class, the module becomes the superclass of the singleton class, and the old superclass becomes the superclass of the module, or rather its include proxy class.)

What this means is that method lookup, which is the most often performed operation in an OO language, becomes really simple and really fast: grab the object, grab its class pointer, see if the method is there, grab the superclass pointer, see if the method is there, grab the superclass pointer … until you found the method.

It does mean that reflection gets a little more complex, but reflection is not a performance-critical operation. For example, if you ask an object for its class, you can't simply return the class pointer, as that would always be its singleton class and thus not very informative. You have to get the superclass, and the superclass's superclass and so on, until you end up at a class which is not a singleton class or an include proxy class.

But method lookup itself is very simple, and super always does what you expect.

In particular, when you create a new class, the singleton class of the superclass becomes the superclass of the singleton class of the subclass, so that "class methods" are inherited just as you would expect.

So, to recap: while Java has three different kinds of "methods" with different inheritance behavior (instance methods get inherited, static methods don't, constructors get inherited but have this super calling restriction), Ruby has only one. It has, however, three different kinds of classes: regular classes, singleton classes, and include proxy classes (created as proxies for mixins, when mixing a module into a class). The latter two are also called "virtual classes" inside of YARV, the most widely-used Ruby implementation.

One last thing: there are also so-called "global methods", sometimes called "global procedures" or "global functions". Again, as you probably guessed already, these don't exist. When you define a method outside of any class, it implicitly becomes a private instance method of Object and thus available for every object.

[I ignored two things here: BasicObject and prepend. These complicate matters somewhat, especially the latter. But the main mental model remains.]

Ruby: alias_method for module static method

Test.singleton_class.send(:alias_method, :bar, :foo)
Test.bar("cat")
#=> "Test Foo with cat"

Overriding instance methods of a class by mixing in a module

You could remove each of B's methods from A before including B.

class A
def method1
"A\#method1"
end

def method2
"A\#method2"
end

B.instance_methods(false).each { |method|
remove_method(method) if instance_methods(false).include?(method)
}
include B
end

Or from within B:

module B
def method1
"B\#method1"
end

def method2
"B\#method2"
end

def self.append_features(mod)
instance_methods(false).each { |method|
mod.send(:remove_method, method) if mod.instance_methods(false).include?(method)
}
super
end
end


Related Topics



Leave a reply



Submit