I'm having the exact same issue as described on this thread:
Rails 5 only_deleted with cancancan #356
I can access a deleted record, like this:
@area = Area.only_deleted.find(params[:id])
but if I add load_and_authorize_resource
to my controller, it'll attempt to run the query like this:
@area = Area.find(params[:id])
which will result in error since it won't find a record with that id on a collection where deleted_at isn't null (not deleted records, the purpose of the Paranoia gem).
If I disable load_and_authorize_resource
for the controller or for that very action, it solves the error but it's not a solution since that means losing authorization control.
Is there a fix for this, or is there an authorization gem which plays nice with Paranoia on Rails 5 which I could switch over to?
Thank you.
So, according to documentation on load_and_authorize_resource, the method will attempt to load an instance variable in case one hasn't been set yet, and won't do so if there's a set instance variable, which is precisely why the application was breaking:
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
runs first on the list, and since there were no instance variables set before its call, it does @area = Area.find(params[:id])
on its own account, which obviously leads to error, since Paranoia overwrittes finder methods to include a condition to check whether the deleted_at
is NULL
.
For example, when using the regular (without Paranoia) Area.find(17)
, you get a query like this on your console:
Area Load (0.2ms) SELECT "areas".* FROM "areas" WHERE "areas"."id" = ? LIMIT ? [["id", 17], ["LIMIT", 1]]
When using Paranoia, you'd get this query:
Area Load (0.2ms) SELECT "areas".* FROM "areas" WHERE ("areas"."deleted_at" IS NULL) AND "areas"."id" = ? LIMIT ? [["id", 17], ["LIMIT", 1]]
This way, records that have been deleted won't be found on common queries since they'll have the deleted_at
timestamp set (deleted_at
is now NOT NULL
).
To access deleted records, you must use either with_deleted
or only_deleted
, like
@area = Area.only_deleted.find(params[:id])
or else it won't find the deleted record, hence why I was getting the error
ActiveRecord::RecordNotFound - Couldn't find Area with 'id'=16 [WHERE "areas"."deleted_at" IS NULL]:
The method load_and_authorize_resource
loaded @area = Area.find(params[:id])
and skipped set_area
, so you could delete the method and it would still set the area even if the code is not there.
The solution is to simply move the load_and_authorize_resource
method below the callbacks list:
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
UPDATE
You can leave the method call load_and_authorize_resource
at the top at the stack, but change it to authorize_resource
so it doesn't attempt to call @area = Area.find(params[:id])
, according to this thread.
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