Instance Methods, Singleton Methods, Class Methods, and the Eigenclass, in Ruby
Instance Methods are methods that are available to all instances of the class. For example:
class Square
def say_something
"hello!"
end
end
square = Square.new
=> #<Square:0x007fec3c116b30>
square.say_something
=> "hello!"
Square.say_something
#=> NoMethodError: undefined method `say_something' for Square:Class
# ...because say_something is not a Class Method - more on this later.
Singleton Methods are methods that are available only to a specific instance of a class. For example:
class Square
end
square = Square.new
=> #<Square:0x007fec3c112f30>
def square.say_something
"hello!"
end
square.say_something
=> "hello!"
Class Methods
First of all, let’s cover Class Variables.
Class Variables eg @@name
are available to all instances of that class. For example:
class Square
@@sides = 4
def sides
@@sides
end
end
square = Square.new
=> #<Square:0x007fec2c116b30>
square.sides
=> 4
When using an Instance Variable, the same is not true:
class Square
@sides = 4
def sides
@sides
end
end
square = Square.new
=> #<Square:0x007fed2c416b30>
square.sides
=> nil
Class Methods are methods that are only available to the class itself.
In our Ruby apps, we often want to do something like Square.sides
and have it return a value. Because class variables are available to all instances of that class (and therefore all classes that inherit from it), it can cause difficult debugging and ‘ghost’ issues in large codebases. It is therefore generally preferred that they are not used (this is also why Rubocop doesn’t allow Class Variables).
Another way to achieve the ability to be able to do Square.sides
and have it return a value, without it referencing a Class Variable, is to use a Class Method that returns an Instance Variable.
class Square
def self.sides
@sides
end
def self.four_sides
@sides = 4
end
def self.where_am_I?
"I'm in the Eigenclass!"
end
end
#is the same as:
class Square
class << self
def sides
@sides
end
def four_sides
@sides = 4
end
def where_am_I?
"I'm in the Eigenclass!"
end
end
end
So, in both cases:
Square.sides
=> nil
Square.four_sides
=> 4
Square.sides
=> 4
Square.where_am_I?
=> "I'm in the Eigenclass!"
square = Square.new
=> #<Square:0x007fec3c116b30>
square.where_am_I?
#=> NoMethodError: undefined method 'where_am_I?' for #<Square:0x007fb231233aa8>
#Define a Singleton method:
def square.where_am_I?
"I'm also in the Eigenclass!"
end
square.where_am_I?
=> "I'm also in the Eigenclass!"
Class methods are placed in the Eigenclass which is an anonymous class that Ruby places in the hierarchy chain, so as not to affect instances of that class.
You may never do the following, but it drives home the above; lets create a Square
class containing Instance Methods and Class Methods:
class Square
def sides
@sides
end
def four_sides
@sides = 4
end
class << self
def sides
@sides
end
def four_sides
@sides = 4
end
end
end
square_instance = Square.new
=> #<Square:0x007fec2fc116b30>
square_instance.sides
=> nil
Square.sides
=> nil
Square.four_sides
=> 4
Square.sides
=> 4
# Square's Eigenclass's properties have been changed, but the square_instance instance of Square has not:
square_instance.sides
=> nil
square_instance.four_sides
=> 4
square_instance.sides
=> 4
square_instance
is an instance of Square
, whereas Square
is an instance of Class
; the Eigenclass
holds Square
’s class methods its variable @sides
. Well, that’s the way I think about it anyway! (Need to dig into this and understand it a bit more).
If we reload this code and do it the other way round, the same sort of thing occurs:
square_instance = Square.new
=> #<Square:0x007fec3c14ab30>
square_instance.sides
=> nil
square_instance.four_sides
=> 4
# because Square's Eigenclass hasn't been affected:
Square.sides
=> nil
Square.four_sides
=> 4
Square.sides
=> 4
Much Tidier Uses of this Pattern
class Square
class << self
attr_reader :sides
def four_sides
@sides = 4
end
end
end
Square.sides
=> nil
Square.four_sides
=> 4
Square.sides
=> 4
class Square
class << self
attr_accessor :sides
end
end
Square.sides
=> nil
Square.sides = 4
=> 4
Square.sides
=> 4
Ta da!