Search code examples
rubymetaclasseigenclasssingleton

class << self idiom in Ruby


What does class << self do in Ruby?


Solution

  • First, the class << foo syntax opens up foo's singleton class (eigenclass). This allows you to specialise the behaviour of methods called on that specific object.

    a = 'foo'
    class << a
      def inspect
        '"bar"'
      end
    end
    a.inspect   # => "bar"
    
    a = 'foo'   # new object, new singleton class
    a.inspect   # => "foo"
    

    Now, to answer the question: class << self opens up self's singleton class, so that methods can be redefined for the current self object (which inside a class or module body is the class or module itself). Usually, this is used to define class/module ("static") methods:

    class String
      class << self
        def value_of obj
          obj.to_s
        end
      end
    end
    
    String.value_of 42   # => "42"
    

    This can also be written as a shorthand:

    class String
      def self.value_of obj
        obj.to_s
      end
    end
    

    Or even shorter:

    def String.value_of obj
      obj.to_s
    end
    

    When inside a function definition, self refers to the object the function is being called with. In this case, class << self opens the singleton class for that object; one use of that is to implement a poor man's state machine:

    class StateMachineExample
      def process obj
        process_hook obj
      end
    
    private
      def process_state_1 obj
        # ...
        class << self
          alias process_hook process_state_2
        end
      end
    
      def process_state_2 obj
        # ...
        class << self
          alias process_hook process_state_1
        end
      end
    
      # Set up initial state
      alias process_hook process_state_1
    end
    

    So, in the example above, each instance of StateMachineExample has process_hook aliased to process_state_1, but note how in the latter, it can redefine process_hook (for self only, not affecting other StateMachineExample instances) to process_state_2. So, each time a caller calls the process method (which calls the redefinable process_hook), the behaviour changes depending on what state it's in.