How to test routes in a Rails 3.1 mountable engine

This problem has been eluding me for a few weeks...

While working on Blogit I wanted to write specs for the routes but kept hitting ActionController::RoutingError with every approach I tried.

Finally I discovered this solution:

# spec/routing/post_routing_spec.rb

...

before do
  @routes = Blogit::Engine.routes
end

it "routes /posts/page/:page to posts#index with page param" do
  { get: "posts/page/2" }.should route_to({
    controller: "blogit/posts",
    action: "index",
    page: "2"
  }
end

Setting the @routes variable to the Blogit Engine's routes seemed to do the trick nicely!

There's something really dirty and "hacky" about this approach though - there surely is a better way? If anyone knows, please leave a comment below.

4 comments

Building A REST API in Rails 3

I just put together a short screencast showing how simple it is to build a REST API using Rails 3.

The screencast also shows how the default scaffold templates can be refactored to be DRYer.

You can pull a copy of the application code (with further refactoring advice) from it's repository on GitHub

Feedback and replies welcome

2 comments

Now running on Blogit: A Ruby on Rails blogging Engine

Over the past few weeks I've been working on a Ruby on Rails gem named blogit and today I've relaunched my own blog (this site) using blogit to handle pretty much everything.

The concept of blogit is simple: Add a blog to your Ruby on Rails application with as little effort as possible! The gem comes with everything you need to get started, and quite a few options which you can set to optimise your setup.

To Install Blogit

Add this to your Gemfile

gem "blogit"

...and run bundle install to install the gem.

Next, run:

# add an initializer to config/initializers with all of the configuration options
$ rails g blogit:install
# This will add the necessary migrations to your app's db/migrate directory
rake blogit:install:migrations
# This will run any pending migrations
rake db:migrate

then add the following to your routes.rb file:

# config/routes.rb
mount Blogit::Engine => "/blog"

... and finally, declare which of your models acts as blogger in your app (usually User).

class User < ActiveRecord::Base

  # This method adds all of the required associations etc.
  blogs

end

Configuration

Running rails g blogit:install will add an initializer file named blogit.rb. In here you can set various configuration options. Here's a quick rundown of the options currently available:

  • :include_comments - Should blogit provide comments too? (defaults to true)
  • :current_blogger_method - The name of the controller method we'll call to return the current blogger. (defaults to :current_user)
  • :blogger_display_name_method - what method do we call on blogger (User) to return their display name? (defaults to :username)
  • :datetime_format - Which DateTime::FORMATS format do we use to display blog and comment publish time (defaults to :short)
  • :posts_per_page - Number of posts to show per page using Kaminari (defaults to 5)
  • :highlight_code_syntax - Should code blocks be highlited using Pygments? (defaults to true)
  • :authentication_method - The name of the before filter we'll call in the controller to authenticate the current user. (defaults to :login_required)
  • :author_edits_only - If set to true, only the user who authored the post may, edit or destroy (defaults to false)
  • :ajax_comments - If set to true, the comments form will POST and DELETE to the comments controller using AJAX calls. (defaults to true)
  • :include_admin_actions - If set to true, the create, edit, update and destroy actions will be included. If set to false, you'll have to set these yourself elsewhere in the app (defaults to true)
  • :include_admin_links - If set to true, links for new posts, editing posts and deleting comments will be available. If set to false, you'll have to set these yourself in the templates. (defaults to true)
  • :default_parser - The default format for parsing the blog content. (defaults to :markdown).
  • :redcarpet_options - When using redcarpet as content parser, this is the options hash for Redcarpet
  • :cache_pages - Should the controllers cache the blog pages as HTML? (defaults to false).

Customisation

The view templates will probably need tweaking to suit your own site. For this reason, I've tried to break the views into as many modular partials as was practical. This means if you want to change only how the header is displayed, you only have to change the _post_head.html.erb. You can nosey around the blogit repository to see how the view templates are broken up.

Still in Beta...

The gem is still in beta while I settle on which configurations and features would suit most people. In the meantime, please feel free to try it out and suggest features you'd like to see.

4 comments

Segmentation error: Compiling REE on OS X 10.7 using RVM

When trying to install REE using RVM on OS X Lion (10.7) you may come across the following error:

/usr/bin/gcc   -dynamiclib system_allocator.c -install_name @rpath/libsystem_allocator.dylib -o libsystem_allocator.dylib
mkdir -p .ext/common
make PREINCFLAGS='-I/opt/local/include' PRELIBS='-L/opt/local/lib -Wl,-rpath,/Users/Gavin/.rvm/rubies/ree-1.8.7-2011.03/lib -L/Users/Gavin/.rvm/rubies/ree-1.8.7-2011.03/lib -lsystem_allocator'
./ext/purelib.rb:2: [BUG] Segmentation fault
ruby 1.8.7 (2011-02-18 patchlevel 334) [i686-darwin11.1.0], MBARI 0x6770, Ruby Enterprise Edition 2011.03

make: *** [.rbconfig.time] Abort trap: 6

The problem: Xcode and OS X Lion now use llvm-gcc, instead of gcc as the default compiler.

The solution: Temporarily set your CC environment variable back to the old gcc:

$ export CC=/usr/bin/gcc-4.2

Now try and reinstall REE passing the force argument to re-download and compile using gcc

$ rvm install ree --force
0 comments

Writing Rails helper tests/specs using MiniTest::Spec

So MiniTest has been receiving a lot of attention recently as being the testing framework for Ruby. Supporting TestUnit and Rspec syntax it appeals to both camps while also offering a much lighter, faster codebase.

This week I bundled together a gem with a bunch of helper methods and CSS classes that I always seem to re-write across every app that I work in. Since the gem is still pretty basic and subject to change a lot before I settle on a stable version 1 I figured this would be a good project to experiment with MiniTest::Spec.

Moving to MiniTest::Spec

I prefer the Rspec flavour so named and structured my test files like so:

spec/
  - spec_helper.rb
  - dummy # a dummy rails app for testing
  - helpers
  - support # extra files for spec support, more on these in a minute 

Note - Using this structure, you'll also have to update your Rakefile a little:

# Rakefile
Rake::TestTask.new(:spec) do |t|
  t.libs << 'lib'
  t.libs << 'spec'
  t.pattern = 'spec/**/*_spec.rb'
  t.verbose = true
end

... and modify spec_helper.rb to look like this:

# Configure Rails Environment
ENV["RAILS_ENV"] = "test"

require File.expand_path("../dummy/config/environment.rb",  __FILE__)

require "rails/test_help"

require "minitest/rails"

Rails.backtrace_cleaner.remove_silencers!

# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }

Creating a dummy class for the specs

Since Rails helpers are modules, we need to define a class to include the module in so we can test the methods. Your helper methods may also call Rails view helper methods so our class should behave just like a normal Rails view.

# spec/support/dummy_class.rb

# define a class that inherits from ActionView::Base
class DummyClass < ActionView::Base
  # include each of your Rails helpers here
  include MyHelper
end

Usually we access the helper through a method provided by rspec-rails named helper. We can recreate that same behaviour by defining this method:

# spec/spec_helper_methods.rb

def helper
  @helper ||= DummyClass.new
end

This method should now be available to all of our specs. We can call it like so:

require "spec_helper"

describe UsersHelper do

  describe :my_method do
    helper.my_method("arg").must_equal "<div class='someclass'>arg</div>"
  end

end

You can run your specs by running rake spec

Testing rails helper methods using MiniTest::Spec

The next problem I ran into was that, using MiniTest's must_equal I had to specify exactly what the output from the view helpers would be.

I find this is very brittle. For example, consider this method:

def bodacious_div(content, klass, id)
  %{<div class="#{klass}" id="#{id}">#{content}</div>}.html_safe
end

And this test:

  describe :bodacious_div do
    it "should return a div with class, id and content" do
      helper.bodacious_div("hello", "my_class", "my_id").must_equal %{<div id="my_id" class="my_class">#{content}</div>}
    end
  end

This test would fail because the ID and class attributes are in the wrong order. In other words, the method is behaving as I'd like it to but the test fails because the syntax isn't exact.

This is too brittle for my liking, I'd rather access a matcher like ActionController's assert_dom_equal.

To add a custom matcher, all we have to do is extend the MiniTest::Matchers module:

# spec/support/mini_test/assertions.rb
module MiniTest::Assertions

  def assert_dom_equal(expected, actual, message = nil)
    expected_dom = HTML::Document.new(expected).root
    actual_dom   = HTML::Document.new(actual).root
    message      = "expected:\n #{actual}\nto equal:\n#{expected}"
    assert_block(message) { expected_dom == actual_dom }
  end

end

This assertion isn't useful in Spec style MiniTests yet though, it's written in the UnitTest assertion syntax. To add it to the MiniTest::Spec expectations, we just need to call the method infect_an_assertion within MiniTest::Expectations

# spec/support/mini_test/expectations.rb

module MiniTest::Expectations

  infect_an_assertion :assert_dom_equal, :must_be_dom_equal, :reverse

end

The first argument refers to the assertion method we should call, the second argument is the name of the expectation we're defining. The reverse argument states whether or not we want to define an opposing expectation (wont_be_dom_equal)

So far, Miniest seems really cool. I'll posting more on this topic as I discover more in the near future.

If you'd like to check out the box_of_tricks gem, you can find it on github: github.com/KatanaCode/box_of_tricks

0 comments