Совместимость Rails 5 между Paranoia и CanCanCan, скомпрометирована?

У меня точно такая же проблема, как описано в этой теме:

Rails 5 only_deleted с помощью cancancan #356

Я могу получить доступ к удаленной записи, например так:

    @area = Area.only_deleted.find(params[:id])

но если я добавлю load_and_authorize_resource к моему контроллеру он попытается выполнить запрос следующим образом:

    @area = Area.find(params[:id])

что приведет к ошибке, так как он не найдет запись с этим идентификатором в коллекции, гдеулисный_индикатор не равен нулю (не удаленные записи, цель самоцвета Паранойя).

Если я отключу load_and_authorize_resource для контроллера или для этого самого действия это решает ошибку, но это не решение, поскольку это означает потерю контроля авторизации.

Есть ли исправление для этого, или есть драгоценность авторизации, которая хорошо работает с Paranoia на Rails 5, на которую я мог бы переключиться?

Спасибо.

1 ответ

Решение

Итак, согласно документации на load_and_authorize_resource, метод попытается загрузить переменную экземпляра, если она еще не установлена, и не будет делать это, если есть переменная экземпляра set, и именно поэтому приложение ломалось:

class AreasController < ApplicationController

  load_and_authorize_resource

  before_action :set_area, only: [:show, :edit, :update, :destroy]

  ...     

  def set_area
    if session[:show_obsolete_records] == true
      @area = Area.only_deleted.find(params[:id])
    else
      @area = Area.find(params[:id])
    end
  end
end

load_and_authorize_resource запускается первым в списке, и поскольку перед его вызовом не было установлено никаких переменных экземпляра, @area = Area.find(params[:id]) сам по себе, что, очевидно, приводит к ошибке, так как паранойя перезаписывает методы поиска, чтобы включить условие, чтобы проверить, является ли deleted_at является NULL,

Например, при использовании обычного (без паранойи) Area.find(17), вы получите запрос на консоли:

Area Load (0.2ms)  SELECT  "areas".* FROM "areas" WHERE "areas"."id" = ? LIMIT ?  [["id", 17], ["LIMIT", 1]]

При использовании Paranoia вы получите этот запрос:

Area Load (0.2ms)  SELECT  "areas".* FROM "areas" WHERE ("areas"."deleted_at" IS NULL) AND "areas"."id" = ? LIMIT ?  [["id", 17], ["LIMIT", 1]]

Таким образом, удаленные записи не будут найдены в общих запросах, так как они будут иметь deleted_at отметка времени установлена ​​(deleted_at сейчас NOT NULL).

Чтобы получить доступ к удаленным записям, вы должны использовать либо with_deleted или же only_deleted, лайк

@area = Area.only_deleted.find(params[:id])

иначе он не найдет удаленную запись, поэтому я и получил ошибку

ActiveRecord::RecordNotFound - Couldn't find Area with 'id'=16 [WHERE "areas"."deleted_at" IS NULL]:

Метод load_and_authorize_resource нагруженный @area = Area.find(params[:id]) и пропустил set_area, так что вы можете удалить метод, и он все равно установит область, даже если кода там нет.

Решение состоит в том, чтобы просто переместить load_and_authorize_resource метод под списком обратных вызовов:

class AreasController < ApplicationController

  before_action :set_area, only: [:show, :edit, :update, :destroy]

  load_and_authorize_resource

  ...     

  def set_area
    if session[:show_obsolete_records] == true
      @area = Area.only_deleted.find(params[:id])
    else
      @area = Area.find(params[:id])
    end
  end
end

ОБНОВИТЬ

Вы можете оставить вызов метода load_and_authorize_resource в верхней части стека, но измените его на authorize_resource поэтому он не пытается позвонить @area = Area.find(params[:id])Согласно этой теме.

class AreasController < ApplicationController

  authorize_resource

  before_action :set_area, only: [:show, :edit, :update, :destroy]

  ...     

  def set_area
    if session[:show_obsolete_records] == true
      @area = Area.only_deleted.find(params[:id])
    else
      @area = Area.find(params[:id])
    end
  end
end
Другие вопросы по тегам