سؤال

I've been struggling through this for some time and I've finally got to the point where it seems that CanCan doesn't allow you to authorize a collection of records. For example:

ads_controller.rb

def index
    @ads = Ad.where("ads.published_at >= ?", 30.days.ago).order("ads.published_at DESC")
    authorize! :read, @ads
end

ability.rb

def initialize(user)
  user ||= User.new # Guest user

  if user
    if user.role? :admin  # Logged in as admin
      can :manage, :all
    else                  # Logged in as general user
      can :read, Ad
      can :read_own, Ad, :user_id => user.id
      can :create, Ad
    end
  else                    # Not logged in (Guest)
    can :read, Ad
  end
end

This results the unauthorised access message when trying to access the index action.

You are not authorized to access this page.

However, if you change the authorize call in the index action to check on the Ad class rather than the collection like so

def index
    @ads = Ad.where("ads.published_at >= ?", 30.days.ago)
    authorize! :read, Ad
end

... it works fine.

Any help in explaining this one would be greatly appreciated.

Thanks in advance.

ps. I was originally getting redirect loops when trying to work this out. It turns out there's a gotchya with the recommended rescue_from that you put in the application controller to give you nice error messages. If your root_path is set to the same place where your authorize! call is not true (or failing), you'll get a redirect loop. Comment out the rescue_from Learnt that one the hard way.

هل كانت مفيدة؟

المحلول

CanCan is not designed to be used like that. You can check whether a user has permissions on the model class (e.g. Ad) or a single instance (e.g. @ad).

I suggest you just use accessible_by to filter your collection:

@ads = Ad.where("ads.published_at >= ?", 30.days.ago).accessible_by(current_ability) 
# @ads will be empty if none are accessible by current user

raise CanCan::AccessDenied if @ads.empty?  # handle however you like

Another approach would be to define a custom permission based on the conditions you use to retrieve the collection:

# ability.rb
can :read_ads_from_past_month, Ad, ["ads.published_at >= ?", 30.days.ago]

# in your controller
def index
  authorize! :read_ads_from_past_month, Ad
  @ads = Ad.where("ads.published_at >= ?", 30.days.ago)
end

نصائح أخرى

I solved this problem usings splats. In this code example, I am trying to authorize users on a collection of TimeOffRequests. They should be authorized if the User is an admin, a manager or the time off request belongs to them.

# time_off_requests_controller.rb
authorize! :read, *@time_off_requests

# Ability.rb
can :manage, TimeOffRequest do |*time_off_requests|
  membership.has_any_role?(:admin, :manager) ||
    time_off_requests.all? { |tor| membership.id == tor.employee_id }
end

I wrote about it in detail here if you're interested: http://zacstewart.com/2012/01/08/defining-abilities-for-collections-of-records-using-cancan.html

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top