DeserializationError when not found with default_scope
philipgiuliani opened this issue · comments
When i have set a default_scope to my model, GlobalID can't find it. https://github.com/rails/globalid/blob/master/lib/global_id/locator.rb#L130
Error while trying to deserialize arguments: Couldn't find Image with 'id'=75
Maybe it should always be set to unscoped
?
If you have a default_scope
it would make more sense to me that it should be applied by default 😄
But I haven't seen the code. Can you paste something that approximates the scenario?
I dont think that it should be applied for mailers/jobs (internal logic)
A little example for my use case:
# User Model
class User < ActiveRecord::Base
default_scope { where(status: :active) }
enum status: [:active, :blocked]
end
# Some method somewhere
def report_user
user = User.find(params[:id])
user.blocked!
AdminMailer.user_blocked(user).deliver_later
end
This example will fail, because the default scope is blocked of course. Currently i am passing user_id instead of the user instead as workaround.
What do you think?
Default scope should be applied here as well. Otherwise applying it sporadically and nondeterministically doesn't make sense to me.
The way I read your code, you only want active users in 99% of the cases, but in this one case where you're dealing with blocked users you should use unscope. ❤️
Thanks for the answer and explanation. With using unscope you mean passing the user_id instead, right?
The way I read your code, you only want active users in 99% of the cases, but in this one case where you're dealing with blocked users you should use unscope.
meaning don't use GlobalID
(aka "good ol pass by id")? or there's a way to specify for unscope
?
I mean use the unscoped
method:
def report_user
User.unscoped do
user = User.find(params[:id])
user.blocked!
AdminMailer.user_blocked(user).deliver_later
end
end
Eh, I just figured that might not work when GlobalID later calls find
because deliver_later
returns immediately.
Yes thats what i meant.. :D
Yeah.. this is tricky. Do we need some sort of AR::Base.definitely_find(id)
? I certainly agree that unlike a random User.find(params[:id])
, if we know we started out with an extant User instance, it does rather seem that it's on us to make sure we manage to get it back again.
Anything we do here is going to change the API required from a Global ID-supporting model class... but I think directly relying on unscoped
would be unreasonably broad. Maybe we should first try for no_really_do_actually_find
, and if it doesn't respond to that, fall back to regular find
? Without such a fallback, we would seem to have a rather unpleasant versioning/compatibility issue.
In GlobalID
there's a default AR finder:
class ActiveRecordFinder
def locate(gid)
gid.model_class.find gid.model_id
end
end
mattr_reader(:default_locator) { ActiveRecordFinder.new }
This can be changed to model_class.unscoped.find gid.model_id
.
The ActiveRecordFinder
is private and you shouldn't override locate in it. Instead you can use our custom app locators for this. Substitute bcx
for your app name here:
GlobalID::Locator.use 'bcx' do |gid|
gid.model_class.unscoped.find gid.model_gid
end
I also think changing the default ActiveRecordFinder
would be the cleanest and simplest way. Since its only for AR there should be no compability issues.
Well, the name is a little misleading. It's the default locator and is for any model that implements find
which takes an id.
find
is the contract for models to be locatable through Global ID. So if we add unscoped
or something that's another method for models to implement. Which Active Record models do by default, but it might not make sense for a lot of other models.
@kaspth we could Have a DefaultFinder
and an ActiveRecordFinder
and the default_locator
can be converted to default_locator_for(gid)
and based on the gid.model_class
decide if it should use DefaultFinder
or ActiveRecordFinder
Maybe we could do it as:
class DefaultFinder
def locate(gid)
gid.model_class.find(gid.model_id)
end
end
class ActiveRecordFinder < DefaultFinder
def locate(gid)
gid.model_class.unscoped { super }
end
end
@matthewd what do you think about this?