I keep a text file open as I work, and when I learn something, or fix a bug, I make a note of that in the text file. I call it my journal. It’s pretty low-tech, but text search turns out to be a sufficient way to look up past experiences when I find myself thinking “this problem seems so familiar”. There’s another benefit too: I find keeping simple notes about what I’m doing—what problems I’m solving, what approaches I’m using, what’s working and what could work better—keeps me focused on my goals. It turns out learning a programming language or a web framework is not as hard as training your mind to solve programming challenges efficiently.
Here are some things from my journal: I’m working on a Rails 3 app right now, something I plan to flog and promote mercilessly in upcoming posts. I’ve encountered some mysteries for which there was no simple HOWTO information easily available via web search. I thought I’d share the answers I cobbled together:
- How to get RSpec and RSpec-Rails for Rails 3;
- How to make devise routing work with related nested routes;
- How to spec controllers that use devise to restrict access to logged-in users;
- How to get
verify :method
to work in Rails 3.
Getting RSpec for Rails 3
There should be an official RSpec 2.0 release soon, and that version will work with Rails 3. But as of today, the official RSpec you’ll find at http://rspec.info is the 1.3.x branch for Rails 2.x.
The short version: this will get you the latest version of RSpec 2.0 and rspec-rails-2:
> gem install rspec-rails --pre
You must have recent versions of the Rails 3 gems installed (currently the latest is release candidate 1, which I use). If you haven’t started using rvm to manage your Ruby versions and gems, you should, immediately.
You’ll notice you get several RSpec gems when you install rspec-rails. RSpec is now all modular in keeping with the Rails 3 philosophy of having it your way. I recommend you read the ample information about Rspec-2 for Rails-3 on at the project’s github repo.
Routing with devise
I’d prefer to use OpenID for my app, but I fear trying to use Ruby’s gems for OpenID could be a time sink, so I’m going to start off with a userid/password-only solution. Fortunately, I don’t have to roll my own. There are plenty of good plugin gems out there. Since I’m working with Rails 3, I can use devise, which builds on a library called warden to provide a Rack application for authentication in Rails. Using devise, you get easily-configurable sign-up, sing-in, “remember me” functionality, password retrieval, and more. There is lots of good information about devise out there, but I want to make a few points about routing and RSpec.
If you’re planning RESTful routes for your app, and you’re modeling your users as the owners of those resources, you’ll want to have Rails routing build URLs like
users/1/someresources/2
The Rails way to do this is to set up a simple declaration in routes.rb that someresources are nested resources of users:
resources :users do resources :someresources end
The tricky thing, though, is doing this and using devise to manage user authentication using the User
model. You get a handy routing macro with devise: devise_for. You can tell Rails to use devise on your User
model with this line in routes.rb:
devise_for :users
The actual controller that handles user authentication actions is inside the devise gem. You don’t need a UsersController
class. However, you cannot treat devise_for
as a resources declaration and pass it a block for nested resources. This doesn’t work:
devise_for :users do resources :someresources end
If you were to add a separate declaration for users, like this…
devise_for :users resources :users do resources :someresources end
…then what if you decide you want a UsersController
, to show and edit profiles for instance? Things could get complicated. The solution is to give devise_for
an alternate path to use for user authentication, e.g. accounts
:
devise_for :users, :path => 'accounts' resources :users do resources :someresources end
It doesn’t matter what path
you use, since devise is going to use its own controller to handle authentication requests.
RSpec, devise, and controllers
Extending the theme of its out-of-the-box ease of use, devise lets you specify that a user must be logged in to use the methods of a controller with this simple directive:
before_filter :authenticate_user!
That's great. But if you have specs written for this controller, they will all begin failing the moment you add this directive, and the error you get back will not be that helpful:
undefined method `authenticate!' for nil:NilClass
Obviously RSpec doesn't know enough to simulate a logged-in user for devise. How do we teach it to mock such a condition?
I read through a long issue ticket on this spec'ing problem, in which the conversation starts out for earlier versions of RSpec and devise and works towards a general solution that I've cobbled together from different folks' posts. It works for me:
Correction: In my original code sample, below, I mocked up an object of the Warden
class with mock_model()
. Bad idea. Recent versions of RSpec and rspec-rails throw an error when you try to do that, since Warden
, being no kind of Rails class, does not extend ActiveModel::Naming
. Instead, you can mock it simply with mock()
.
describe RequestsController do include Devise::TestHelpers # to give your spec access to helpers def mock_user(stubs={}) @mock_user ||= mock_model(User, stubs).as_null_object end ... before(:each) do # mock up an authentication in the underlying warden library request.env['warden'] = mock(Warden, :authenticate => mock_user, :authenticate! => mock_user) end
verify this!
I'm a big fan of Rails' mechanisms for making sure the right HTTP method is used for destructive controller actions. At the least, I don't want GET requests to be able to update or delete resources. As far as I know, this verify
mechanism has been in Rails since 2.0, if not earlier:
verify :method => :delete, :only => [ :destroy ], :render => {:text => '405 HTTP DELETE required', :status => 405}, :add_headers => {'Allow' => 'DELETE'}
As of Rails 3.0, however, this verify method is now excluded from the Rails Core. You can, however, install it easily as a plugin:
> rails plugin install git://github.com/rails/verification.git
To do
As I wrote above, keeping a journal is a good way to keep track of how things could go better. From my notes, I read that RSpec's routing specs could better confirm nested routes. Also, some of devise's generators could be more expressive. Does devise work with reCAPTCHA? And, finally, an up-to-date OpenID authentication engine, possibly built on warden, would be teh hawtness. Thanks to the triumph of Open Source in the Ruby community, I don't have to just whine about these things. I could get working on projects and patches right now. If only blogging didn't take so much time in itself. :-)
One thought on “Developer’s journal: devise, Rails 3, RSpec, and more”
Comments are closed.