Focusing autotest

August 24th, 2008

The usual autotest workflow goes something like this:

  • Edit and save
  • Autotest runs associated specs
  • Are there failures? Fix and start over
  • Autotest runs the entire suite

Sometimes though, you want autotest to just ignore most of your specs and focus on a few specs.

Last week, while Kevin, Rick, and Yossef (OG) were here, they shared an autotest tweak that does exactly that.

The tweak allows you to specify a regular expression to limit the files which autotest watches; for example, to autotest only files matching *user*.rb you would (atest is an alias):

% atest user

Here is the (lightly modified) code to do this; in your .autotest file add:

if ENV['AUTOTEST'] and not ENV['AUTOTEST'].empty?
  only_these_files_re = Regexp.new(ENV['AUTOTEST'])

  Autotest.send(:alias_method, :real_find_files, :find_files)
  Autotest.send(:define_method, :find_files) do |*args|
    real_find_files.reject do |k, v|
      !only_these_files_re.match(k)
    end
  end
end

And in your .bash_profile add:

# Autotest
  function fn_autotest() {
    AUTOTEST=$1 autotest
  }
  alias atest='fn_autotest'

Now use the alias to invoke autotest. For standard autotest behavior:

% atest

To limit what autotest is watching, pass a regular expression (which can be a simple string):

% atest user.*html

Voodoo send

July 12th, 2008

Recently when looking through some plugin’s code, we found the following line:

self.send(:include, Foo)

This seems to be an acceptable variant of the commonly used

ActiveRecord::Base.send(:include, Foo)

As you may have surmised, it is not an acceptable variant. This is a Voodoo programming smell, as it indicates a misunderstanding of exactly what the semantics of send are. There is also a clearer, more intention revealing, and identical1 alternative:

include Foo

or with the omitted parenthesis:

include(Foo)

This of course, leads us to why ActiveRecord::Base.send(:include, Foo) is acceptable. The include method is private, so the most elegant way to invoke it on another object is by using send; this is possible because send ignores access control allowing us to invoke include directly.

1 Almost, the difference is that send is in the call stack.

include and extend

April 6th, 2008

include and extend are commonly used to add methods to Ruby classes, and sometimes, why we use one or the other is not clear. Both do similar operations, but they are not equivalent. extend is more parsimonious adding only methods, includealso includes constants and module variables.

A simple guideline is to include instance methods and to extend class methods.

Here is an example of a class with an instance method and a class method. We have reopened the class so that we can show the differences from the original empty class:

def list_diff(label, list1, list2)
  puts "#{label}: #{(list1 - list2).sort.join ' '}"
end

class One
end

methods_from_new = One.new.methods
methods_from_class = One.methods

class One
  def instance_method
  end

  def One.class_method
  end
end

list_diff "Instance One.new diff", One.new.methods, methods_from_new
list_diff "Class One diff", One.methods, methods_from_class

when run shows the expected differences:

Instance One.new diff: instance_method
Class One diff: class_method

Now lets create and include a module:

module ModuleTwo
  CONSTANT_TWO = "two"
  def module_method
  end
end

class One
  include ModuleTwo
end

list_diff "Instance One.new diff", One.new.methods, methods_from_new
list_diff "Class One diff", One.methods, methods_from_class
puts "has constant? #{One.constants.sort.join ' '}"

when run shows that module_method and CONSTANT_TWO are now available:

Instance One.new diff: instance_method module_method
Class One diff: class_method
has constant? CONSTANT_TWO

And if we extend another module:

module ModuleThree
  CONSTANT_THREE = 'three'
  def another_module_method
  end
end

class One
  extend ModuleThree
end

list_diff "Instance One.new diff", One.new.methods, methods_from_new
list_diff "Class One diff", One.methods, methods_from_class
puts "has constant? #{One.constants.sort.join ' '}"

When run, shows that another_module_method is now available as a class method, but CONSTANT_THREE is not1:

Instance One.new diff: instance_method module_method
Class One diff: another_module_method class_method
has constant? CONSTANT_TWO

Finally, we can use include instead of extend to include class methods:

module ModuleFour
  CONSTANT_FOUR = 'four'
  def yet_another_module_method
  end
end

class One
  class << self
    include ModuleFour
  end
end

list_diff "Instance One.new diff", One.new.methods, methods_from_new
list_diff "Class One diff", One.methods, methods_from_class
puts "has constant? #{One.constants.sort.join ' '}"
When run, shows that yet_another_module_method is available as a class method, but CONSTANT_FOUR is not2
Instance One.new diff: instance_method module_method
Class One diff: another_module_method class_method yet_another_module_method
has constant? CONSTANT_TWO

1 Remember, include does more3 than extend.

2 For more details, check Programming Ruby: The Pragmatic Programmer’s Guide .

3 Bonus!

module ModuleFive
  CONSTANT_FIVE = 'five'
  def module_five
  end
end

one = One.new
one.extend ModuleFive

list_diff "Instance extends module", one.methods, methods_from_new
puts "has constant? #{one.class.constants.sort.join ' '}"

Instance extends module: instance_method module_five module_method
has constant? CONSTANT_TWO

define_method

March 23rd, 2008

While we were waiting for March 20th RubyJax meeting to get started, Steve Bristol entertained us with a Ruby quiz. One of the questions was particularly interesting as it had many possible answers. I will paraphrase the question as:

“How many ways are there to add a method to an existing class in Ruby?”

I have cataloged the answers that were tossed out as well as a few I just added.

1 . Open the class and add a new method

class String
  def method_one
    puts 'method 1'
  end
end

"abcde".method_one

2 . Create a singleton method on an instance

s = "12345"

def s.method_two
  puts 'method 2'
end

s.method_two

3 . Use Kernel#method_missing

class String
  def method_missing(name, *args)
    super unless name == :method_three
    puts 'method 3'
  end
end

"abcde".method_three

4 . Use Module#define_method

class String
  def create_method(name, &block)
    self.class.send(:define_method, name, block)
  end
end

"abcde".create_method(:method_four) { puts 'method 4' }
"12345".method_four

5 . A variation of #1 : include a module in the class

module ExtraMethods
  def method_five
    puts 'method 5'
  end
end

class String
  include ExtraMethods
end

"abcde".method_five

6 . A variation of #4 : use Object#instance_eval

String.instance_eval("define_method(:method_six) { puts 'method 6' }")

"abcde".method_six

7 . We could use Kernel#eval to execute any of the above as well; here we will emulate instance_eval with a binding

class String
  def self.get_binding
    binding
  end
end

eval("define_method(:method_seven) { puts 'method 7' }", String.get_binding)

"abcde".method_seven

Any others?