Blog
what did i learn today
testing image_tag using rspec2 and rails3
When testing your helpers, you might want to test if your call to image_tag generates the correct output. Unfortunately, in Rails, for caching purposes, the asset tag is added which makes testing it somewhat unreliable. Luckily there is an easy way around that. If you define the environment variable RAILS_ASSET_ID, that is used instead of some permutation of the file-creation-date/time. You could set RAILS_ASSET_ID to a specific number, simplifying the tests, because the number will always be the same, on all machines executing the tests. But if you set RAILS_ASSET_ID to an empty string, it effectively removes the tag from the link. For testing this is exactly what we want. So an easy way to test is: [ruby] describe OperatorsHelper do context "get_operator_icon" do before(:each) do ENV['RAILS_ASSET_ID'] = '' end it "returns the image for any operator (not current)" do helper.stub(:current_operator?).and_return(false) @operator = Factory(:operator) helper.get_operator_icon(@operator).should == "<img alt=\"User-business-gray\" src=\"/images/icons/user-business-gray.png\" title=\"interactive user\" />" end it "returns the image for the current operator" do helper.stub(:current_operator?).and_return(true) @operator = Factory(:operator) helper.get_operator_icon(@operator).should == "<img alt=\"User-business\" src=\"/images/icons/user-business.png\" title=\"interactive user (yourself)\" />" end end end [/ruby] Hope this might help someone with the same problem :)
During the migration of a huge application from rails2 to rails3 I encountered a seriously baflling error. In almost every controller spec, i got the the following error: [ruby] Caught ActionController::RoutingError: No route matches {:controller=>"home"} [/ruby] This got me puzzling. If i ran a single controller-spec it worked, running the whole bunch failed (using rake spec or rspec spec/controllers/**/*_spec.rb). So I posted a question on stackoverflow and on the rspec-mailing-list. When googling, all related issues seemed to be caused by actual errors in config/routes.rb, but my application was working, it was generating all links correctly. It only failed when testing, and only when running the full suite. What i did to solve this, was actually compare the log-files of a single group of working controllers with the total set of controllers. And there it was: the routing table was erased by the first spec i ran. We have a base controller class where the shared behaviour for our API-controllers is placed. To test this controller we create, inside our spec, a new controller-class that derives from that baseclass and add a new route. In rails2 and rspec1 this spec looked as follows (simplified): [ruby] require 'spec_helper' class WsTestController < Ws::BaseController def index end end ActionController::Routing::Routes.draw do |map| map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end describe WsTestController do context "without authentication" do it("asks for Basic authentication") { get :index response.status.should contain("401") } end context "with authentication" do # .. snipped the rest .. end end [/ruby] Now apparently what happens now, in rails3, is that instead of adding routes, the entire routing table is replaced by these routes. Whoops. So in effect, this was almost the only controller-spec that actually worked. When i disabled this spec, or more specifically the part where the routes are manhandled, all my controller and view specs worked again. So how do you fix this? How can you dynamically add routes in rails3 at runtime? Luckily google found the solution here. So my working spec for rails3/rspec2 looks as follows (and that doesn't interfere with my other specs) : [ruby] require 'spec_helper' class WsTestController < Ws::BaseController def index render :text => 'this worked' end end describe WsTestController do before(:each) do begin _routes = Dpplus::Application.routes _routes.disable_clear_and_finalize = true _routes.clear! Dpplus::Application.routes_reloader.paths.each{ |path| load(path) } _routes.draw do # here you can add any route you want get "ws_test/index" end ActiveSupport.on_load(:action_controller) { _routes.finalize! } ensure _routes.disable_clear_and_finalize = false end end context "without authentication" do it("asks for Basic authentication") { get :index response.status.should contain("401") response.header["WWW-Authenticate"].should contain('Basic') } end context "with authentication" do # ..snipped ... end end [/ruby] Hope this helps :)
I have created a few rails 3 sites, but i have never before ported an existing rails2 application to rails3. I will describe the problems i encountered. First off there were some very good resources (step-by-step descriptions) to guide me through it: The conversion ran pretty smoothly, but I will describe the things i bumped into that were less obvious.

ActiveSupport::Callbacks

Before we wrote: [ruby] module Workflow class Base define_callbacks :before_something, :after_something def some_method run_callbacks :before_something puts "some method" run_callbacks :after_something end end class SpecificWorkflow < Base before_something :do_something def do_something puts "something" end end [/ruby] Now, this has changed. Into the following: [ruby] module Workflow class Base define_callbacks :something def some_method run_callbacks :something do puts "some method" end end end class SpecificWorkflow < Base set_callback :something, :before, :do_something def do_something puts "something" end end [/ruby] Which is pretty nice. It has the disadvantage we can only use :before and :after callbacks anymore, but that was easily solved.

