Last active
October 18, 2023 01:44
-
-
Save yoelblum/12b6a648d0ff7df4e3c0e95a55e8be1d to your computer and use it in GitHub Desktop.
Ruby base class VS including modules (mixins)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Ruby's inheritance mechanisms are a bit unique and have some subtelties | |
# There are two main ways to inherit: Direct Inheritance (with a "Base" class) and "Mixins" (including modules) | |
# "Direct inheritance" (B is the base class of A) | |
class B | |
X = 30 | |
def self.static_method | |
"Hello from static method" | |
end | |
def inst_method | |
"Hello from instance method" | |
end | |
end | |
class A < B | |
end | |
puts A.static_method # Hello from static method | |
puts A::X # 30 | |
puts A.new.inst_method # Hello from instance method | |
# Pretty much what you'd expect coming from other programming languages: static methods are inherited, | |
# instance methods are inherited and constants are inherited | |
# Important: You can only inherit from one class in this way! | |
# Way #2 is Mixins (including modules) | |
module Includable | |
X = 30 | |
def self.static_method | |
"Hello from static method" | |
end | |
def inst_method | |
"Hello from instance method" | |
end | |
end | |
class C | |
include Includable | |
end | |
puts C.static_method # NoMethodError (undefined method `static_method' for C:Class) | |
puts C.new.inst_method # Hello from instance method | |
puts C::X # 30 | |
# So with mixins instance methods are inherited, constants are inherited but static methods are not! | |
# ActiveSupport Concerns are a popular way to mixin "fully inherited" modules (instance methods, static_methods and constants!) | |
# https://api.rubyonrails.org/classes/ActiveSupport/Concern.html | |
# Ruby Inheritance Order of Precedence | |
# So who gets precedence, a mixin or a base class? | |
class E | |
X = 30 | |
def say_hello | |
"Hello from E" | |
end | |
end | |
module Mixin | |
X = 40 | |
def say_hello | |
"Hello from Mixin" | |
end | |
end | |
class F < E | |
include Mixin | |
end | |
puts F::X # 40 | |
puts F.new.say_hello # Hello from Mixin | |
# It may be a bit surprising but the Mixin gets precedence over the base case | |
# If you're having a hard time remembering this, you can always use the ancestors method | |
puts F.ancestors # [F, Mixin, E, Object, Kernel, BasicObject] | |
# The order of precedence is just what you see in ancestors: First F, the class itself (obviously!). Then perhaps surprisingly | |
# all mixins (included modules) which is "Mixin" in our example. then the base class E. Later it's ruby's Object, Kernel and | |
# BasicObject |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment