Rspec Without db:test:prepare

By Phil Matarese on April 23rd, 2008

Tagged with: rails, rspec, rake

First Things First

At the Hotel MatareseOnce upon a time, after deploying an Rspec'd app for the first time, we wanted to run all the specs on our staging server to see that everything was running as expected.  Only, there was a problem with dependencies.

At the time we had a pretty gross, gem-dependency soup cooking up in our app.  We had rspec as a frozen gem and rspec_on_rails as a plugin, and this was causing (unconfirmed) load-order problems for rake.  After checking the rspec documentation again, we changed rspec from a gem to a plugin, which straightened everything out (I think).

The Basics

Basically, our current setup is to have piston (essential for plugin management), rcov (keep the coverage above 50% or the bus blows up), and ZenTest (because autotest is fun) installed locally as gems on all development machines - there is no reason for us to use these on our production or staging servers.  Within the app we have rspec_on_rails, rspec_autotest, and rails_rcov (which uses magic) installed in the plugins directory (under the watchful eye of piston.)  These are part of the app, not only because the rspec docs like it that way, but because we'd like to ensure all the tests pass after a deployment.  Everything is all nice and neat now.  Shouldn't have anymore problems.  Nope.  No sir.  No way.  Uh-uh.

The Problem

Gimmelwald, Population 130When running rake spec, the database can't be found.  But... we installed it... right? Of course, it's right there.  And... the permissions? They're good, too.  Uhh... Hey, did that say the "development" database can't be found?  What the...

Unbeknownst to us, the spec task had as a prerequisite db:test:prepare, which takes a schema_dump from the development database and uses it to create a test database.  This is something we don't need.  Unfortunately, there's no option or flag that you can use to disable this prerequisite.

The Solution

