views:

329

answers:

2

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
A: 

Well, the right thing would be to use CanCan ability class to define the correct authorization rules.

in Ability.rb

  def initialize(user)
#everyone
    can [:read, :random], [CardSet]
#everyone who is editor
    if user.editor?
      can [:new, :create], [CardSet]

etc

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.

While CanCan authorizes controller action, building a new instance in random action (i.e. CardSet.new) is not in the scope of CanCan. You probably get the error because you have no rules defined in Ability.rb for random action. My example above should solve your problem

Tadas Tamosauskas
I updated my question to include my Ability class. I tried adding the `can :random, CardSet` to my Ability class this morning, but it didn't seem to work.
irkenInvader
A: 

I found my problem. CanCan was not the issue at all! The following line in my CardSet controller was throwing the exception and redirecting my guest (not logged in) users to the log in page:

before_filter :authenticate_user!, :except => [:show, :index]

I changed it to read:

before_filter :authenticate_user!, :except => [:show, :index, :random]

And now the code works as intended: Guest users can view the new random sets created, but cannot 'Save' them unless they first log in.

So, my real problem was with Devise (or, actually, my Devise configuration) and not with CanCan at all.

irkenInvader