In 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 did class << 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 and app/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