Saturday, 12 September 2020
I've worked with a lot of Ruby codebases in my career, some in better shape than others. One bug bear of mine is that many would-be Ruby developers, or those coming from other languages seem to use a class method (e.g self.method
) in place of a good ole instance method. In less than desirable codebases you'll often find:
class MyService
def self.method(args)
# Do some stuff
end
end
Soon the functionality of the class above will need to be extended, and an inexperienced developer will throw in some state without realising the ramifications of doing so in a self
method:
class MyService
def self.method(args)
# Do some stuff
@an_instance_variable = result_of_above
end
def self.another_method
puts @an_instance_variable
end
end
When I saw the usage of instance variables like this in class methods, I baulked. But it turns out it does work:
irb(main):055:0> class MyService
irb(main):056:1> def self.method(args)
irb(main):057:2> # Do some stuff
irb(main):058:2>
irb(main):059:2> @an_instance_variable = 1
irb(main):060:2> end
irb(main):061:1>
irb(main):062:1> def self.another_method
irb(main):063:2> puts @an_instance_variable
irb(main):064:2> end
irb(main):065:1> end
=> :another_method
irb(main):066:0> MyService.method({})
=> 1
irb(main):067:0> MyService.another_method
1
=> nil
So a Ruby class can have class level instance variables
🤯. This was news to me. To take things further, you can assign a class level instance variable outside of a method and the value will still be persisted in class methods:
class MyService
@an_instance_variable = 2
def self.another_method
puts @an_instance_variable
end
end
Here is the irb
to prove it:
irb(main):085:0> MyService.another_method
2
To strip things back even further:
irb(main):086:0> class MyService
irb(main):087:1> @an_instance_variable = 2
irb(main):088:1> end
=> 2
irb(main):089:0> MyService.instance_variables
=> [:@an_instance_variable]
So, how can this be the case? Well:
Because each class is an object, it can have instance variables just like any other Ruby object.
I'm not sure why you would want to introduce such a niche feature of Ruby into your codebase, but knock yourself out (if you are OK with unintended consequences of such).
I have often seen class methods using the self.method
syntax and then the weird looking class << self
syntax used interchangeably. The latter syntax is something called an Eigenclass
, or more simply a Singleton
class:
class MyService
# Approach A
def self.method
1
end
# Approach B
class << self
def method
1
end
end
end
The two approaches above are not equivalent!
irb(main):102:0> class Hi
irb(main):103:1> self #=> Hi
irb(main):104:1>
irb(main):105:1> class << self
irb(main):106:2> self #=> #<Class:Hi>
irb(main):107:2> self == Hi.singleton_class #=> true
irb(main):108:2> end
irb(main):109:1> end
=> true
irb(main):111:0> Hi.itself
=> Hi
irb(main):112:0> Hi.singleton_class
=> #<Class:Hi>
self
in Ruby usually refers to the instance of a class when you call it in that context. However self
in the example above can also refer to the Hi
class itself, as after all, all Ruby classes are still objects.
What the class << self
syntax is saying is to create an anonymous class based off of self
which in the lexical scope is the class Hi
(and given that class << self
is a special syntax that signifies a Singleton, then assign the new anonymous class to Hi.singleton_class
). The outputs above prove that to be the case.