Guitars

Adventures in Ruby - Self vs @

Nov 29, 2019

Ever had that moment when someone asks you a deceptively simple question which, the more you think about it, gets harder and harder to answer? So I had that last night with this seemingly innocuous bit of code in my Rails app:

class Order < ApplicationRecord

  def process!
      self.processed = true
      self.processed_at = Time.now.utc
  end

end

That’s a lot of selfs. This method, as we can see, is in the Order class, which inherits from ApplicationRecord, and it’s updating some values on the order instance of our class.

So the question that came to mind was why do we need self here? As we’re in our class why can’t we just refer to the instance variables like so:

class Order < ApplicationRecord

  def process!
      @processed = true
      @processed_at = Time.now.utc
  end

end

This caused some head scratches as all my tests started to fail. After lots of playing around I realised the subtle (but very important) difference in the change:

  1. “@instance_variable = x” is assigning the @instance_variable with the value x
  2. “self.instance_variable = x” is calling the method ‘instance_variable=’ and passing in x as the argument

In straight-up Ruby, self.instance_variable is normally something that you need to use if you’re triggering a getter or a setter method which will be returning the instance variable under the hood like so:

  def name
    @name
  end

  def name=(n)
    @name = n
  end

If retrieving or changing the variable is the only thing we we expect to do then you often see this little piece of ruby magic being used:

  attr_accessor :name

However we could be doing other things as well in that getter or setter method like logging that the instance variable is updated or udating a value in the database. This detail is very important when it comes to Active Record models, as our class instances have the following:

  1. Variables which map to values in the database for that instance
  2. Variables which are just held in memory on that instance

For database backed fields we need to be careful that rather than assigning to an instance variable of that name that we use the setter method that Rails helpfully sets up for each of the database backed fields otherwise we will see some unexpected behaviour…

      @processed = true

This will not actually alter the database backed field when we come to call save on our Active Record model. That’s because behind the scenes Active Record has no knowledge of an instance variable called ‘processed’.

This is why the setter methods are so important - they allow Active Record to do it’s thing in it’s own way without us having to know the details of exactly how it’s doing it like so:

      self.processed = true

We don’t need to know what Active Record is doing behind the scenes, but we can trust that our database-backed field will be updated and any other actions that Active Record wants to handle will happen too.

So self.instance_variable= is a method invocation, @instance_variable= is an assignment to a variable in memory that may or may not be Active Record related! This also has implications for straight up ruby applications as well

  • if you’re wanting to do more than just assign a value to a variable with a getter or setter then you would need your
  • own methods that you would invoke with self that callers of the method can trust to do your thing.