So this one got a little hairy.  The load order for rake tasks is rails, custom code in lib/tasks/*, and then rspec.  So, I couldn't drop the spec prerequisites with my custom code, because they didn't exist yet.  My first attempt to solve this involved wiping out db:test:prepare completely, no matter what.  I was a bit hesitant to do this, because it had nothing to do with what I was really trying to accomplish – I've got nothing against db:test:prepare, I just want it decoupled from rspec.  Here is our current solution which only wipes out db:test:prepare when the spec task is invoked.  Not only does this solve the problem of testing on a staging server, but it also speeds up testing in our local development environments.  Enjoy.

 1  # bock_db_test_prepare.rb
2
3 # without this, you could never
4 # remove elements of a task
5 class Rake::Task
6 def clear_it
7 @actions.clear
8 @prerequisites.clear
9 end
10 end
11
12 # set up the blocking as a rake task,
13 # so we can add it as a prereq to spec
14 namespace :ap do
15 namespace :spec do
16 task :skip_db_test_prepare do
17 Rake::Task['db:test:prepare'].clear_it
18 end
19 end
20 end
21
22 # block it!
23 task :spec => 'ap:spec:skip_db_test_prepare'

The final line works even though the spec task doesn't exist yet. Essentially, our custom code creates an empty spec task with our single prerequisite. When rspec loads, the real task and prerequisites will be appended after ours. Success.

Dreaming in Code

By Phil Matarese on April 21st, 2008

Tagged with: ruby, frivolity

I dreamed about Ruby again last night.  I was looking at the way code looked, and I realized that the results of executing the code looked like the code that was being executed.  It went on recursively with code that looked like code that looked like code...  There was even drop-shadows and reflection on the code - it was beautiful.

I'm not sure if this is a result of reading Why's (Poignant) Guide to Ruby or if I'm just wandering down the same path he did.  It doesn't really matter though, in fact, the only thing that matters now is creating Ascii Art numbers.  This is important, I must do this...

  1  class NumberWang
2 ALL = %w{ zero one two three four five six seven eight
nine
}
3
4 # Omphaloskepsis
5 def self.reveal_method(method_name = nil)
6 @@source ||= File.open(__FILE__).read
7
8 source = ''
9
10 if method_name
11 in_method = false
12 @@source.each do |s|
13 next unless in_method || s.strip == "def
self.#{method_name}
"
14 break if s.strip == "end"
15 in_method = true
16
17 source << s
18 end
19 else
20 ALL.each{|a| reveal_method(a)}
21 end
22
23 puts source.chop #drop the linebreak
24 puts
25 end
26
27 def self.all
28 ALL.map{|a| self.send(a.to_sym)}
29 end
30
31 def self.zero
32 ----------1*1-------------
33 ---------1 - 1------------
34 ---------1 * 1------------
35 ---------1 - 1------------
36 ----------1*1------------1
37 end
38
39 def self.one
40 ------------1--------------
41 -----------11--------------
42 ------------1--------------
43 ------------1--------------
44 ----------11-1-------------1
45 end
46
47 def self.two
48 ----------1-1--------------
49 -------------1-------------
50 -----------11--------------
51 ----------1----------------
52 ----------1-11------------1
53 end
54
55 def self.three
56 ----------1--1------------
57 --------------1-----------
58 -----------1-1------------
59 --------------1-----------
60 ----------1--1-----------1
61 end
62
63 def self.four
64 ----------1--1------------
65 ----------1--1------------
66 ----------1-1-1-----------
67 -------------1------------
68 -------------1------------1
69 end
70
71 def self.five
72 ----------11-1-------------
73 ----------1----------------
74 -----------1-1-------------
75 --------------1------------
76 -----------1-1------------1
77 end
78
79 def self.six
80 ----------11*1-------------
81 ---------1-----------------
82 ---------1*-1--------------
83 ---------1 - 1-------------
84 ----------1-1-------------1
85 end
86
87 def self.seven
88 ----------11-1-------------
89 -------------1-------------
90 ------------1--------------
91 -----------1---------------
92 ----------1---------------1
93 end
94
95 def self.eight
96 ----------11*1------------
97 ---------1 - 1------------
98 ----------1-1-------------
99 ---------1 - 1------------
100 ----------1-1-------------1
101 end
102
103 def self.nine
104 -----------1*1------------
105 ----------1 - 1-----------
106 -----------1*11-----------
107 -------------1------------
108 -----------1-------------1
109 end
110 end

Ok, that was a strange diversion.  Sorry if I've upset anyone.  I'll try to stay a little more focused from now on.

Wrapping Up

There's some fun stuff in there (apart from the numbers) that might be worth poking around in.  Specifically, the reveal_method, which I think is too much of a hack to be useful but was fun to play with nonetheless.  Now, load this thing in irb and have some fun exploring.

curl 'http://rubyforge.org/snippet/download.php?type=snippet&id=337' > number_wang.rb
irb -r number_wang.rb

Try something like this, NumberWang.reveal_method :two to get you started.

Ultimate One-Liner

By Phil Matarese on April 18th, 2008

Tagged with: dilbert, one-liner

Update - This post has fallen flat, now that you can subscribe to daily dilberts for free.

I love one-liners, and here's the command I use to read Dilbert everyday:

curl -s http://dilbert.com | grep -o -m1 -E '\/comics\/dilbert\/archive\/images\/dilbert[0-9]*\.(gif|jpg|jpeg)' | sed "s/\(.*\)/--url http:\/\/dilbert.com\1/" | curl -s --config - | open -f -a Preview.app

From now on, I expect you to finish all of your projects in one hour.Unlike other publications, I don't read dilbert.com for the articles.  I also don't read it for the advertisements or any of the other junk – I just want to see today's comic.  Only, the file for the daily comic has randomness in the filename which makes it complicated to automatically scrape the comic (I guess they want to avoid people doing what I'm doing... silly people).  Nothing is too complicated for a one-liner, though.

I aliased this script in my .bash_login file, so a fresh Dilbert is always just a few keystrokes away.  (Sorry, the Preview.app part makes it only work on Mac.  I guess you'd have to pipe it into a different app for other systems.)

Autotest with Growl. And iChat?

By Phil Matarese on April 18th, 2008

Tagged with: rails, autotest, growl, ichat

Autotest is Great

It adds another layer of satisfaction onto test-driven development.  The ideal setup for TDD is to have two monitors – one for developing and another for your autotest terminal (along with server and log-tailing terminals.)  Without a second monitor, though, your autotest results need a better way of notifying you.  Many people have solved this on their Macs by using Growl's growlnotify in a .autotest file (goes in your project root or home directory.)  It could be as simple as requiring ZenTest's own growl interface:

1  require 'autotest/growl'

But, for more control, it's sometimes better to expose the guts and operate a little bit like this:

 1  module Autotest::Growl
2 PICTURE_ROOT = '/Users/philip/Pictures/Autotest/'
3
4 def self.growl msg, status
5 pri = {:fail=>2,:pending=>-1,:ok=>-2}[status]
6 title =
{:fail=>'Failed',:pending=>'Pending',:ok=>'Passed'}[
status]
7
8 msg.gsub!("\n","\\\n\n")
9 system "growlnotify -n autotest --image
\"#{PICTURE_ROOT}rails_#{status.to_s}.png\" -p
#{pri} -m \"#{msg}\" \"Tests #{title}\"
"
10 end
11
12 def self.failure_details results
13 details = ''
14 results.each do |line|
15 line = line[/[^\/]*\.rb\:[0-9]*\:/]
16 details += "#{line}\n" if line
17 end
18 return details
19 end
20
21 def self.pending_details results
22 results.slice!(0,3)
23 results.slice!(-4,4)
24 details = ''
25 results.each do |line|
26 details += "#{line[/[^\(]*/]}\n"
27 end
28 return details
29 end
30
31 Autotest.add_hook :ran_command do |at|
32 output =
at.results.last[/[0-9]*\sexample(s)*,\s[0-9]*\sfailu
re(s)*(,\s[0-9]*\spending)*
/]
33 if output =~ /\s0\sfailure(s)*/
34 if output =~ /\spending*/
35 growl "#{pending_details(at.results)}\n#{output}",
:pending
36 else
37 growl "#{output}", :ok
38 end
39 else
40 growl "#{failure_details(at.results)}\n#{output}",
:fail
41 end
42 end
43 end

