Poor Men Story Runner

By Renzo Borgatti on October 30th, 2008

Tagged with: rspec, rails, storyrunner

Say that for some reasons like for example time constraints on the project, lack of customer collaboration or lack of business analysts you can’t maintain a full fledged automated story base. In that case one option could be to maintain the story runner format to describe the feature to implement but not try to make that executable. I know, I know, what a waste for such a great opportunity. But I’m talking about a desperate case.

As funky as it sounds, you can try to implement the plain text story in rspec as controller specs with integrated view. For example:

 1  describe BlogsController, "blog details page" do
2 integrate_views
3
4 before(:each) do
5 Given "the user is logged in"
6 When "I click on a blog post"
7 end
8
9 it { Then "the blog detail view is displayed" }
10 it { And "the full text of the blog is shown" }
11 it { And "the comment list section is shown" }
12
13 end
14
15 [:Given, :When, :Then, :And].each do |method_name|
16 self.class.send :define_method, method_name do |what|
17 self.send(what.gsub(' ','_'))
18 end
19 end

Yes, very very not-readable. It just destroy all the effort to create stories in plain text. But it’s something a developer can read. The next step is to create a method for each of the descriptive texts, replacing blanks with underscores:

 1  def the_user_is_logged_in
2 @user = login
3 end
4
5 def I_click_on_a_blog_post
6 @blog = stub_model(Blog,
7 :title => "Random thoughts.",
8 :comments => comments,
9 :text => "MIDWAY upon the journey of our life")
10 Blog.should_receive(:find_by_id).with(1).and_return(@blog)
11 get :show, :id => 1
12 end
13
14 def the_blog_detail_view_is_displayed
15 response.should have_tag("div#blog") do
16 with_tag("strong", @blog.title)
17 end
18 end
19
20 def the_full_text_of_the_blog_is_shown
21 response.should have_tag("div#blog") do
22 with_tag("div#text", @blog.text)
23 end
24 end
25
26 def the_comment_list_section_is_shown
27 response.should have_tag("div#blog") do
28 with_tag("div#comments") do
29 with_tag("div#comment", 5)
30 end
31 end
32 end

As you probably guessed already, this is the first scenario for a blog section on some web page. You can click on the “more” link to read more and access comments to the blog post. The plan is to implement the other scenarios in the same spec file changing the describe block to change the target controller. The specs are just scratching the surface of the view, then you’ll need to change spec to implement other part of the system.

Another interesting thing is that for this to be called “integration” test, I shouldn’t have used any stub_model instruction. Correct. First step I want to create it fast, no database thinking. Second step I’ll remove all stubs and using some way of talking to the database (FixtureReplacement for example) I’ll be using real connections to the model and therefore to the database. At the end, a kind of acceptance testing.

I know what you’d like to ask now: is this really any better than just plain run the Story Runner itself? Not at all. It’s absolutely a bad practice to use RSpec like this, I don’t want to suggest in any way that this can substitute the story runner. But in case of emergency (read, fixed scope, fixed price, fixed time projects) where the customer is not helping writing stories, where there are no business analysts and everything should be done by developers, dropping the story runner is probably the less of all possible pains. You should also consider that if you are using Watir or Selenium on Rails connected to the story runner, the effort of maintaining such an environment sensible acceptance framework is big.

Specifically:

  • You don’t need the infrastructure to run stories which is usually a separated task from running specs
  • You don’t need to maintain steps either
  • You don’t need to attach the story runner binary to the continuous integration, stories will run as normal specs.
  • You are free to maintain story texts outside the project, on a wiki or online somewhere.
  • Coverage with rcov is just part of the package. For stories you can’t know how well are you covering that given aspect of the application. At least, I thought about it and I don’t know how to deal with the problem.


Well, hope you can forgive me for this post but find the idea useful for your project.

matchers_in_have_tag_blocks.should be(/with_tag/)

By Phil Matarese on October 15th, 2008

Tagged with: rails, rspec

The Setup

When writing test code for Rails apps, I prefer Rspec.  For me the spec definition syntax feels more natural.  Though, the other day I got tripped up by the Rspec equivalent of assert_select.  With standard rails tests, the assert_select method can be nested to help ensure properly structured html elements.

1  # This tests that both users show up in the yes_list.
2 # As opposed to the no_list I guess.
3 assert_select 'div#yes_list' do
4 assert_select 'div#user', /#{@user1}/
5 assert_select 'div#user', /#{@user2}/
6 end

