Ruby class methods syntax
ArticleIn Ruby you are able to write class methods multiple ways.
We will quickly show 4 major ways and explain how they work and when you may want to use one over the other:
option 1 - def self.method_name
class MyModel
def self.what_is_your_quest
"To find the Holly Grail"
end
def self.what_is_your_favorite_color(knight)
case knight
when "Lancelot"
"blue"
else
"blue, ...no red !"
end
end
def hello_world
"this is an instance method"
end
end
MyModel.what_is_your_quest
# => "To find the Holly Grail"
MyModel.what_is_your_favorite_color("Lancelot")
# => "blue"
MyModel.new.hello_world
# => "this is an instance method"
option 2 - class << self
class MyModel
class << self
def what_is_your_quest
"To find the Holly Grail"
end
def what_is_your_favorite_color(knight)
case knight
when "Lancelot"
correct_color
else
"#{correct_color}, ...no, red !"
end
end
private
def correct_color
"blue"
end
end
def hello_world
"this is an instance method"
end
end
MyModel.what_is_your_quest
# => "To find the Holly Grail"
MyModel.what_is_your_favorite_color("Lancelot")
# => "blue"
MyModel.correct_color
# => NoMethodError (private method `correct_color' called for MyModel:Class)
MyModel.new.hello_world
# => "this is an instance method"
Option 3 - extend a module
class MyModel
module BridgeKeeperQuestions
def what_is_your_quest
"To find the Holly Grail"
end
def what_is_your_favorite_color(knight)
case knight
when "Lancelot"
correct_color
else
"#{correct_color}, ...no, red !"
end
end
private
def correct_color
"blue"
end
end
extend BridgeKeeperQuestions
def hello_world
"this is an instance method"
end
end
MyModel.what_is_your_quest
# => "To find the Holly Grail"
MyModel.what_is_your_favorite_color("Lancelot")
# => "blue"
MyModel.correct_color
# => NoMethodError (private method `correct_color' called for MyModel:Class)
MyModel.new.hello_world
# => "this is an instance method"
Option 4 - instance eval
class MyModel
def hello_world
"this is an instance method"
end
end
MyModel.instance_eval do
def what_is_your_quest
"To find the Holly Grail"
end
def what_is_your_favorite_color(knight)
case knight
when "Lancelot"
correct_color
else
"#{correct_color}, ...no, red !"
end
end
private
def correct_color
"blue"
end
end
MyModel.what_is_your_quest
# => "To find the Holly Grail"
MyModel.what_is_your_favorite_color("Lancelot")
# => "blue"
MyModel.correct_color
# => NoMethodError (private method `correct_color' called for MyModel:Class)
MyModel.new.hello_world
# => "this is an instance method"
If you are experienced Ruby dude and you are like: “Wait a minute ! There are more than 4 ways !” then yes you are right but to be honest all those remaining ways are really doing one of these 4 things just different way.
What class methods really are ?
When you look at Option 4 you may be wondering: Hmm, shouldn’t that be class_eval
?
Answer is no ! instance_eval
is correct
In Ruby everything is an object. Even class methods are
actually instance methods of the class object instance. Camel case
names like MyModel
are nothing else then just constants referencing
these objects.
To prove that let me reverse engineer the class instance backward:
a = Class.new
# => #<Class:0x0000000001a30b50>
def a.what_is_your_quest
"To find the Holly Grail"
end
# => :what_is_your_quest
MyModel = a
MyModel.what_is_your_quest
# => "To find the Holly Grail"
a.class_eval do
def hello_world
"this is an instance method"
end
end
MyModel.new.hello_world
# => "this is an instance method"
If it feels confusing run this example several times in
irb
It’s not a rocket science. When you do:
class MyModel
end
…what you really create is a Class.new
and assign it to constant
MyModel
like: MyModel = Class.new
Then when you are defining “class methods”:
class MyModel
def self.what_is_your_quest
# ...
end
end
…in reality you are defining
“instance methods” on this Class
instance
This apply to every of the options I’ve mentioned above. Here is a proof:
Option 1:
my_model = Class.new do
def self.what_is_your_quest
"To find the Holly Grail"
end
def hello_world
"this is an instance method"
end
end
my_model.what_is_your_quest
# => "To find the Holly Grail"
my_model.new.hello_world
# => "this is an instance method"
Option 2:
my_model = Class.new
class << my_model
def what_is_your_quest
"To find the Holly Grail"
end
end
my_model.what_is_your_quest
# => "To find the Holly Grail"
self
is just the reference of current instance. So when we didclass << self
in the original Option 2 example we wrote exact equivalent of this example
Option 3:
module BridgeKeeperQuestions
def what_is_your_quest
"To find the Holly Grail"
end
def what_is_your_favorite_color(knight)
case knight
when "Lancelot"
correct_color
else
"#{correct_color}, ...no, red !"
end
end
private
def correct_color
"blue"
end
end
my_model = Class.new
my_model.extend(BridgeKeeperQuestions)
my_model.what_is_your_quest
# => "To find the Holly Grail"
Option 4
my_model = Class.new
my_model.instance_eval do
def what_is_your_quest
"To find the Holly Grail"
end
end
my_model.what_is_your_quest
# => "To find the Holly Grail"
Which one to use ?
It really doesn’t matter. Only argument is style of writing the class.
When you go with Option 1 you may end up with too many definitions in the class:
# app/model/my_model.rb
class MyModel
def self.klass_method_1
end
def self.klass_method_2
end
def self.klass_method_3
end
def self.klass_method_4
end
def self.klass_method_5
end
# ...
def initialize(foo)
@foo = foo
end
def finally_my_instance_method
@foo + "hi"
end
end
…this way you may have too much knowledge in your code on the class (which we now agreed is a different object) and not so much on the instance (which is what the object oriented programming is trying to work upon)
plus it’s hard/confusing to do private class methods this way
When you go with Option 2:
# app/model/my_model.rb
class MyModel
class << self
def klass_method_1
end
def klass_method_2
end
def klass_method_3
end
def klass_method_4
end
# ...
private
def private klass_method
end
# ...
end
def initialize(foo)
@foo = foo
end
def finally_my_instance_method
@foo + "hi"
end
end
… you are able to do private
methods easily but you will have the same problem as with Option 1:
“Too much knowledge around class”. Plus it’s super easy to lost context
on what are class methods and where instance methods start when you have long
enough file.
Option 4 is more an option for metaprogramming and when you are writing libraries / overwriting libraries in your system.
So that leaves us with Option 3. When you are dealing with small amount of class methods it’s easy to maintain them within the same file:
# app/model/my_model.rb
class MyModel
module MyModelKlassMethods
def klass_method_1
end
def klass_method_2
end
end
extend MyModelKlassMethods
def initialize(foo)
@foo = foo
end
def finally_my_instance_method
@foo + "hi"
end
end
And once they get out of hand all you need to do is to extract them to separate file:
# app/model/my_model.rb
class MyModel
extend MyModelKlassMethods
def initialize(foo)
@foo = foo
end
def finally_my_instance_method
@foo + "hi"
end
end
# app/model/concerns/my_model_klass_methods.rb
module MyModelKlassMethods
def klass_method_1
end
def klass_method_2
end
def klass_method_3
end
def klass_method_4
end
# ...
private
def private klass_method
end
# ...
end
How do I maintain my class methods
To be honest although Option 3 (extend module) is cool and all, when I’m writing a code I keep my class methods defined directly within the file with Option 1:
# app/model/my_model.rb
class MyModel
def self.klass_method_1
# ...
end
def instance_method
# ...
end
end
And as soon as I see there is more than 3 class methods in the model that are related to same thing I extract them to module. That means I may end up with something like this:
# app/model/my_model.rb
class MyModel
extend MyModel::AccountingClassMethods
extend MyModel::CartoonWatchingClassMethods
def self.what_is_your_favorite_color
# ...
end
def instance_method_1
# ...
end
def instance_method_2
# ...
end
end
# app/model/my_model/accounting_class_methods.rb
module MyModel::AccountingClassMethods
# ...
end
# app/model/my_model/cartoon_watching_class_methods.rb
module MyModel::CartoonWatchingClassMethods
# ...
end
In reality I’m using “Bound Contexts” to hold my related moduls/classes in place. So the file would be
app/bound_contexts/accounting/my_model_class_methods.rb
andapp/bound_contexts/cartoon_watching/my_model_class_methods.rb
but I’m preparing series of articles on Bound Contexts where I’ll explain that in details
The point is don’t just blindly move class methods away from your objects just so the models are “clean”. In reality you may actually create bigger mess if you move unrelated class method level stuff of different contexts.
That’s why I don’t like using class << self
as it will scope all class methods as if they were
related.
In reality your class methods are representing different contexts that just behave similar way.
I hope you learned something new in this article. If not maybe you will
consider to prefer extend
over class << self
.
One more note: You can maintain instance methods similar way too using include
having a generic way
how to write code for instance and class methods.
Modules
One more important trick. When you are writing modules you can define “class methods” like this:
module MyModule
def self.what_is_your_quest
"To find the Holly Grail"
end
def self.what_is_your_favorite_color(knight)
case knight
when "Lancelot"
"blue"
else
"blue, ...no red !"
end
end
end
MyModule.what_is_your_quest
# => "To find the Holly Grail"
MyModule.what_is_your_favorite_color('Lancelot')
# => "blue"
but you can also do this:
module MyModule
extend self
def what_is_your_quest
"To find the Holly Grail"
end
def what_is_your_favorite_color(knight)
case knight
when "Lancelot"
"blue"
else
"blue, ...no red !"
end
end
end
MyModule.what_is_your_quest
# => "To find the Holly Grail"
MyModule.what_is_your_favorite_color('Lancelot')
# => "blue"
What the module will do it will extend itself with self so the module methods becomes “class methods”
Now this is cool and all but you need to be carefull to do this only on modules where you are 100% sure everything will be class level (for example when you are writing functional programming module, as if you were writing code in Elixir lang)
If you module should be exetending instances with include
you may want
to stick with def self.method_name
or do something like this:
module MyModule
module KlassMethods
def what_is_your_quest
"To find the Holly Grail"
end
def what_is_your_favorite_color(knight)
case knight
when "Lancelot"
"blue"
else
"blue, ...no red !"
end
end
end
extend KlassMethods
def instance_method_1
'this is instance method'
end
end
class Foo
include MyModule
end
Foo.new.instance_method_1
# => 'this is instance method'
MyModule.what_is_your_quest
# => "To find the Holly Grail"
sources
Entire blog website and all the articles can be forked from this Github Repo