I have a few before filters which I am using to control access to resources on a resource-by-resource level. The basic idea is as follows:
- A user can be a
useroradminand can have access to specific resources based on an “accesses” table. - Resources/methods can be limited in access to
admin,owner, particular users, or everyone.
This is best illustrated by some code examples. We have 4 application-level methods that are added to the call chain with before_filter. Here is the top of an example controller class:
before_filter :require_user
before_filter :get_object, :only=>[:show, :edit, :update, :destroy]
before_filter :require_access, :only=>[:show]
before_filter :require_owner, :only=>[:edit, :update, :destroy]
As you can see, first we require that a user be logged in to access any method in this controller. Here are 3 fo the methods (defined in application.rb) so that you can see what they look like:
private
def get_object
begin
class_name = controller_name.gsub("Controller","").downcase.singularize
instance_variable_set "@#{class_name}".to_sym, class_name.capitalize.constantize.find(params[:id])
rescue
flash[:error] = "You do not have access to that #{class_name}."
redirect_to "/" and return
end
end
private
def require_owner
class_name = controller_name.gsub("Controller","").downcase.singularize
accessable = instance_variable_get("@#{class_name.downcase}")
unless accessable.user == current_user
flash[:error] = "You do not have access to that #{class_name.downcase}."
redirect_to "/" and return
end
end
private
def require_access
class_name = controller_name.gsub("Controller","").downcase.singularize
accessable = self.instance_variable_get("@#{class_name.downcase}")
unless current_user.has_access?(accessable)
flash[:error] = "You do not have access to that #{class_name.downcase}."
redirect_to "/" and return
end
end
This is all fine, as far as I can tell, from a coding perspective. But it’s just so god-damn ugly! In particular the lines:
class_name = controller_name.gsub("Controller","").downcase.singularize
obj = instance_variable_get("@#{class_name.downcase}")
OR
instance_variable_set "@#{class_name}".to_sym, class_name.capitalize.constantize.find(params[:id])
Does anyone know of a bit more elegant way to do what I am doing here?
I don’t know if there’s a really clean way to do this, but here are a few suggestions:
First, create a controller
ResourceControllerand have all relevant controllers inherit from it. (If this authorization applies to all controllers you can just useApplicationController.)Now, implement a private method in the superclass called
model_name(like yourclass_name) so you don’t have to derive it every time you need it. And, you should be able to derive it by simply doing this:You can also implement a
modelmethod in the superclass which returns the actual class:At this point you might as well also add something like this:
I don’t see a quick way around using
instance_variable_get/setexcept for always using@objector something like it. But if you don’t want to do that, those lines are now a little simpler:At this point your code should be more readable, and a little prettier.
You might also want to look into what some of the recent Rails authorization plugins are doing, in particular cancan and declarative_authorization.