I'm working on a Rails 6.0 application that has the following soft delete ActiveRecord concern defined:
module ActiveRecordSoftDeletion
extend ActiveSupport::Concern
def self.included(base)
base.scope :not_deleted, -> { base.unscoped.where(deleted: false) }
base.send(:default_scope) { base.not_deleted }
base.scope :only_deleted, -> { base.unscope(where: :deleted).where(deleted: true) }
def destroy
self.deleted = true
self.save(validate: false)
end
def recover
self.deleted = false
self.save(validate: false)
end
end
end
The concern has worked fine for a long time and is widely used throughout the application.
Recently we introduced single table inheritance, which was also working fine until we decided to include ActiveRecordSoftDeletion to the base class.
Here's an example of my STI classes...
class Widget < ApplicationRecord
include ActiveRecordSoftDeletion
end
class Thingy < Widget
end
class Dohicky < Widget
end
Before adding ActiveRecordSoftDeletion to Widget, the code behaved like this...
> Thingy.first
SELECT "widgets".* FROM "widgets" WHERE "widgets"."type" = $1
ORDER BY "widgets"."id" ASC LIMIT $2 [["type", "Thingy"], ["LIMIT", 1]]
> Dohicky.first
SELECT "widgets".* FROM "widgets" WHERE "widgets"."type" = $1
ORDER BY "widgets"."id" ASC LIMIT $2 [["type", "Dohicky"], ["LIMIT", 1]]
The problem is ActiveRecordSoftDeletion is replacing the default scope. So after including ActiveRecordSoftDeletion the queries look like this...
> Thingy.first
SELECT "widgets".* FROM "widgets" WHERE "widgets"."deleted" = $1
ORDER BY "widgets"."id" ASC LIMIT $2 [["deleted", false], ["LIMIT", 1]]
> Dohicky.first
SELECT "widgets".* FROM "widgets" WHERE "widgets"."deleted" = $1
ORDER BY "widgets"."id" ASC LIMIT $2 [["deleted", false], ["LIMIT", 1]]
I'd like to avoid changing anything in ActiveRecordSoftDeletion if possible, since that code is used in many places. But I can't find a way to get the STI classes to add their default scope back.
The documentation for scopes clearly states if you have multiple default scopes they get ANDed together but something about the way the concern is doing things seems to replace the default scope rather than add to it.
Adding the default scope to the STI classes almost works but not quite.
If I change the base class to this...
class Widget < ApplicationRecord
include ActiveRecordSoftDeletion
default_scope { where(type: sti_name) }
end
Then all my queries become...
SELECT "widgets".* FROM "widgets"
WHERE "widgets"."deleted" = $1 AND "widgets"."type" = $2
ORDER BY "widgets"."id" ASC LIMIT $3
[["deleted", false], ["type", "Widget"], ["LIMIT", 1]]
even for the subclasses; they all look for type Widget.
Even doing this has the same result...
class Widget < ApplicationRecord
include ActiveRecordSoftDeletion
end
class Thingy < Widget
default_scope { where(type: sti_name) }
end
class Dohicky < Widget
default_scope { where(type: sti_name) }
end
How can I add the correct STI default scope back to the models without changing the soft delete code?
Alternately, how can I update the soft delete code without impacting other models that use the concern?