I’ve written before about lightweight access control using inherited_resource’s begin_of_association_chain and raising a 404.
I still prefer this approach in the vast majority of apps I work on, but it doesn’t work in the case where there is no relationship between a user and an object other than access control.

Last week, I had a need to differentiate access control to some controller actions based on whether a signed in user was a teacher or student.
This is a simple need, and I’m loathe to introduce a large gem into the app for what could just be a custom before_filter:
class CoursesController < InheritedResources::Base
actions :new, :show, :index
before_filter :authenticate
before_filter :deny_student, :only => [:new]
protected
def deny_student
deny_access if current_user.student?
end
end
However, I recently saw acl9 mentioned on a mailing list and really liked the DSL. Couldn’t we have the best of both worlds?
So, Josh Clayton and I wrote up a sub-set of acl9’s DSL that would meet my needs.
module AccessControl
def self.included(controller)
controller.extend(ClassMethods)
controller.send(:include, InstanceMethods)
end
module ClassMethods
def access_control(options = {}, &block)
before_filter(options) do |controller|
controller.authenticate
end
before_filter(options) do |controller|
controller.instance_eval(&block)
end
end
end
module InstanceMethods
def allow(role, opts = {})
if opts[:to].nil? || opts[:to].include?(action_name.to_sym)
unless current_user.send("#{role}?")
deny_access(opts[:flash])
end
end
end
def deny(role, opts = {})
if opts[:from].nil? || opts[:from].include?(action_name.to_sym)
if current_user.send("#{role}?")
deny_access(opts[:flash])
end
end
end
end
end
ActionController::Base.send :include, AccessControl

In action:
class CoursesController < InheritedResources::Base
actions :new, :show, :index
access_control do
allow :teacher, :to => [:new]
end
end
Very expressive. Nice work, Oleg Dashevskii!

UserThis will usually be included when most actions require a signed in user. Therefore, we call authenticate and allow an ugly override for edge cases:
class CoursesController < InheritedResources::Base
actions :new, :show, :index
access_control(:except => :show) do
allow :teacher, :to => [:new]
end
end
This is used with Clearance and can therefore rely on authenticate, current_user, and deny_access.

Writing code with a pretty DSL in mind is exceedingly fun. The one downside seems to be that scope can become confusing.
Yesterday I was messing with Rails app templates and read the source for the 2.3 template runner. It, like this custom access control DSL, also uses instance_eval as the key line to make things work.
In the app template example, it was hard to figure out how to get the file path of the original template.
I simply wanted to require 'helper', a separate file in the app template, but because of the scope within which it was instance_eval‘d, helper could not be found.
in_root { self.instance_eval(code) }
The solution in that case was to take advantage of other public methods and mess with the load path, which would probably an atrocious solution for a vendored gem in a Rails app, but acceptable for a one-off script to generate a Rails app:
here = File.expand_path(File.dirname(template), File.join(root,'..'))
$LOAD_PATH << here
require 'helper'
In the access control example, it was hard to figure out how to have access to flash, redirect_to, etc. from a class-level scope.
Similar to the require 'helper' example, your tests will fail if:
deny and access are defined at the class levelinstance_eval is not used to delay evaluation until runtimebefore_filter’s blockThe solution is to use blocks and lazy evaluation to control scope, but it can be confusing to get there:
before_filter(options) do |controller|
controller.authenticate
end
before_filter(options) do |controller|
controller.instance_eval(&block)
end
Every time you decide to roll your own, remind yourself that you may waste time getting lost in something like a scope problem you haven’t seen before. Then, once you get the rhythm down, be careful of a thought like “it won’t take me that long to write.” Re-inventing the wheel has to be balanced with a specific reason in addition to, “it will be fun for me.”
The third-party ecosystem of gems and plugins is one reason why Rails is awesome. However, writing custom code every now and again is worth it for programmer pleasure, fewer lines of code and dependencies, and staying focused on only what you need.
Ruby makes great DSLs, though, so don’t be afraid to take inspiration from existing code and try your hand at making beautiful code for a specific purpose.