Can you spot the error in this Ruby code?

Can You Spot the Error?

I recently encountered this error and in the process of debugging, learned some useful stuff about `self` and how assignment works in Ruby. See if you can figure it out yourself. Can you spot the error in this simple Ruby snippet?

2 min read

I know, I know... The name property is public and I can assign it directly using the object. This example is vastly simplified, just to make a point. Now, give it a try again. What's wrong here?

class Blog
  attr_accessor :name

  def publish
    # some other code...
    name = 'Rails Blog'
    # some other code...
  end

  def preview
    puts "Blog Name: " + name
  end
end

I also asked this question on LinkedIn, and many people mentioned that it's missing a constructor, or I should assign the value to an instance variable, i.e. @name. However, you don't need a constructor here, and the attr_accessor method will create an instance variable called @name behind the scenes.

See this excellent, in-depth answer on StackOverflow which explains how attr_accessor works: What is attr_accessor in Ruby?

So these are not the real issues.

What's the Problem?

To see the problem in action, let’s create a new Blog object and publish it, which should assign its name.

rails_blog = Blog.new

rails_blog.publish

So far, so good. Now try to access the name with the preview method.

rails_blog.preview

scratch.rb:11:in `+': no implicit conversion of nil into String (TypeError)

How’s that possible? Didn’t we just assign the value to name? Why did the name variable not retain its value?

What's the Solution?

The answer is in the way Ruby handles assignments inside objects. Let’s review the  publish method again, which tries to set the name.

def publish
  # some other code...
  name = 'Rails Blog'
  # some other code...
end

In the above code, name is a local variable, instead of the setter method created by attr_accessor. Hence, its scope is limited to the publish method and no one outside it can access it. The value we assign to this local variable is lost as soon as the control exits the method.

If we were using a more traditional language like C# or JavaScript, the difference would've been clear, as we'd have used brackets to make a method call. Since Ruby is more forgiving, it thinks we're creating a new local variable and assigns the value to it.

To access the setter method, we need to use self, like this:

def publish
  # some other code...
  self.name = 'Rails Blog'
  # some other code...
end

Using self ensures that Ruby calls Blog#name method, instead of creating a local variable. Typically, self is not needed if you're simply calling a method in the class, Ruby will implicitly call that method on the current object. However, in this example, self is required.

Here’s the complete working example. If I run this code, everything works as expected.

class Blog
  attr_accessor :name

  def publish
    # some other code...
    self.name = 'Rails Blog'
    # some other code...
  end

  def preview
    puts "Blog Name: " + name
  end
end

rails_blog = Blog.new
rails_blog.publish

rails_blog.preview  # Blog Name: Rails Blog

Hope this was useful, and you learned something new.