For a new green-field project I just started, we needed a simple user system. And by simple, I mean we need an admin user and everyone else. There are plenty of options, a few of which I’m familiar with. I checked out Ruby Toolbox to get the overview of usage. I’ve heard lots of grumbling about Devise, though I’ve used it with no real problems. But it seemed a little heavy for what I was looking for. I decided to look into the two other packages recommended by cohorts and co-workers, Sorcery and Clearance.
Trying to stick with simple and clear, I decided to go with clearance. It appears to be built for easy overrides, but with most everything that you’d need. It also works as a Rails::Engine – which initially seems like a pretty good thing. But as you’ll see, that has it’s drawbacks.
For this system, we had 2 layouts – one for admin (which starts on top of Twitter/Bootstrap) and the other is custom. So I want all the admin pages to use the
layout/admin.html.erb. Seems straight-forward enough. With
clearance they suggest subclassing the controllers to make your overrides. So we add to the project
class UsersController < Clearance::UsersController layout 'admin' end
and the same for PasswordsController and Sessions Controller. I add some specs to validate:
describe UsersController do describe "#new" do before do get :new end it 'uses the admin layout' do expect(response).to render_template 'admin' end end end
which passes with flying colors.
Then I fire up a server and check it out. To my dismay, the tests lie. I see the application layout (not the admin layout). After some more digging, I figure out that my controller override is not getting picked up because the engine classes are loaded after my definition. And more investigation proves that this is *not* the case for the specs.
I see two possible solutions, none of which are great, but I’ll write them up here.
I thought, “if I just open up their class and force it to use my template, then we’re all done”.
class Clearance::UsersController layout 'admin' end
But that’s got it’s own issues. There’s a great discussion of the idea here. The summary is that when you include an Rails::Engine in your project, it’s classes are *eager_loaded* which means that any class reopen tricks you might play in the app will get ignored. So, the same post mentioned above talks about a solution which looks like
I did re-write all the controllers (passwords, sessions, users) as above and added this class reloader logic and things worked. But, it left a bad taste in my mouth. I recall, from my Java days, a co-worker who said
“If you are writing a custom boot-loader, you’re probably doing something wrong.”
‘Course he said this while he was in-deep writing a custom boot loader. But fundamentally, I got the point. And I feel like this little gist is kind of like rewriting a Java bootloader.
Manage with routes
I took these straight from the clearance routes and added them to my
# reroute clearance endpoints to use our derived controllers for auth resources :passwords, :controller => 'passwords', :only => [:create, :new] resource :session, :controller => 'sessions', :only => [:create, :new, :destroy] resources :users, :controller => 'users', :only => [:create, :new] do resource :password, :controller => 'passwords', :only => [:create, :edit, :update] end match 'sign_in' => 'sessions#new', :as => 'sign_in' match 'sign_out' => 'sessions#destroy', :as => 'sign_out', :via => :delete match 'sign_up' => 'users#new', :as => 'sign_up'
Then I subclassed controllers (as above) and my controllers start to pick up the actions.
I wanted to discourage sign up, so I added a simple template in
app/views/sessions/new.html.erbwhich successfully overrode the default provided by clearance.
This method seems a bit nicer, but the idea that I have to copy routes from the engine code just seems a little janky. On the other hand, this seemed to be a cleaner than the class-reopen option and being able to reuse views (except where I needed a little override) is nice.
In the end, we went with option 2 (routes and subclassed controllers). And it worked out pretty nicely.
If you’ve had similar issues, I’d love to hear your solution.
P.S. If you’re looking for Rails Auth systems there are piles of options. Here is a list of the top few that I’ve either used or heard folks talk about: