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?
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_accessorworks: What is
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
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.
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
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.