ActionController::ParamsParser

We hooked into the Rack middleware before the ActionController::ParamsParser to make sure we return Bad Request when the parsing fails. This middleware has been renamed to ActionDispatch::ParamsParser. Easy fix :)

safe_helper removed?

We prepared our Rails 2 project a long time ago, and used the rails_xss plugin. We used the safe_helper all over our helper-methods, but apparently this is not supported in Rails 3. Bummer. So, before [ruby] def some_helper "<strong>something</strong>" end safe_helper :some_helper [/ruby] After: [ruby] def some_helper "<strong>something</strong>".html_safe end [/ruby] A bit annoying to do, a bit unfortunate, because we thought we were preparing ourselves for a smoother upgrade. Not entirely so.

The routes!

Only after those steps did my rails get far enough to discover my routes. I did not change anything about the routes, just placed inside the correct block. I deleted my index.html and put back my original application.html.haml and the first view was working.

The missing to_key

The to_key was missing inside the user_session model. I described that solution already in this blogpost. So i just needed to fix that.

Replacing ActionController::RecordIdentifier.singular_class_name

We used ActionController::RecordIdentifier.singular_class_name, in rails3 this is replaced by ActionController::RecordIdentifier.dom_class.

Missing translations

A feature we used a lot was I18n.t('.filter') and this would look for operators.filter.filter because the line was inside a partial _shared/_filter.html.haml and called from the operators/index.html.haml. Now this does not work anymore, and now instead I18n looks for _shared.filter.filter. I did not find an easy way to solve this, just moved some translations. Luckily we did not override the translations of a partial based on the context where it was rendered yet. Does anybody has any tips on that?

Upgrading javascript helpers

In rails3 unobtrusive is the way to go. So, a remote_form_for like [ruby] - form_remote_tag :url => update_url do [/ruby] becomes [ruby] - form_tag :url => update_url, :remote => true do [/ruby] Which seems easy enough. It does get more complicated when the :update tag is used. For example: [ruby] - remote_form_for post, :update => {:success => "result_#{id}"} do |form| [/ruby] becomes [ruby] - remote_form_for post, :remote => true, :html => {:class => 'remote-post-form', :'data-update' => "#result_#{id}"} do |form| [/ruby] The :update is no longer supported as before, and we have to perform some glueing ourselves. So i replaced the :update by data-update. And then inside javascript (e.g. application.js) we can do the following: [javascript] jQuery(function($) { $(".remote-post-form") .bind("ajax:success", function(data, status, xhr) { var update_selector = $(this).attr('data-update'); $(update_selector).html(status); }); }); [/javascript] If you need more elaborate example, check this question on stackoverflow.

master_helper_module

This has been replaced by _helpers.

active_layout

The active_layout method, of a controller, no longer exists inside rails3. To replace it you need to use two methods:
  • action_has_layout? returns true if a layout exists.
  • To get the layout-name, i have no better solution then calling the private method _layout. This can be done, as known, by doing controller.send(:_layout).

Upgrade rspec

I had to upgrade Rspec 1 to v2. First off in the Gemfile i added the correct version. I removed the rspec.rake file. I ran [ruby] rails g rspec:install [/ruby] But still all rake spec commands were missing. The fix was simple. In the Gemfile make sure the rspec-rails is also usable from development mode: [ruby] group :development, :test do gem 'rspec-rails' end [/ruby] Now on to fixing the tests!

Fixing the tests

In the upgrade from rspec1 to rspec2 a lot of things have changed. What i encountered:
  • replace all Spec:: by RSpec::
  • controller_name 'users' no longer exists, write a surrounding describe UsersController do instead (that is also clearer imho)
  • ...

Extending Rspec::Rails::ExampleGroup::ControllerExampleGroup

Before, in rspec1, ControllerExampleGroup was a class and you could add code to it. So for instance, we had a file like this in our support folder: [ruby] module RSpec module Rails module Example class ControllerExampleGroup let(:the_account ) { Factory(:account) } let(:the_user ) { Factory(:user)} end end end end [/ruby] A bit larger, but you get the picture: this allowed us to define a set of shared let definitions. In rspec-2 this is no longer possible this way, because ControllerExampleGroup is now a module. I handled that like this: [ruby] module Lets def self.included(base) base.let(:the_account ) { Factory(:account) } base.let(:the_user ) { Factory(:user)} end end [/ruby] and inside my spec_helper this file is automatically required (since it is stored in the support folder) and i just add [ruby] config.include(Lets) [/ruby]

