Love Your Self

Ruby’s Self

When you write ruby code, many times, the context in which your code is executed matters. This context will contain an assumption as to the default Object methods are being called on, self . Within a class definition, this is generally the class itself. Within an instance method definition, it is the instance of that class, etc.

So,

example()

is functionally identical to

self.example()

The self keyword is implied by the code context.

The implicit self is one of the things that lends ruby to expressive, programmer-friendly DSLs. The code looks nicer and easier to read. But it’s also part of what makes ruby (and rails) seem ‘magical’ sometimes. When you see

validates :user, presence: true

The intent is usually pretty clear, but it’s not always obvious what’s actually happening when the code is executed.

Using Self to Clarify Code

These DSL techniques can be powerful, but also somewhat obtuse.

My best example of this is the boilerplate ActionController code you see everywhere:

respond_to do |format|
  format.html
  format.xml { render xml: @record }
end

At first glance, you can guess what is happening here, but the more you consider the code, the less clear it is what is actually going on.

Stop and read that code for a bit, and decide what exactly is happening there.

It feels like something like this is happening:

if format == 'html' then output_default_formatting
elsif format == xml then output_xml_formatting

But that’s definitely not what’s written. There are no if statements there; no case statement, no boolean logic at all… What is it actually doing?

self.respond_to() do |format|
  format.html
  format.xml { render xml: @record }
end

So, ActionController implements a respond_to method that takes a block and supplies it a format object. The format object has methods for various output types and each of those methods takes an optional block. We can surmise that that block is only executed if the method and the requested output type match.

Why is it implemented this way? I have absolutely no idea. A question for another day…

Anyway, if there is always an implicit self target, why is there a self keyword, and why do you have to use it sometimes?

Sometimes, it’s not just useful to think of the self in order to understand code, it’s necessary in order for the ruby parser to disambiguate software intent.

When to use self

The self keyword is required to remove ambiguity in some expressions:

Operator Overloading

When using methods like value= or class [] , + methods, etc. The ruby default syntax takes precedent, so you need to explicitly state you are trying to use the local one. Creating a temporary variable would take precedent over the value= method.

e.g.

class Dog
  def name= (name)
    @name = name
  end

  def something
    name = "potato"
  end

The name= in the something method would not be calling the name= method defined above. It would be creating a temporary variable called name. Then assigning it the value ‘potato’, exiting the method, at which point that variable would go out of scope. The same thing would happen using the implicit setter method created by attr_accessor.

Class Methods

Of course, we also use self to define class methods. You can do this two ways:

class TestClass
  def self.message
    @@message
  end
end

You can also find this written as:

class TestClass
  class << self
	  def message
	    @@message
	  end
  end
end

Method Chaining

Users.where(subscribed: true).order(date_of_birth: :asc).limit(1)

Each method above returns an ActiveRecord::Relation. The method alters the scope of the query each time, and, since the return type is also ActiveRecord::Relation, these methods can be chained together for brevity and clarity.

For method chaining, it doesn’t matter whether the same, mutated object is returned, or a new one of the same type. Here’s a simple counter:

class Counter
  def initialize
    @count = 0
  end

  def inc
    @count +=1
  end

  def dec
    @count -=1
  end

  def to_s
    @count.to_s
  end
end

c = Counter.new
c.inc
c.dec
c.inc
c.inc
c.inc

puts c
# => "3"

You can modify this slightly, so that this will still work, but you can also chain methods together:

class Counter
  def initialize
    @count = 0
  end

  def inc
    @count +=1
    self
  end

  def dec
    @count -=1
    self
  end

  def to_s
    @count.to_s
  end
end

c = Counter.new
c.inc
c.dec
c.inc
c.inc
c.inc

puts c
# => "3"

x = Counter.new
x.inc.dec.inc.inc.inc.inc.dec

puts x
# => "3"
Tagged: | ruby | self |
Cover Image: Andre Mouton, via Unsplash