Joe Yates' Blog

Programming and DevOps

Ruby Bareword Assignment and Method Calls With Implicit Self

Problem

If I do this:

Problem - problem.rb
1
2
puts foo
foo = 3

there is always the doubt whether I’m accessing a local variable, or calling methods foo and foo=.

TL;DR

When you want to call an instance’s own methods, use self:

TL;DR - tldr.rb
1
2
self.foo             # Calls foo
self.foo = 'bar'     # Calls foo=

Example 1

Example 1 - example_1.rb
1
2
3
4
5
6
7
8
9
def example1
 'example1 method'
end

example1 #=> "example1 method"

example1 = 'assigned value'

example1 #=> "assigned value"

Here, we define a method, and then make an assignment. As we assign to a bareword, Ruby creates a new local variable.

As soon as a value is assigned to the local variable, the method no longer gets called.

Example 2

But, what if we also have an assignment method?

Example 2 - example_2.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
def example2
 'example2 method'
end

def example2=(value)
 puts "example2= called" # (this never gets called)
end

example2 #=> "example2 method"

example2 = 'assigned value'

example2 #=> "assigned value"

Adding the method example2= does not change things. When we assign to a bareword, Ruby takes it as assignment to a local variable.

Example with a Class

x - x.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Foo

 attr_accessor :bar

 def initialize
   @bar = 42
 end

 def method1
   puts bar
 end

 def method2
   bar = 99
   puts bar
 end

 def method3
   bar = 99
   puts self.bar
 end

end

foo = Foo.new

foo.bar  #=> 42
foo.method1 #=> 42
foo.method2 #=> 99
foo.method3 #=> 42

method2 is the problem case. bar is assigned to, creating a local variable, so subsequent calls to bar return 99. method3 disambiguates by explicitly calling the bar method on self.

The Cause

There are two things going on here:

  1. bareword assignment creates local variables,
  2. local variables mask methods of the same name.

Refactoring Might Break Code

One solution is to use self.method only in cases where local variables mask methods. The problem with this approach is that code may be altered, introducing local variables, and so altering the behaviour of following code:

Original Code

x - x.rb
1
2
3
4
5
6
7
8
9
10
11
class Foo
 attr_accessor :bar

 def baz
   puts bar
 end
end

foo = Foo.new
foo.bar = 42
foo.baz #=> 42

Modified Code

x - x.rb
1
2
3
4
5
6
7
8
9
10
11
12
class Foo
  attr_accessor :bar

  def baz
    bar = 99 # <= variable assignment introduced
    puts bar
  end
end

foo = Foo.new
foo.bar = 42
foo.baz #=> 99

Solution

The best solution is to always call instance methods on self.