Resque Admin in Rails 3 Routes with CanCan
Resque is a background jobs queue that’s highly recommended over Delayed::Job if you are processing a lot of jobs. It uses Redis as the backend which doesn’t suffer from db related bottlenecks under high load.
Resque comes with a built-in admin interface that’s Rack compatible. In Rails 3, you can mount the Resque server admin directly in your routes.rb file.
mount Resque::Server, at: '/resque'
But you’ll definitely want to add password protection. Ryan Bates in his Resque RailsCast covers the basics of using Devise and HTTP auth. However, you’ll probably want to hook into your existing ACL system. In my case, I’m using CanCan.
CanCan is not available in the routes.rb by default, but it’s pretty easy to manually load the user and check permissions.
# routes.rb
namespace :admin do
constraints CanAccessResque do
mount Resque::Server, at: 'resque'
end
end
# config/initializers/admin.rb
class CanAccessResque
def self.matches?(request)
current_user = request.env['warden'].user
return false if current_user.blank?
Ability.new(current_user).can? :manage, Resque
end
end
# ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.is_admin?
can :manage, Resque
end
end
end
You’ll need User.is_admin? method or change the logic in Ability to suit your project.
Now an authenticated user with is_admin? == true will be able to access Resque admin. Other users will get a 404 since no route matches.
Thanks to Arcath’s blog post for initially pointing me in the right direction.
Jul 08, 2011 @ 19:37:45
Nice Joe. For people who aren’t using CanCan, it’s easy to just use a lambda:
constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? }
constraints constraint do
mount Resque::Server, :at => ‘/resque’
end
Jul 25, 2011 @ 08:55:10
Thanks for this great time-saver. I really like the completeness of your solution (and arcath’s by extension): tying everything together by using CanCan in the constraint is really nice and keeps authorization logic centralized. Sweet.
One thing I noticed is that Resque::Server is defined in ‘resque/server’. Since ‘resque/server’ calls `require ‘resque’` (ie., it’s a superset of `require ‘resque’`), I just updated my Gemfile require: `gem ‘resque’, require: ‘resque/server’`.
Aug 17, 2011 @ 20:10:38
Your solution works really well for my app. I found a few other posts on this topic and couldn’t get any of them to work cleanly as I kept running into Rack errors.
One nicety I added was a named route:
mount Resque::Server.new, :at => ‘resque’, :as => ‘resque_console’
Thanks for writing this up! Saved me a good deal of time.
osh
Sep 01, 2011 @ 23:48:49
Hello – pretty awesome, but sadly I’m getting:
undefined method `user’ for nil:NilClass
This seems to be an issue with request.env['warden'].user – any ideas?
@Jack, I tried your approach as well, got a NoMethod error for request.env["warden"].authenticate?
Sep 13, 2011 @ 17:36:20
@Jack Thanks nice and elegant solution!