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 howattr_accessor
works: What isattr_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.