Category Archive for Ruby

Rack::Bug debugging toolbar in four minutes

April 22, 2009

Here’s a quick screencast demonstrating Rack::Bug, a new Rack middleware for inspecting requests to any Rack-compatible web application.

Download the screencast (12 MB, 4:00)

The source is on GitHub. It ships with nine panels:

  • Rails Info
  • Resource Usage
  • Rack Env
  • SQL
  • AR Objects
  • Memcached
  • Templates
  • Log
  • Memory Usage
./script/plugin install git://github.com/brynary/rack-bug.git
1
2
3
4
5
6
7
# config/initializers/middleware.rb
require "rack/bug"

ActionController::Dispatcher.middleware.use Rack::Bug,
  :ip_masks   => [IPAddr.new("127.0.0.1")],
  :secret_key => "epT5uCIchlsHCeR9dloOeAPG66PtHd9K8l0q9avitiaA/KUrY7DE52hD4yWY+8z1",
  :password   => "rack-bug-secret"

Switching Webrat to Selenium mode

April 06, 2009

Ryan Bates did a great job covering Webrat in Railscast #156, and I’d recommend anyone who hasn’t used Webrat check it out. It’s a clear demonstration of how you can start using Webrat in a few minutes today with Rails built-in integration testing framework. Webrat works with RSpec, Cucumber, Shoulda, etc. too, but they aren’t required.

Unfortunately, during the screencast some Webrat bugs prevented running the tests in Selenium mode. Webrat has worked with Selenium for some time, but there have been some lingering rough edges around getting it up and running that I’ve been meaning to resolve. Today I’m happy to report those issues are now fixed, and getting started using Selenium with Webrat should now be drop-dead simple.

Give it a try by checking out the full Episode #156 source code, then make the following changes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# config/environments/test.rb
config.gem "webrat", :version => ">=0.4.4" # was 0.4.3

# test/test_helper.rb
class ActiveSupport::TestCase
  # ...
  self.use_transactional_fixtures = false # was true

  # ...

  # Add these lines:
  setup do |session|
    session.host! "localhost:3001"
  end
end
  
Webrat.configure do |config|
  config.mode = :selenium # was :rails
end

Running the test again should pop open a Firefox window for the running tests and print output like this:

$ rake test:integration
(in /Users/bhelmkamp/p/railscasts-episodes/episode-156/authenticator)
Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
Started
==> Waiting for Selenium RC server on port 4444... Ready!
==> Waiting for rails application server on port 3001... Ready!
..
Finished in 11.679288 seconds.

2 tests, 2 assertions, 0 failures, 0 errors

Success!

Note: I just pushed Webrat 0.4.4, so it might not be available on all the gem sever mirrors yet. If not, you can install it with the following commands:

git clone git://github.com/brynary/webrat.git
cd webrat
rake install_gem

Rack::Test released: Simply test any Rack-compatible app

March 04, 2009

Today I’m happy to announce the first release of a new library I’ve been cooking up: Rack::Test. It’s a simple API for issuing fake requests to any Rack app (e.g. Rails, Sinatra, Merb, etc.) similar to what you might be familiar with in Rails’ integration tests or Merb’s request specs. Basically, I extracted Merb’s request helper code into a small, reusable, framework agnostic library. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require "rack/test"

class HomepageTest < Test::Unit::TestCase
  include Rack::Test::Methods
  
  def app
    MyApp.new
  end
  
  def test_redirect_logged_in_users_to_dashboard
    authorize "bryan", "secret"
    get "/"
    follow_redirect!
    
    assert_equal "http://example.org/redirected", last_request.url
    assert last_response.ok?
  end
end

While developing Webrat, I started to realize all of the Ruby web frameworks were reinventing boilerplate code to create a test environment for issuing fake requests. They all have to:

  • Convert params hashes into encoded strings (something the browser handles for normal requests)
  • Maintain a simulated cooke jar to enable sessions
  • Generate multipart requests (to support test file uploads)

Through discussions with Josh Knowles and Yehuda Katz, it became apparent that an abstracted layer for these concerns would be valuable.

With Rack::Test, we hope to make it easy for frameworks to encourage their users to write tests by making it trivial to provide a testing environment. We’d like to foster compatibility between Ruby web app testing environments (especially important as ideas like multi-framework apps become more prominent). The philosophy is the library should stay small and extendable so frameworks can layer on additional functionality they want to offer without modifying Rack::Test’s core behavior or resorting to monkeypatching.

The Rack::Test API

