We use a few Rails engines such as High Voltage and Clearance which allow you to override default behaviors of controllers by subclassing them like:
class SessionsController < Clearance::SessionsController
layout 'public-facing'
end
Meanwhile, we often work on apps which require most of the controllers to be authorized. If we wanted to have a home page that uses High Voltage, one way we might do it is:
class ApplicationController < ActionController::Base
include Clearance::Authentication # or other authentication mechanism
before_filter :authorize
end
class PagesController < HighVoltage::PagesController
skip_before_filter :authorize
end
MyApp::Application.routes.draw do
# all the authorized routes
resources :pages
end
It’s annoying to have a new route plus a new controller and to define and
override a before_filter
. Meanwhile, the authorization logic is “hidden” in
ApplicationController
.
Another way is:
class ApplicationController < ActionController::Base
include Clearance::Authentication
end
class CommentsController < ApplicationController
before_filter :authorize
end
For each controller, you call the before_filter
. It’s declarative in the sense
that when you read the CommentsController
class, you can see it’s protected by
authorization. The downsides are:
- you have to remember to write it for each controller
- you have to write some custom authorization specs for the controller
Another way is:
class ApplicationController < ActionController::Base
include Clearance::Authentication
end
class AuthorizedController < ApplicationController
before_filter :authorize
end
Now, we subclass from AuthorizedController
for only those controllers that need
the functionality.
class CommentsController < AuthorizedController
# ...
end
Again, it’s declarative, we can read the class and know it’s authorized. Again, it requires we remember, which means we might forget.
I’d argue that if we TDD the controller, we shouldn’t forget.
The advantage of subclassing from the intermediate AuthorizedController over
the before_filter :authorize in each controller
style means we can feel
confident with a spec like:
describe CommentsController do
it { should be_kind_of(AuthorizedController) }
end
That assumes we have a spec somewhere like:
class MocksController < AuthorizedController
def show
end
end
describe MocksController do
before do
MyApp::Application.routes.draw do
resource :mock, :only => [:show]
match 'sign_in' => 'sessions#new', :as => :sign_in
end
end
after do
Rails.application.reload_routes!
end
context "a visitor" do
it "is denied access to blank slate" do
get :show
should redirect_to(sign_in_url)
end
end
end
If this style gains a little support, we could put AuthorizedController
and
this spec into Clearance itself so it wouldn’t need to be hanging around in
each of our app’s codebases.
I feel like it’s the right amount of work, with the right amount of test coverage, without extraneous subclassing and routes.
Which approach do you prefer? Something different?