Rspec: replace config.extend

I also noticed that using config.extend(ModuleName) dit not work anymore. Instead i had to write (on the same place, inside the configure block) : [ruby] Rspec.configure do |config| .. include ModuleName .. end [/ruby] and that worked for me.

More?

Do you have more hints to share? What bumps did you find and how did you overcome them?
getting rcov working with rspec 2.0.0.rc
I had troubles to get rcov working with the latest rspec2 release (since beta23 and now 2.0.0.rc). I got the same error every time: [bash] `require': no such file to load -- spec_helper (LoadError) [/bash] But luckily, somebody found the problem and it is extremely easy to fix. Just add -Ispec to your rcov task. The rake spec:rcov does not work for me (as it needs to be fixed). So i added my own task (add this code to the end of Rakefile or add in a seperate file rcov.rake inside lib/tasks) : [ruby] desc "Run all specs with rcov" RSpec::Core::RakeTask.new("test_cov") do |t| t.rcov = true t.rcov_opts = %w{--rails --include views -Ispec --exclude gems\/,spec\/,features\/,seeds\/} end [/ruby] and then you can run the task by typing [bash] > rake test_cov [/bash] in your rails root folder.
rspec2 using watchr instead of autotest
I was having troubles with autotest, and not really finding a good solution. But i noticed that inside rspec2 they were using watchr. Watchr is a very generic gem that will allow you to watch a set of files and take action when something changes. That sounds great, but maybe a bit too general :) So, it appears you need a script to run your rspec2 continuously using watchr. Luckily i did find some examples, which allowed my to brew my own (save in your rails-root as specs.watchr): [ruby wraplines="false"] # adapted from http://github.com/rspec/rspec-rails/blob/master/specs.watchr # Run me with: # # $ watchr specs.watchr # -------------------------------------------------- # Convenience Methods # -------------------------------------------------- def all_spec_files Dir['spec/**/*_spec.rb'] end def run_spec_matching(thing_to_match) matches = all_spec_files.grep(/#{thing_to_match}/i) if matches.empty? puts "Sorry, thanks for playing, but there were no matches for #{thing_to_match}" else run matches.join(' ') end end def run(files_to_run) puts("Running: #{files_to_run}") system("rspec -c #{files_to_run}") no_int_for_you end def run_all_specs run(all_spec_files.join(' ')) end # -------------------------------------------------- # Watchr Rules # -------------------------------------------------- watch('^spec/(.*)_spec\.rb') { |m| run_spec_matching(m[1]) } watch('^app/(.*)\.rb') { |m| run_spec_matching(m[1]) } watch('^app/(.*)\.haml') { |m| run_spec_matching(m[1]) } watch('^lib/(.*)\.rb') { |m| run_spec_matching(m[1]) } watch('^spec/spec_helper\.rb') { run_all_specs } watch('^spec/support/.*\.rb') { run_all_specs } # -------------------------------------------------- # Signal Handling # -------------------------------------------------- def no_int_for_you @sent_an_int = nil end Signal.trap 'INT' do if @sent_an_int then puts " A second INT? Ok, I get the message. Shutting down now." exit else puts " Did you just send me an INT? Ugh. I'll quit for real if you do it again." @sent_an_int = true Kernel.sleep 1.5 run_all_specs end end [/ruby] It even seems to be quicker than autotest, but not sure if that is just wishful thinking.
With Rails3 i can create a project fully cut to my needs. I will write it down here, just so i remember it well and hopefully it will help some of you too. In my projects, i want to use haml, rspec2, factory-girl, jquery, ... We need a few steps to complete this.

Create the project

For starters, create the folder, without Test::Unit (-T) and without prototype (-J). [ruby] rails new test-project -T -J [/ruby] You could also specify the database, using option -d, [--database=DATABASE], with the following options:
  • mysql
  • oracle
  • postgresql
  • sqlite3 (default)
  • frontbase
  • ibm_db

Specify needed gems

We also have to specify the gems we need. To do that, we need to edit the Gemfile which is located in the root of your project. My file looks as follows: [ruby] source 'http://rubygems.org' gem 'rails', '3.0.0.rc' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' #gem 'pg' gem 'sqlite3-ruby', :require => 'sqlite3' # Use unicorn as the web server # gem 'unicorn' # Deploy with Capistrano # gem 'capistrano' # To use debugger # gem 'ruby-debug' gem 'rails3-generators' gem "bson_ext" gem "haml" gem "haml-rails" gem "jquery-rails" gem "rcov" # we need this here, see http://blog.davidchelimsky.net/2010/07/11/rspec-rails-2-generators-and-rake-tasks/ group :development, :test do gem "rspec-rails", ">= 2.0.0.beta.18" end # test-environment gems group :test, :spec, :cucumber do gem "factory_girl_rails" gem "rspec", ">= 2.0.0.beta.18" gem "remarkable", ">=4.0.0.alpha2" gem "remarkable_activemodel", ">=4.0.0.alpha2" gem "remarkable_activerecord", ">=4.0.0.alpha2" gem "capybara" gem "cucumber" gem "database_cleaner" gem "cucumber-rails" end [/ruby] Run bundle install to install all needed gems.

Use wanted generators

Then now edit the file application.rb, located in the config folder, and add the following lines: [ruby] # Configure generators values config.generators do |g| g.test_framework :rspec, :fixture => true g.fixture_replacement :factory_girl, :dir=>"spec/factories" end [/ruby] This will make sure that the generators use our own defaults. So now when you type [ruby] rails generate model TestModel name:string rails generate controller TestModel [/ruby] and it will create haml views, rspec tests, and factories. Awesome :)

Prepare to use jquery!

To start using jQuery in Rails3, you have to first get the rails.js specifically for jQuery (stored in the jquery-ujs project). Save it in your public\javascripts directory. Download jquery, and make sure to include both in your application layout or view. In HAML it would look something like. [ruby] = javascript_include_tag 'jquery.min.js' = javascript_include_tag 'rails' [/ruby] BUT: there is a quicker route! We also installed the gem jqeury-rails, which has the following generator if we want to install jquery in one go! [ruby] rails g jquery:install #--ui to enable jQuery UI [/ruby] Rails requires an authenticity token to do form posts back to the server. This helps protect your site against CSRF attacks. In order to handle this requirement the driver looks for two meta tags that must be defined in your page's head. Luckily rails makes it easier for us, again, and we just need to include csrf_meta_tag somewhere inside your page's head (rails3 does this default in your application.html.erb). Be sure to include it in your haml code too. An example application.html.haml would look like this: [ruby] !!! Strict %html{ "xml:lang" => "en", :lang => "en", :xmlns => "http://www.w3.org/1999/xhtml" } %head %title== Test-project #{@page_title} %meta{'http-equiv' => "Content-Type", :content => "text/html; charset=utf-8"} %link{ :href => "/favicon.ico", :rel => "shortcut icon" } = stylesheet_link_tag :all = javascript_include_tag 'jquery-1.4.2.min.js', 'rails.js', 'application.js', :cache => 'all_1' = csrf_meta_tag = yield :local_javascript = yield :head %body #banner =image_tag 'your-logo.png' .the-title %h1 %p Your application #navigation #container #sidebar #contents =yield #footer == &copy; 2010 your company [/ruby]

Done! :)

rails3 and rspec2 view testing
Testing the view in Rspec is just great. It allows you to test the view in total isolation. It just does the render. Make sure you set up some data that is needed, and then check if all areas are available for instance. Now for Rspec2 a lot of things have changed:
  • have_tag is no longer supported: use have_selector from webrat instead
  • response is deprecated, use rendered instead
  • the assignment has changed, write assign(:events, [stub_model(Event)]) instead of using the old assigns[:events]=[ ...] notation
Let me show you a full example, because that always makes things clearer: [ruby wraplines="false"] require 'spec_helper' DUMMY_NUMBER="dummy number" DUMMY_MESSAGE="dummy message" describe "envelopes/index.html.haml" do before (:each) do envelope = mock_model(Envelope) envelope.should_receive(:destination).and_return(DUMMY_NUMBER) envelope.should_receive(:message).and_return(DUMMY_MESSAGE) assign(:envelopes, [envelope]) render end it ("has a h1") { rendered.should have_selector('h1')} it ("shows the correct title") { rendered.should contain(t('envelopes.list.title')) } it ("have a table-grid") { rendered.should have_selector('.grid') } it ("shows the destination") { rendered.should contain(DUMMY_NUMBER) } it ("shows the message") { rendered.should contain(DUMMY_MESSAGE) } end [/ruby]