The full RDoc is available on gitrdoc, but I’ve reproduced some of it below. It should feel familiar if you’re used to Rails integration testing or Merb request specs:

Rack::Test.initialize(app) — Initialize a new session for the given Rack app

#get(uri, params = {}, env = {}) — Issue a GET request for the given URI with the given params and Rack environment. Stores the issues request object in #last_request and the app’s response in #last_response. Yield #last_response to a block if given.

#post, #put, #delete, and #head — Behave like #get but specifying a different HTTP method

#request(uri, env = {}) — Issue a request to the Rack app for the given URI and optional Rack environment. Stores the issues request object in #last_request and the app’s response in #last_response. Yield #last_response to a block if given.

#follow_redirect! — Rack::Test does not follow any redirects automatically. This method will follow the redirect returned in the last response. If the last response was not a redirect, an error will be raised.

#header(name, value) — Set a header to be included on all subsequent requests through the session. Use a value of nil to remove a previously configured header.

#authorize(username, password)— Set the username and password for HTTP Basic authorization, to be included in subsequent requests in the HTTP_AUTHORIZATION header.

#last_request — Return the last request issued in the session. Raises an error if no requests have been sent yet.

#last_response — Return the last response received in the session. Raises an error if no requests have been sent yet.

Note: Thanks to the people who helped get Rack::Test off the ground: The Merb Team for their request helper code; Yehuda Katz and Josh Knowles for guidance, encouragement and support; Simon Rozet and Pat Nakajima for early code contributions.

Cucumber step definition tip: Stubbing time

February 02, 2009

When writing Cucumber scenarios, you'll occasionally need to describe a behavior that is dependent on time passing. For example, at weplay we want to ensure that a password reset link expires after one week.

In a RSpec code example, you might be used to stubbing time in this situation:

1
2
3

now = Time.now
Time.stub!(:now).and_return(now + 8.days)

This doesn't work out-of-the-box in a Cucumber step definition. By default, Cucumber doesn't include any of RSpec's mocking or stubbing support. This makes sense because Cucumber is a tool for acceptance testing (full stack), where stubbing and especially mocking should be discouraged and only used for very specific purposes (like stubbing time!).

If you find yourself in this situation, you can enable RSpec's stubbing support in your Cucumber step definitions by adding the following code to env.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

require "spec/mocks"

Before do
  $rspec_mocks ||= Spec::Mocks::Space.new
end

After do
  begin
    $rspec_mocks.verify_all
  ensure
    $rspec_mocks.reset_all
  end
end

Now you can go ahead and create a time_steps.rb like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

When /^(\d+) minutes pass$/ do |minutes|
  now = Time.now
  Time.stub!(:now).and_return(now + minutes.to_i.minutes)
end

When /^(\d+) hours pass$/ do |hours|
  now = Time.now
  Time.stub!(:now).and_return(now + hours.to_i.hours)
end

When /^(\d+) days pass$/ do |days|
  now = Time.now
  Time.stub!(:now).and_return(now + days.to_i.days)
end

RSpec will clear your time stub at the end of each scenario.

The RSpec Book is now in Beta

January 29, 2009

The RSpec Book

I’m very happy to report that The RSpec Book, which I’ve been collaborating on, is now available for sale as a Beta Book from the Pragmatic Bookshelf. David Chelimsky posted an announcement to the rspec-users mailing list which sums it up quite well. To quote:

On behalf of all the authors, I’d like to extend a special thank you to all of you who have contributed to the software and the conversation around RSpec, Cucumber, and BDD in general. RSpec would be nothing without the community that has evolved around it, so thank you, thank you, thank you.

Enjoy! And if you do pick it up, please let us know what you think. We’re eager to improve it based on the feedback we get.

features2cards released: Convert Cucumber feature files to PDF task cards

January 20, 2009

Today I’d like to announce the first real release of features2cards 0.1.1. The code has been sitting in my GitHub repository for awhile now, but I thought it was time to give it a formal release announcement.

features2cards is a simple tool to generate a printable PDF of scenario cards from Cucumber feature files. At weplay, we use this at the start of each sprint to get out Scrum task board up-to-date. Interally, features2cards uses the Cucumber feature parser and Prawn for PDF generation (in native Ruby!).

I’ve uploaded the gem and you should be able to install it off RubyForge very soon:

sudo gem install features2cards

Here’s how you use it:

features2cards features/scale_rails.feature

This will generate a file called cards.pdf in the current directory. Open that up, print it, and you’ve got cards ready to be chopped up and stuck to your Agile task board.

