Can You Spot the Error?

Can You Spot the Error?

April 23, 2023

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?

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 nameWhy 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.

Sign up for my newsletter

Let's learn to become better developers.

Comments (1)

N
Neener54

I believe that the idiomatic way to do this would be: ''' 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 rails_blog = Blog.new rails_blog.publish rails_blog.preview ''' attr_accessor creates instance variables and a getter and setter for the attribute. So @name is now available as an instance variable. self.name is using the getter for the instance variable which is roughly the same. But I think in most codebases you'll see instance variables being used and the attr_accessor is a way to shortcut creating external access to it.

Sign in to leave a comment.