Rspec uses have_tag as a wrapper to this method, but uses with_tag and without_tag to wrap the method when called inside the nesting.  The without_tag allows for simpler negative assertions, in case we wanted to test that user3 was not in the list for example.

1  # This also tests that both users show up in the yes_list.
2 response.should have_tag('div#yes_list') do
3 with_tag('div#user', {:text => /#{@user1}/})
4 with_tag('div#user', {:text => /#{@user2}/})
5 end

Once you know this, I think the spec code looks very clear.  Unfortunately, calling have_tag within a have_tag block won't raise an error.  There's no mechanism that will warn you that your inner have_tag is ineffective.  The results of the inner have_tag will simply be ignored.

The Pitfall

When I first wrote the above spec, I used have_tag and have_text instead of with_tag.  And to my naïve eyes, everything looked great.  The code ran and the tests passed.  Done and done.

 1  # This merely asserts the existence of a yes_list,
2 # no matter who's on the list.
3 # Here at the Rails Loft, we're more discerning than that.
4 response.should have_tag('div#yes_list') do
5 have_tag('div#user') do
6 have_text(/#{@user1}/)
7 end
8 have_tag('div#user') do
9 have_text(/#{@user2}/)
10 end
11 end

This was completely wrong.  The tests were passing, but they still passed when I removed @user1 from the yes_list one.  When used inside another have_tag block, a have_tag's assertions was completely ignored.  I think this had something to do with the inner have_tag setting up a new AssertSelect object that then got ignored by the outer AssertSelect object.  The reason for my misunderstanding doesn't really matter, though.  What matters is that I thought everything was fine; all the tests were passing after all.  Luckily, I fiddled with the code a bit before committing, and discovered that reversing an important condition in my application code didn't affect my test one bit.  Whoops.  This was a mistake that continuous integration and testing metrics could have never caught, which emphasized to me how important it is to be thorough and careful when writing tests.

The Lesson

Always, always, always make sure your tests fail before you make them pass.  Your tests are there to ensure proper functionality of application code, and this is the only way you can be certain your test code is doing what you expect.  This doesn't necessarily mean you always have to use test-first development, though you should certainly give TDD a try.  Making your tests fails could be as simple as toggling a conditional in your application code or changing the id of a tag against which you're matching.  This way you'll be confident that your tests are truly working the way they appear to work.

Thinking in Legacy

By Scott Roth on May 21st, 2008

Tagged with: legacy

If you get my reference in the title of this post, then you have probably been around for a while and may have fallen into the thought trap I did the other day.  Those of you that don't know Bruce Eckel's book probably are free from the "experience" (more accurately described as "baggage") that causes people to think in legacy patterns.

I was working on the metrics reporting section of a Rails application recently.  We needed to display a value which was derived by summing up a subset of the rows in a metrics table.  The metaphorical lightbulb went off over my head - I can do all this work in the database and just return the final result to my application!

So, I got busy and when I was done I had a a perfectly ugly SQL string using the sum function hardcoded into my model.  I then used the always nice call of "ActiveRecord::Base.connection.execute(hardcoded_sql_string)" to get my result back.  I thought job well done and moved on with my life.

Big Iron

Well, one of my less scarred co-workers saw the commit email go through and asked me why I was tainting the project with such hideous code.  Well, I proudly proclaimed, thinking I was about to teach a lesson, you always want to push the processing as far down the stack as possible - after all, those database boxes are Big Iron.  In addition, if you do the calculation in the database, you don't have to push all the rows over the wire in order to do a simple calculation in the app.

But, Scott, my co-worker exclaimed, the database is on the same box as the application server.  And as he said it, I realized I had pre-optimized based on a set of assumptions that didn't make sense for this application.  This application wasn't in some bloated enterprise infrastructure where there was a huge Oracle box lurking in the background with a firewall or two between it and the application server.  So, we rewrote the code in a more Railsy way, pulling back the data and populating the objects and iterating through them to arrive at our results, and, lo and behold, there wasn't a performance difference between the two methods - and the Rails way was much easier on the eyes and for any future developers to understand.

I'm not advocating that you never drop down to SQL in your Rails apps.  ActiveRecord is definitely a leaky abstraction (though a handy one) and it is well known that you can get into performance problems quickly if you don't understand the queries that are being made behind the scenes.  However, in this particular case, I had no reason to drop down an abstraction level to direct SQL.  I gained nothing and made the code less maintainable.  This specific example really just made me realize that we all carry around internalized assumptions that can turn out to not be valid for all situations.  It's good to remember this as we move from project to project, language to language, infrastructure to infrastructure, and environment to environment.

Setting up Multiple Site Instances on Joyent Accelerators

By Scott Roth on May 14th, 2008

Tagged with: joyent, capistrano, rails, tutorial

I recently set up a new Rails site on a Joyent Accelerator for a client.  The accelerator size selected had more than enough capacity, so we decided to set up the staging instance of the site on the same box.  There is a great set of instructions on the Joyent wiki for how to properly set up a Rails site that will be deployed via Capistrano to an Accelerator, so start there if you've never set up a site on Joyent before.  There are actually very few tweaks to those instructions you need to follow to set up multiple site instances on one Accelerator - in my case a "staging" and a "production" environment for a single application.

mongrel_cluster.yml
The default place to store this file is under /config.  We are actually going to set up a separate mongrel file for each environment, so create a file called mongrel_cluster_[environment].yml  (e.g. mongrel_cluster_staging.yml) under /config/accelerator/environments in your Rails application.  Set the number of servers, the port, and the environment variables as you would normally.  We'll be moving this file back to its expected location during the deployment process.

Now copy over the three files mentioned on the wiki into the /accelerator folder and make the needed changes from the instructions.  We'll just be adding a few additional modifications.

accelerator_tasks.rb
Since we have moved the location of the mongrel configuration file we need to let the "create_vhost" task know where to read in the YAML file from as it uses variables from that file to properly configure apache.  So update this line:

1  cluster_info = 
YAML.load(File.read("config/accelerator/environments/mong
rel_cluster_#{application}.yml
"))

apache_vhost.erb
No changes needed.  W00t!

smf_template.erb
This file is the template used to set up all the dependant services that your Rails app will need.  The problem with the default version is that the mongrel service needs to have a distinct name for each application that is set up on the box otherwise bad conflicts happen.  (This is also mentioned on the Joyent wiki page.)  Update this line to make sure you have a distinct name for each environment:

1  <dependent name='mongrel_multi-user_<%= application %>' 
restart_on='none' grouping='optional_all'>

Now we move onto the final step.  We have moved the location of the mongrel configuration file so that we can support multiples of that file in our source control system.  However, the mongrel service on the deployed server is still looking for that file in the old location with the old name.  So, we are going to add a simple task to deploy.rb to fix this at deploy time.  At the bottom of your file add a new custom task:

1  namespace :custom do
2 task :movemongrelconfig, :roles => :app do
3 # Move the env specific file to the general location
4 run "mv
#{release_path}/config/accelerator/environments/mongr
el_cluster_#{application}.yml
#{release_path}/config/mongrel_cluster.yml
"
5 end
6 end

And call the new task at the end of the deployment process:

1  after :deploy, 'custom:movemongrelconfig'

I'm sure that there are other ways to get multiple site instances running, but this one was relatively simple and has been working well for us for some time now.

Making FTP Code Compatible With SFTP Servers

By Phil Matarese on April 30th, 2008

Tagged with: ruby, rails, plugin

Why?

We recently needed to toggle between support for SFTP and traditional FTP in one of our applications.  In a Rails app, this is more of an annoyance than necessary because of the interface differences between Net::FTP and Net::SFTP.  Not only are the transfer and navigation methods arbitrarily different, but the way the current directory on the server is dealt with is not consistent.  I suspect these differences grow from the underlying differences in the technologies, but it would still be nice to code just one branch of code that does file transfers for either type of server.  This is Ruby, right?

What?

This was the impetus for writing the sftp-compat plugin to extend Net::SFTP, giving it all the same interfaces as Net::FTP.  The plugin is very new, but it was extracted from a real, working project we developed.  It's functional, and it's being used in production right now as we speak (rather as I write and as you read - nobody is speaking right now - it's actually very quiet here - nice and tranquil - mmm.)

How?

All of the documentation can be found at http://sftp-compat.rubyforge.org/.  There may be some functionality missing from the interface depending on your use case.  The current version was designed for writing to remote FTP hosts, so there are a few methods for reading that I never needed to implement.  If you are interested in using sftp-compat, but need the missing functions or would like to commit extra functions, please drop me an email.