Как использовать канкан для авторизации массива ресурсов?

У меня есть неуспокоительный контроллер, который я пытаюсь использовать для авторизации канкана! способ применения разрешений.

У меня есть действие delete_multiple, которое начинается так

def delete_multiple
    @invoices = apparent_user.invoices.find(params[:invoice_ids])

Я хочу убедиться, что у пользователя есть разрешение на удаление всех этих счетов, прежде чем продолжить. Если я использую

authorize! :delete_multiple, @invoices

в разрешении отказано. Моя способность.rb включает в себя следующее

if user.admin?
  can :manage, :all
elsif user.approved_user?
  can [:read, :update, :destroy, :delete_multiple], Invoice, :user_id => user.id
end

Это вопрос циклического прохождения моего массива и индивидуального вызова авторизации или есть более разумный способ сделать что-то? Мне начинает казаться, что выполнять авторизацию вручную было бы проще, чем с помощью канкана для сложного неуспокоительного контроллера (хотя в моем приложении есть много других спокойных контроллеров, где он отлично работает).


person brad    schedule 27.03.2011    source источник


Ответы (2)


Немного поздно, но вы можете написать это в своем классе способностей.

can :delete_multiple, Array do |arr|
  arr.inject(true){|r, el| r && can?(:delete, el)}
end

ИЗМЕНИТЬ

Это можно записать также как:

can :delete_multiple, Array do |arr|
  arr.all? { |el| can?(:delete, el) }
end
person Gerry    schedule 15.09.2011
comment
Я даже не могу вспомнить, как я справился с этим в конце, но этот код так сильно перевернул мой мозг, что я должен проголосовать за него! - person brad; 15.09.2011
comment
Итак, у меня определена аналогичная :read_multiple способность. В верхней части моего контроллера у меня есть load_and_authorize_resource :through => :tickets, :except => :index. И в моем индексном методе у меня есть @claims = Claim.where(:claimant_provider_id => current_user.provider.id); authorize!(:read_multiple, @claims) unless @claims.empty?. Тем не менее, он всегда не авторизуется, даже если это необходимо. Я пытался добавить либо debugger, либо вызов raise внутри блока read_multiple, но ни один из них никогда не срабатывал, поэтому я не уверен, что эта возможность вообще используется. Есть идеи? - person Chris Bloom; 18.01.2013
comment
Ничего, решил свою проблему. Способность, определенная выше, проверяет класс Array, но @claims технически является классом ActiveRecord::Relation. Изменение его на @claims.all заставляет его вести себя так, как ожидалось. - person Chris Bloom; 18.01.2013
comment
Спасибо за обновления! Голосование за ответ 22-месячной давности и мгновенное редактирование дает мне SO-ner. Если бы я мог, +1 снова. - person lime; 04.07.2013
comment
@brad Я бы сказал, что если этот код исказил вам мозг, то он не заслуживает одобрения. Сложность не должна вознаграждаться. Держите его простым и прямолинейным. Я бы не хотел смотреть на этот код через несколько месяцев и пытаться понять, чего я пытался достичь. - person Joshua Pinter; 12.09.2014
comment
Вызов @claims.to_a более явный. - person Stefan Lyew; 10.03.2017
comment
Также хорошее место для определения способности было бы в инициализаторе. - person Stefan Lyew; 10.03.2017

Кажется, что authorize! работает только с одним экземпляром, а не с массивом. Вот как я обошел это с помощью Rails 3.2.3 и CanCan 1.6.7.

Основная идея состоит в том, чтобы подсчитать общее количество записей, которые пользователь пытается удалить, подсчитать записи, которые равны accessible_by (current_ability, :destroy), а затем сравнить подсчеты.

Если вам просто нужен массив записей, которые пользователь имеет право уничтожить, вы можете использовать массив, возвращаемый accessible_by (current_ability, :destroy). Однако я использую destroy_all, который работает непосредственно с моделью, поэтому я остановился на этом решении подсчета и сравнения.

Стоит проверить журнал разработки, чтобы увидеть, как выглядят два оператора SELECT COUNT: второй должен добавить фразы WHERE для ограничений авторизации, наложенных CanCan.

Мой пример касается удаления нескольких сообщений.

ability.rb

if user.role_atleast? :standard_user
  # Delete messages that user owns
  can [:destroy, :multidestroy], Message, :owner_id => user.id
end

messages_controller.rb

# Suppress load_and_authorize_resource for actions that need special handling:
load_and_authorize_resource :except => :multidestroy
# Bypass CanCan's ApplicationController#check_authorization requirement:
skip_authorization_check :only => :multidestroy

...

def multidestroy
  # Destroy multiple records (selected via check boxes) with one action.
  @messages = Message.scoped_by_id(params[:message_ids]) # if check box checked
  to_destroy_count =  @messages.size
  @messages = @messages.accessible_by(current_ability, :destroy) # can? destroy
  authorized_count =  @messages.size

  if to_destroy_count != authorized_count
    raise CanCan::AccessDenied.new # rescue should redirect and display message
  else # user is authorized to destroy all selected records
    if to_destroy_count > 0
      Message.destroy_all :id => params[:message_ids]
      flash[:success] = "Permanently deleted messages"
    end
    redirect_to :back
  end
end 
person Mark Berry    schedule 17.08.2012
comment
Спасибо за этот Марк! Я пошел тем же путем, но создал его как расширение моего файла способностей. Вы можете найти реализацию с примером здесь: gist.github.com/chriscz/ac35172e0870248af16b23475a7ddb72 - person chriscz; 11.06.2021