I spent about 30 minutes today trying to determine if I had been misunderstanding basic Ruby syntax for more than a year, or if I had just discovered an undocumented bug in the language. The answer was neither, of course.
Ruby variable naming is straightforward. Class instance variables begin with @
, as in @user_name
. Local variables are unadorned - just user_name
. So, when I came across some code that a co-worker had written that did not follow this convention, and he said that it was working fine, I was initially confused. Here’s an extremely simplified example:
1 class VarTest
2 attr_accessor :instance_var
3
4 def print_it
5 puts "The instance var is #{instance_var}"
6 end
7
8 end
9
10 vt = VarTest.new
11 vt.instance_var = "fred"
12 vt.print_it
13
This code should be spitting out something about the invalid use of the instance variable instance_var
, right? That should be @instance_var
in that method (line 5). But there it was, working just fine.
So I experimented a bit.
1 class VarTest
2 @instance_var = "some initial value"
3
4 def print_it
5 puts "The instance var is #{instance_var}"
6 end
7
8 end
9
10 vt = VarTest.new
11 vt.print_it
12
13
14
15
Now that’s what I had expected the first time. After more moments of confusion than I really care to admit, the extremely obvious answer came to me. In the first example, instance_var
was the accessor method, NOT the variable. Duh. In the second example, where the error was thrown, instance_var
was a local variable, since no accessor had been declared. Hence, the error. Again, duh.
It does bring up an interesting point, however. When I am accessing an instance variable from within a method, I use @varname
, never just varname
. But, I also frequently create accessor methods in Rails models that manipulate the data in some way. This leads to the possibility of some interesting bugs. Here’s a pretty contrived example.
1 class User
2
3 def lastname=(val)
4 @lastname = val
5 end
6
7 def lastname
8 "Mr. " + @lastname
9 end
10
11 def print_name
12 puts "print_name: #{lastname}"
13 end
14
15 def print_name2
16 puts "print_name2: #{@lastname}"
17 end
18
19 end
20
21 vt = User.new
22 vt.lastname = "Kruger"
23 vt.print_name
24 vt.print_name2
25
26
27
28
This is fine, and correct of course, unless what I really wanted was “Mr. Kruger” in both cases.
The lesson here is…
The only real lesson is to be aware of the difference between using @ and not in class methods. Best practice is probably to consistently use @, in part simply because it helps differentiate between local and instance variables. In cases where the accessor really needs to be used, as when the accessor function is performing some sort of processing, its best to use self.varname
to make it clear that a function is being called.