Tuesday, July 12, 2016

Ruby classes and instances

Ruby class, instance, module and all the frustration in-between

Programming in Ruby can be problematic when you start trying to dwell a bit beyond the common usages. In my opinion this is due to Ruby having a bit too many rules (ie. 4-5 different ways to inherit with different behaviors) and not being all-around concise (ie. some non-bang methods actually doing in-place replacement).

Below I try to bring some sanity to all this by hopefully providing some a-ha moments.

Methods come in two flavors: instance and class

Instance methods are the most commonly used. Class methods (aka static methods in other languages) are the ones with self prepended. In that case, self refers to the class itself and NOT an instance to it.

class MyClass
    def self.class_do
        print(10)
    end
    def instance_do
        print(20)
    end
end

And you call them like this

MyClass.class_do        # -> 10
MyClass.new.instance_do # -> 20

Self can point to a class or an instance

In most programming languages we use self solely to refer to the instance of a class from inside the class. However in Ruby self can refer to more than that.

Below we have two methods that print the self variable in different contexts:

class MyClass
    def self.show_self # class context
        print(self)
    end
    def show_self      # instance context
        print(self)
    end
end

And you call them like this

MyClass.show_self     # -> MyClass
MyClass.new.show_self # -> <MyClass:0x000000026edb90>

As you see, self can refer to more than the instance.

Modules have self as well

What’s the above behaviour in a module?? Apparently it’s exactly as with classes. But since a module can’t be instantiated by definition, we simply never see self as an instance reference.

module MyModule
    def self.show_self
        print(self)
    end
    def show_self
        print(self)
    end
end

MyModule.show_self     # -> MyModule
MyModule.new.show_self # => ERROR

The last error is due to the module not being able to be instantiated than anything else.

Extend, include, inheritance!

All these ways to inherit behavior might make a new-comer confused. What you need to remember is that include copies over instance methods while extend copies over variable methods.

Let’s say we have the MyModule module with an instance and a class method.

module MyModule
    def instance_do
        print(10)
    end
    def self.class_do
        print(20)
    end
end

Let’s include the module in our class:

class MyClass
    include MyModule
end

MyClass.instance_do     # -> ERROR
MyClass.class_do        # -> ERROR
MyClass.new.instance_do # -> 10

Nothing unexpected here. We inherit the instance methods as expected so we can use them with any class instances.

Let’s extend our class with the module:

class MyClass
    extend MyModule
end

MyClass.instance_do     # -> 10
MyClass.class_do        # -> ERROR
MyClass.new.instance_do # -> ERROR

Now this is probably the thing that is most confusing. As we see, extend actually takes the instance methods from the module and translates them to class instances.

This makes sense to some degree since self in the context of the module refers to the module itself. Confusing non-the-less.

Written with StackEdit.