I am writing a web app to pick random lists of cards from larger, complete sets of cards. I have a Card model and a CardSet model. Both models have a full RESTful set of 7 actions (:index, :new, :show, etc). The CardSetsController has an extra action for creating random sets: :random
.
# app/models/card_set.rb
class CardSet < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
has_many :memberships
has_many :cards, :through => :memberships
# app/models/card.rb
class Card < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
has_many :memberships
has_many :card_sets, :through => :memberships
I have added Devise for authentication and CanCan for authorizations. I have users with an 'editor' role. Editors are allowed to create new CardSets. Guest users (Users who have not logged in) can only use the :index
and :show
actions. These authorizations are working as designed. Editors can currently use both the :random
and the :new
actions without any problems. Guest users, as expected, cannot.
# app/controllers/card_sets_controller.rb
class CardSetsController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index]
load_and_authorize_resource
I want to allow guest users to use the :random
action, but not the :new
action. In other words, they can see new random sets, but not save them. The "Save" button on the :random
action's view is hidden (as designed) from the guest users. The problem is, the first thing the :random
action does is build a new instance of the CardSet model to fill out the view. When cancan tries to load_and_authorize_resource
a new CardSet, it throws a CanCan::AccessDenied exception. Therefore, the view never loads and the guest user is served a "You need to sign in or sign up before continuing" message.
# app/controllers/card_sets_controllers.rb
def random
@card_set = CardSet.new( :name => "New Set of 10", :set_type => "Set of 10" )
I realize that I can tell load_and_authorize_resource
to skip the :random
action by passing :except => :random
to the call, but that just feels "wrong" for some reason.
What's the "right" way to do this? Should I create the new random set without instantiating a new CardSet? Should I go ahead and add the exception?
Update
I didn't include my Ability class above. I've updated it to include the ':random' action, but it still isn't working quite right.
class Ability
include CanCan::Ability
def initialize( user )
user ||= User.new # User hasn't logged in
if user.admin?
can :manage, :all if user.admin?
else
# All users, including guests:
can :read, [Card, CardSet]
can :random, CardSet
# All users, except guests:
can :create, [Card, CardSet] unless user.role.nil?
can :update, [Card, CardSet] do |c|
c.try( :creator ) == user || user.editor?
end
if user.editor?
can [:create, :update], [Card, CardSet]
end
end
end
end