That’s all there is to it for now. Fork away. It’s only 150 lines including whitespace. I’d be happy to add additional formats, color support and other enhancements.

Special thanks to Luke Melia. His pdf-storycards project inspired this.

Our Git deployment workflow

August 03, 2008

At weplay, we recently switched to pure git version control from git-svn. Now that we've had a couple weeks for the dust to settle, I'd like to share our workflow for managing deployments to our staging and production clusters.

We started by outlining the goals of our system:

  • All code that's pushed to our staging and production environments must be in GitHub. Nothing goes straight from a local repository to our servers.

  • Any developer can deploy our most recent work to staging.

  • Any developer can deploy the code on staging into production. We (try to) avoid deploying anything to production that hasn't been pushed to staging first.

  • Any developer can see a diff between "What is deployed" and "What I'm about to deploy."

  • Any developer can branch from the production codebase for time sensitive tweaks and fixes. These need to be staged before they're deployed to production too.

  • That functionality is available in an automated, safe, easy to use form. (Hint: rake)

We ruled out having "production" and "staging" tags because updating tags across a tree of remotes doesn't seem to work smoothly. We also ruled out doing git merges from master into our staging branch. Our staging server/codebase jumps around from the latest changes to the production code (up to a week old) based on what we need to test, so we really just want to replace it.

We settled on using remote git branches for production and staging on origin and hard resetting them to other branches to simulate a copy. We treat these branches more like tags and never commit to them directly. This has worked great so far.

Here's what our rake tasks look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
class GitCommands

  def diff_staging
    `git fetch`
    puts `git diff origin/production origin/staging`
  end
  
  def tag_staging(branch_name)
    verify_working_directory_clean
    
    `git fetch`
    `git branch -f staging origin/staging`
    `git checkout staging`
    `git reset --hard origin/#{branch_name}`
    `git push -f origin staging`
    `git checkout master`
    `git branch -D staging`
  end
  
  def tag_production
    verify_working_directory_clean
    
    `git fetch`
    `git branch -f production origin/production`
    `git checkout production`
    `git reset --hard origin/staging`
    `git push -f origin production`
    `git checkout master`
    `git branch -D production`
  end

  def branch_production(branch_name)
    verify_working_directory_clean
    
    `git fetch`
    `git branch -f production origin/production`
    `git checkout production`
    `git branch #{branch_name}`
    `git checkout #{branch_name}`
    `git push origin #{branch_name}`
  end
  
protected

  def verify_working_directory_clean
    return if `git status` =~ /working directory clean/
    raise "Must have clean working directory"
  end
end


namespace :tag do
  desc <<-DESC
    Update the staging branch to prepare for a staging deploy.
    Defaults to master. Optionally specify a BRANCH=name
  DESC
  
  task :staging do
    branch_name = ENV['BRANCH'] || "master"
    GitCommands.new.tag_staging(branch_name)
  end

  desc "Update the remove production branch to prepare for a release"
  task :production => ['diff:staging'] do
    GitCommands.new.tag_production
  end
end

namespace :diff do
  desc "Show the differences between the staging branch and the production branch"
  task :staging do
    GitCommands.new.diff_staging
  end
end

namespace :branch do
  desc "Branch from production for tweaks or bug fixs. Specify BRANCH=name"
  task :production do
    branch_name = ENV['BRANCH']
    raise "You must specify a branch name using BRANCH=name" unless branch_name
    GitCommands.new.branch_production
  end
end

namespace :deploy do
  desc "Tag and deploy staging"
  task :staging => "tag:staging" do
    `cap staging deploy:long`
  end
end

The last one (rake deploy:staging) simply wraps up the common task of tagging our latest code to be pushed to staging and initiating a staging deploy.

Note: To use most of these commands, your local working directory must be clean. If we have outstanding changes in our tree when we need to run them, we use git-stash to temporarily move them out of the way.

Thanks to Scott Chacon for helping us work this out. Be sure to check out his GitCasts and Git Internals PDF.

Story Driven Development slides posted

April 26, 2008

Just wrapped up my Story Driven Development talk at GoRuCo 2008. There were some great questions at the end, and I'm looking forward to more hallway track conversations about SDD.

Download the slides as a PDF (1.6 MB)

Note: Confreaks is recording the talks today, so a full video of my presentation should be online soon.

Presenting at GoRuCo in NYC

February 09, 2008

I just received word that my proposal to speak at NYC’s very own second annual GoRuCo has been accepted. The conference is set for Saturday, April 26th at Pace University downtown. My topic will be the same as my Scotland on Rails presentation.

