in my rails app (I use devise and cancan), each (registered) user belongs to exactly one role ('Administrator' or 'Users') but to at least one group (something like 'Family', 'Friends', 'Co-workers'). At runtime, when a new folder (see below) is created, a habtm relation to one or many groups can be set, which defines who can access the folder. Selecting no group at all should result in a world-wide accessible folder (i.e. users do not have to be logged in to access these folders). But right now, I don't know yet, how to define such world-wide accessible folders in my ability.rb, because I do not know how to define "can read folders which have no groups associated to it".
The relevant snippet of my app/models/ability.rb
looks like this:
user ||= User.new
if user.role? :Administrator
can :manage, :all
elsif user.role? :Users
# user should only be able to read folders, whose associated groups they are member of
can :read, Folder, :groups => { :id => user.group_ids }
else
# here goes the world-wide-accessible-folders part, I guess
# but I don't know how to define it:
## can :read, Folder, :groups => { 0 } ???
end
The relevant snippet of my app/controllers/folders_controller.rb
looks like this:
class FoldersController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource
Can someone give me a hint?
I had the same problem just the other day. I figured out the solution after reading the CanCan readme, which you should do if you haven't yet.
You can view my solution here: Context aware authorization using CanCan
To give you an answer more specific to your use case, do the follow:
In your application controller you need to define some logic which will pick your abilities.
class ApplicationController < ActionController::Base
check_authorization
def current_ability
if <no group selected logic> # Maybe: params[:controller] == 'groups'
@current_ability = NoGroupSelectedAbility.new(current_user)
else
@current_ability = GroupSelectedAbility.new(current_user)
end
end
# Application Controller Logic Below
end
You'll then need to create a new ability (or abilities) in your app/models/ folder. You can also do cool stuff like this:
if request.path_parameters[:controller] == groups
@current_ability = GroupsAbility.new(current_group_relation)
end
Where current_group_relation is defined in app/controllers/groups_controller.rb. This will give you specific abilities for specific controllers. Remember that a parent classes can call methods in child classes in Ruby. You can define a method in your controller, and call it from ApplicationController, as long as you are certain what controller is currently being used to handle the request.
Hope that helps.
EDIT: I wanted to show you what a custom ability looks like.
# File path: app/models/group_ability.rb
class GroupsAbility
include CanCan::Ability
# This can take in whatever you want, you decide what to argument to
# use in your Application Controller
def initialize(group_relation)
group_relation ||= GroupRelation.new
if group_relation.id.nil?
# User does not have a relation to a group
can :read, all
elsif group_relation.moderator?
# Allow a mod to manage all group relations associated with the mod's group.
can :manage, :all, :id => group_relation.group.id
end
end
end