…or: how to put class scope to use
The class scope, i.e. the space inside class declaration, but outside method definitions, is a no man’s land in many languages (particularly in Java). By this I mean that you can define methods, variables, and other classes there and you can even execute some code (in variable initializations) but nothing more. Fortunately, in Ruby empty space between method definitions can be filled with all kinds of useful, executable code. Let’s check a few examples.
Case study #1: Class level DSL’s
Perhaps the best example of class scope use are various class level DSL’s. All the belongs_to, has_many or validates_something directives that Rails developers use everyday are not that mysterious or strange as Java annotations. They are just class method calls that mutate our class in some way. These class methods are defined somewhere in base classes. But ActiveRecord’s DSL is not the only popular DSL in Ruby. Ruby itself uses similar directives, e.g. attr_reader etc.
In Ruby, such DSL’s are easy to create. Let’s see how easily we can create directive similar to said attr_reader, but even better. Our directive will generate reader methods and also initialize method with initialization code for the attributes. All the fancy stuff like default parameter values or error checking is left as an exercise for the reader.
class Test
def self.attr_with_init *args
args = [args].flatten.each do |a|
define_method a do
instance_variable_get "@#{a}"
end
end
class_eval <<-END
def initialize(#{args.join','})
#{args.map{|a|"@#{a} = #{a}"}.join"\n"}
end
END
end
attr_with_init :foo, :bar
end
t = Test.new 8, 'x'
puts t.foo
puts t.bar
puts t.instance_variables
This code should print:
8 x @bar @foo
In Java, you can use annotations to achieve similar goals, but the effort you will have to put into it will probably make your code a humiliating exercise in satisfying the compiler, but far from fun.
Case study #2: similar methods
Let’s say we have a User model class with several fields that can be filled by the user. Those fields’ contents should be checked against some black list to prevent the user from using offensive words in their profile info. We want to test if the validations for all fields are in place. We can do that this way:
def test_blacklisted_content
%w[forename surname address about_me tags].each do |f|
u = User.new f => 'your_favorite_curse_here'
assert !u.valid?
assert_not_nil(u.errors.on(f))
end
end
This works but in case of failure we don’t have information about which field failed. And one failure means that all following fields are not checked by this test. We would like to have a separate test method for each field. But is this possible without copy’n'paste? Of course, we just need to move the loop outside the method. Now this may sound like an abomination for a Java-guy, but this is Ruby, remember? Designed to make programming fun!
%w[forename surname address about_me tags].each do |f|
define_method "test_blacklisted_content_#{f}" do
u = User.new f => 'your_favorite_curse_here'
assert !u.valid?
assert_not_nil(u.errors.on(f))
end
end
We only changed first 2 lines, but now we have a loop that defines five independent test methods. If three of our five fields lack proper validations, we now get three failing tests with appropriate names. The code is not much harder to understand that the previous version — any noob should grok it after reading define_method documentation.


September 3rd, 2008 at 16:25:46
[...] Neal used an example of generating test methods in a loop that was very similar in idea to my old Looping in a no man’s land [...]