I’m really excited to have the opportunity to present at home, in front of many of my friends and colleagues. Now I just need to make sure I don’t suck. Better get back to working on my slide deck…

Presenting at Scotland on Rails

January 31, 2008

I’m going to be presenting on Story Driven Development at the first Scottish Ruby on Rails conference, Scotland on Rails. I’ll be exploring the concept of Story-first development from both an agile process and implementation standpoint.

Topics will include:

  • Collaborating with the product owner on stories
  • What makes good stories and scenarios?
  • Driving stories with a browser simulator like Webrat
  • Potential pitfalls and ways to avoid them

If you’re going to be in Edinburgh for the conference, let me know.

"What's new in Rails 2?" slides posted

December 12, 2007

Thanks to everyone who attended my “What’s new in Rails 2?” talk last night at NYC.rb. The questions were great, and I picked up a few tips myself from the discussions we had.

Download the slides as a PDF (1.4 MB).

Save the date: GoRuCo will be April 26th, 2008

December 08, 2007

The second annual Gotham Ruby Conference a.k.a. GoRuCo will be held Saturday, April 26th 2008 at Pace University in Manhattan. Last year was a great day of learning and socializing with fellow Ruby geeks, and we expect this year to be even better. I’ll see you there!

Speaking at NYC.rb on Tuesday

December 08, 2007

On Tuesday, December 11th, I’ll be speaking at NYC.rb about “What’s new in Rails 2?”. Come by and watch or check back after then and I’ll be sure to have my slides up.

Webrat 0.1.0 released

December 08, 2007

Last week I pushed Webrat 0.1.0 out the door. So far the response has been great, and I’ve already received a couple patched (Thanks, David!). Here’s a quick usage example (from the README):

1
2
3
4
5
6
7
8
def test_sign_up
    visits "/"
    clicks_link "Sign up"
    fills_in "Email", :with => "good@example.com"
    select "Free account"
    clicks_button "Register"
    ...
  end

Behind the scenes, this will perform the following work:

  1. Verify that loading the home page is successful
  2. Verify that a “Sign up” link exists on the home page
  3. Verify that loading the URL pointed to by the “Sign up” link leads to a successful page
  4. Verify that there is an “Email” input field on the Sign Up page
  5. Verify that there is an select field on the Sign Up page with an option for “Free account”
  6. Verify that there is a “Register” submit button on the page
  7. Verify that submitting the Sign Up form with the values “good@example.com” and “Free account” leads to a successful page

Take special note of the things not specified in that test, that might cause tests to break unnecessarily as your application evolves:

  • The input field IDs or names (e.g. “user_email” or “user[email]”), which could change if you rename a model
  • The ID of the form element (Webrat can do a good job of guessing, even if there are multiple forms on the page.)
  • The URLs of links followed
  • The URL the form submission should be sent to, which could change if you adjust your routes or controllers
  • The HTTP method for the login request

A test written with Webrat can handle these changes smoothly.

SVN is at http://svn.eastmedia.net/public/plugins/webrat/. Check out the full README.

Scourging your Ruby code with Flog

September 13, 2007

Flog is a tool build by Ryan Davis to analyze Ruby code complexity. It’s dead simple to run, and immediately provides useful metrics in the form of a “Flog score” per method. Don’t get too hung up on the actual values, but high outliers are prime candidates for refactoring.

I ran this on a couple recent projects I worked on, and it proved quite accurate in identifying the areas of code that seem to be in the most pain. Flog doesn’t replace hands-on code review, but it is still a helpful aid.

Below is a rake task I threw together for running Flog on a Rails project. Seems like a great candidate to install via Chris Wanstrath’s Sake, a system for handling Rake tasks that you want to be available from anywhere.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def flog(output, *directories)
  `find #{directories.join(" ")} -name \\*.rb|xargs flog > #{RAILS_ROOT}/tmp/flog/#{output}.txt`
end

desc "Flog models, controller, helpers and lib"
task :flog do
  flog "all", *%w[app/models app/controllers app/helpers lib]
end

namespace :flog do
  desc "Flog code in app/models"
  task :models do
    flog "models", "app/models"
  end
  
  desc "Flog code in app/controllers"  
  task :controllers do
    flog "controllers", "app/controllers"
  end
  
  desc "Flog code in app/helpers"  
  task :helpers do
    flog "helpers", "app/helpers"
  end
  
  desc "Flog code in lib"  
  task :lib do
    flog "lib", "lib"
  end
end