Clearance is now a Rails engine… BLAOW!
Clearance has served us well for many months. Our only complaints were shared by others:
With the re-institution of Rails engines in Rails 2.3, we decided to convert Clearance to engine. The process was relatively painless, the code is far cleaner, and we think we were able to scratch all our itches.
We highly recommend that you use the Cucumber features that come with Clearance to test the integration of the engine with your app:
You no longer run Clearance’s generated Shoulda & Factory Girl tests within your test suite. That code is unit tested internally. Use the Cucumber features to test integration. If and when you override functionality, write your own unit tests.
Read Mixing Cucumber with Test::Unit/Shoulda if you’re getting started with Cucumber and not using RSpec.
We haven’t and probably won’t ever move Clearance beyond email and password authentication, despite frequent requests. We’re focused on “clean code that works” for the baseline authentication we’ve written over and over again for clients.
A huge part of “clean code that works” means that overriding Clearance needs to be painless. The change to an engine helps achieve that goal.
class UsersController < Clearance::UsersController def edit ... end end ActionController::Routing::Routes.draw do |map| map.resources :users end
All knowledge pertaining to Clearance can be found on its Github wiki, where you’ll find such articles as:
.. and much more.
UPDATE: After about two years of using this approach in Clearance, we removed the 403 Forbidden feature in Clearance. We discovered that setting the 403 status code turned out to be a bad user experience in some browsers such as Chrome on Windows machines. Philosophically, we decided we value user experience over technical purity.
Clearance tries to be fluent in HTTP. That means a few things:
<ul> <li>Know when to return which <span class="caps">HTTP</span> status codes.</li> <li>Know when to raise errors.</li> </ul> <h2>401 Unauthorized</h2> <p>In layman’s terms:</p> <blockquote> <p>“Specifically for use when authentication is possible but has failed or not yet been provided.”</p> </blockquote> <p>The response is <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error">401 Unauthorized</a> “out of the box” with Clearance when:</p> <ul> <li>A user tries to sign in with bad credentials.</li> <li>A user without confirmed email tries to sign in.</li> </ul> <p>If you protect an action with <code>before_filter :authenticate</code> in your app, Clearance will also return 401 Unauthorized when:</p> <ul> <li>A user who is not signed in tries to access that action.</li> </ul> <h2>403 Forbidden</h2> <p>In layman’s terms:</p> <blockquote> <p>“The request was a legal request, but the server is refusing to respond to it.<br />
Unlike a 401 Unauthorized response, authenticating will make no difference.”
<p><img src="http://ui.thoughtbot.com/assets/2009-2-22-forbdden_kiss.jpg" style="float:right;" />The response is <a href="http://en.wikipedia.org/wiki/HTTP_403">403 Forbidden</a> “out of the box” with Clearance when:</p> <ul> <li>A user tries to confirm a user with confirmed email.</li> <li>A user tries to confirm a user without a token.</li> <li>A user tries to confirm a user without the correct token for an unconfirmed user.</li> <li>A user tries to edit a user’s password without a token.</li> <li>A user tries to update a user’s password without a token.</li> <li>A user tries to edit a user’s password without the correct token for the user.</li> <li>A user tries to update a user’s password without the correct token for the user.</li> </ul> <p>These are legal requests by someone or something (maybe a malicious user) requesting actions in forbidden, exceptional ways. They are not available to any user, regardless of their authentication status. The server should refuse to respond to it.</p> <h2>When to raise errors</h2> <p>Consider a typical edit, show, or destroy action:</p> <pre><code>def show
@user = User.find(params[:id]) end
<p>In the development and test environments, this will raise a <code>ActiveRecord::RecordNotFound</code> error if a User does not exist for the given id. In production, this will return 404 Not Found instead of 500 Internal Server Error.</p> <p>Rails does this by rescuing the <code>ActiveRecord::RecordNotFound</code> error for <strong>public requests</strong> (for example, staging or production environments). Inside the rescue, it returns the logical status code, :not_found. For <a href="http://api.rubyonrails.org/classes/ActionController/Rescue.html#M000397"><strong>local requests</strong></a> (for example, development or test environments), the error is not rescued.</p> <p>Rails provides similar functionality for other errors:</p> <pre><code>'ActionController::RoutingError' => :not_found,
‘ActionController::UnknownAction’ => :not_found, ‘ActiveRecord::RecordNotFound’ => :not_found, ‘ActiveRecord::StaleObjectError’ => :conflict, ‘ActiveRecord::RecordInvalid’ => :unprocessable_entity, ‘ActiveRecord::RecordNotSaved’ => :unprocessable_entity, ‘ActionController::MethodNotAllowed’ => :method_not_allowed, ‘ActionController::NotImplemented’ => :not_implemented, ‘ActionController::InvalidAuthenticityToken’ => :unprocessable_entity
<p>This maps errors to <span class="caps">HTTP</span> status codes.</p> <p>Clearance creates a custom error, <code>ActionController::Forbidden</code>, and maps it to <code>:forbidden</code> to match this convention.</p> <p>So when situations arise when 403 Forbidden is called for, Clearance simply does:</p> <pre><code>raise ActionController::Forbidden</code></pre> <p>The effect is exactly like <code>ActiveRecord::RecordNotFound</code>. In development and test environments, the developer has the opportunity to investigate what is going wrong. In staging and production, the app behaves like a good internet citizen by responding with the correct <span class="caps">HTTP</span> status code.</p> <p><em>Note:</em> One could argue that Rails could provide a mapping like this for all <span class="caps">HTTP</span> status codes, or at least a few more of the most common ones. A patch for another day, perhaps.</p> <h2>Attribution</h2> <p>These ideas were lifted from the good work coming out of the <a href="http://merbivore.com">Merb</a> community. The implementation was driven out through conversations with Tim Pope, Joe Ferris, Mike Burns, and Jason Morrison.</p> <p><a href="http://github.com/thoughtbot/clearance">Clearance is on github</a>.</p>