Growl SettingsThat script is based on internautdesign.com's, and uses their images (rails_fail.png, rails_ok.png).  I added a few updates, like better formatting in the Growl notification and implementation of 'pending' status (rails_pending.png).  It took me a few tries to get the line-breaks right in the notification, until I realized that I needed double backslashes – one for the shell, and one for the \n.  I also decided to grab a little more than the last line of results for pending and fail statuses, which took a few ugly lines of text hacking.  Also, you should set your Growl colors to something like the picture on the right.

That Was Nice

That was nice, and it solves the single-screen-notification problem pretty well, but now it's time for something new.

Bragging about my ruby-centric iTunes playlist through iChat.Test failures publicized through iChatAnd, since I was looking for a reason to try out RubyOSA, I wrote a .autotest script that notifies my team (and my friends (and my mom)) whenever a test passes (or fails (or is pending)) via iChat's status and buddy icon.  Too fun!  Maybe this isn't the most practical way to do TDD, but it's fun code to play with.  You could even use RubyOSA to lower the volume on iTunes whenever there are too many errors – maybe.

 1  require 'rbosa'
2
3 module Autotest::Chat
4 PICTURE_ROOT = '/Users/philip/Pictures/Autotest/'
5
6 def self.chat test_stats, status
7 @@chat ||= OSA.app('iChat')
8 begin
9 @@chat.status_message = test_stats
10 @@chat.status = status==:fail ? 'away' : 'aval'
11 @@chat.image =
File.read("#{PICTURE_ROOT}rails_#{status.to_s}.png
")
12 rescue
13 #ignore it, iChat is prolly just turned off
14 end
15 end
16
17 Autotest.add_hook :ran_command do |at|
18 output =
at.results.last[/[0-9]*\sexample(s)*,\s[0-9]*\sfailu
re(s)*(,\s[0-9]*\spending)*
/]
19 if output =~ /\s0\sfailure(s)*/
20 if output =~ /\spending*/
21 chat output, :pending
22 else
23 chat output, :ok
24 end
25 else
26 chat output, :fail
27 end
28 end
29
30 Autotest.add_hook :quit do |at|
31 chat "Chillin'", :chillin
32 end
33 end

Autotest has some usefel hooks like 'quit', so I can let people now that I'm chillin' again (rails_chillin.png) after a successful round of testing.  Oh, and you can get the chat code from RubyForge with the following command.

curl 'http://rubyforge.org/snippet/download.php?type=snippet&id=329'

Today

As I wrote this post, I started looking deep into the bowels of ZenTest to make sure I was setting up my autotest scripts correctly.  What I found was heaps of code already designed to use Growl, and iChat, and KDE Notify, Snarl, Heckle, and some other stuff.  What I didn't find was much documentation about the features – you pretty much have to dig into the source.  And even with the source, some of the code's usage is mysterious – like utilizing generic autotest hooks by adding require 'autotest/growl' or require 'autotest/shame' to an empty .autotest file.

Now Back To the RUBY-CENTRIC Music

More iTunes playlist nonsense

Using Foxy Fixtures for Polymorphic Associations in Rails 2

By Scott Roth on April 5th, 2008

Tagged with: rails, rails 2.x, fixtures, polymorphic associations

As we all know, the Rathole plugin was sucked into Rails core with the release of Rails 2 and now using fixtures has become, um, foxier.  One use case that wasn't immediately clear to me was how to specify polymorphic relationships using the new and improved syntax.  It turns out that this is straightforward.

Let's say your application captures contact information for several models.  Our hypothetical application is a brand new social network for people in the music industry.  (Quick, let's pitch it to VCs!)  Each application user would have contact information and, since they are in the music industry, they belong to a Record Label.  Record Labels also have contact information stored for them.  So we get this:

 1  class Contact < ActiveRecord::Base
2 belongs_to :contactable, :polymorphic => true
3 end
4
5 class User < ActiveRecord::Base
6 has_one :contact, :as => :contactable
7 end
8
9 class Label < ActiveRecord::Base
10 has_one :contact, :as => :contactable
11 end

Now to set up the fixtures.  All you need to do is specify the contactable name and contactable_type for each contact and we are done.

 1  # User
2 music_industry_person:
3 username: person1
4 password: secret
5
6 # Label
7 label1:
8 name = Some Indie Label
9
10 # Contact
11 fred:
12 contactable: music_industry_person
13 contactable_type: User
14 first_name: Fred
15
16 sam:
17 contactable: label1
18 contactable_type: Label
19 first_name: Sam

As you can see, a piece of cake!

Update:  Phil knew a one liner way to do this:

1  sam:
2 contactable: label1 (Label)
3 first_name: Sam