When learning Ruby, one of the interesting language features that I came across was the calling of methods within class definitions. When a class has finished loading, the method calls that exist within the class are executed, one after another. The purpose of these method calls is generally to make modifications to the class and change its functionality, although this isn’t necessarily always the case. One of the most common examples of this feature is the attr_accessor method. The ability to modify a class via code execution is very powerful, although somewhat surprising to the new Ruby developer.

Let’s take a look at how this works.

In the following code, we see hoe attr_accessor is typically used.

#!/usr/bin/env ruby class ExampleA attr_accessor :var def initialize(val) self.var = val end end a = ExampleA.new(2) puts "a = " + a.inspect # Prints... # a = # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/usr/bin/env ruby class ExampleA attr_accessor : var def initialize ( val ) self . var = val end end a = ExampleA . new ( 2 ) puts "a = " + a . inspect # Prints... # a = #

There are several interesting facts about the class ExampleA that should not go unnoticed. First, in the initialize method there is a reference to a method “var=” that doesn’t exist until the attr_accessor method has completed executing. However, the attr_accessor method hasn’t executed when the Ruby interpreter first parses and loads the class definition. This implies that the Ruby interpreter does not check that the class actually has a method named “var=”. There could be calls to fictitious method names on fictitious classes and the Ruby parser would not raise any sort of error. It is only during code execution, and specifically at the line where the call is made, that there is a check to ensure that an object can actually receive the method call.

To verify that the calls to the methods listed in the class definition are made after the class is fully parsed, the following small adjustments were made to the code from above.

#!/usr/bin/env ruby class ExampleB def initialize(val) self.var = val end attr_accessor :var end b = ExampleB.new(3) puts "b = " + b.inspect # Prints... # b = # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/usr/bin/env ruby class ExampleB def initialize ( val ) self . var = val end attr_accessor : var end b = ExampleB . new ( 3 ) puts "b = " + b . inspect # Prints... # b = #

As we can see, the code contained within the attr_accessor method call is indeed executed after the full class definition is parsed by Ruby.

To further demonstrate that absolutely no checks are performed when the class is loaded, a call to the new method on a completely made up class was performed in the method “example_method” below.

#!/usr/bin/env ruby class ExampleC attr_accessor :var def initialize(val) self.var = val end def example_method NoneExistentClass.new end end c = ExampleB.new(4) puts "c = " + c.inspect # Prints... # c = # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #!/usr/bin/env ruby class ExampleC attr_accessor : var def initialize ( val ) self . var = val end def example _ method NoneExistentClass . new end end c = ExampleB . new ( 4 ) puts "c = " + c . inspect # Prints... # c = #

As you can see, even though the class is used, no error is raised because they section of code is never actually executed.

In summary, these are very powerful features. However, it should be very obvious now that it is paramount that all applications have tests and that all the tests exercise every line of code. This is in stark contrast to Java where the compiler performs a large number of checks concerning the correctness of the code during compilation. It is not the intent to advocate Java over Ruby. There are many differences between the two and any serious discussion of the merits of one versus the other should include them all. As the old saying goes, “With great power, come great responsibility.” I suppose it could be said that the training wheels are now off.

Here are a few additional resources on this topic: