Совместимость 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