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.
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
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
...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.
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
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