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?