Reputation: 4657
On my model MediaFileAsset, I have the following added via an included lib:
default_scope ->{eager_load(:service_asset_core).where(service_asset_cores: {archived: false})}
Leaving aside reasons to not use default_scopes, is there a way to use unscope to remove this scope from a query?
So, for example, maybe someone passes me
assets = MediaFileAsset.where(custom_conditions)
and I want to count all of these IGNORING the archived
field on service_asset_cores
. If archived
were a field of media_file_assets
, I could use
assets.unscope(where: :archived).count
But that doesn't work here because archived
is a field on ServiceAssetCore
, not MediaFileAsset
.
Using unscoped
and unscope(:where)
work to remove the archived condition, but these remove more than just that condition, which is not quite what I'm looking for. I believe this is why the unscope()
method was added in the first place, and I'm trying to learn if it's possible to use it to remove query conditions on joined tables.
For reference:
2.2.2 > MediaFileAsset.unscoped.count
(505.8ms) SELECT COUNT(*) FROM `media_file_assets`
=> 3078951
And to unscope just the specific where: :archived clause, I have tried:
2.2.2 > MediaFileAsset.unscope(where: {service_asset_cores: :archived}).count
(0.5ms) SELECT COUNT(DISTINCT `media_file_assets`.`id`) FROM `media_file_assets` LEFT OUTER JOIN `service_asset_cores` ON `service_asset_cores`.`service_asset_id` = `media_file_assets`.`id` AND `service_asset_cores`.`service_asset_type` = 'MediaFileAsset' WHERE `service_asset_cores`.`archived` = 0
=> 10
2.2.2 > MediaFileAsset.all.merge(ActsAsServiceAsset::ServiceAssetCore.unscoped).count
(0.5ms) SELECT COUNT(DISTINCT `media_file_assets`.`id`) FROM `media_file_assets` LEFT OUTER JOIN `service_asset_cores` ON `service_asset_cores`.`service_asset_id` = `media_file_assets`.`id` AND `service_asset_cores`.`service_asset_type` = 'MediaFileAsset' WHERE `service_asset_cores`.`archived` = 0
=> 10
2.2.2 > MediaFileAsset.unscope(where: :archived).count
(0.8ms) SELECT COUNT(DISTINCT `media_file_assets`.`id`) FROM `media_file_assets` LEFT OUTER JOIN `service_asset_cores` ON `service_asset_cores`.`service_asset_id` = `media_file_assets`.`id` AND `service_asset_cores`.`service_asset_type` = 'MediaFileAsset' WHERE `service_asset_cores`.`archived` =
Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1: SELECT COUNT(DISTINCT `media_file_assets`.`id`) FROM `media_file_assets` LEFT OUTER JOIN `service_asset_cores` ON `service_asset_cores`.`service_asset_id` = `media_file_assets`.`id` AND `service_asset_cores`.`service_asset_type` = 'MediaFileAsset' WHERE `service_asset_cores`.`archived` =
ActiveRecord::StatementInvalid: Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1: SELECT COUNT(DISTINCT `media_file_assets`.`id`) FROM `media_file_assets` LEFT OUTER JOIN `service_asset_cores` ON `service_asset_cores`.`service_asset_id` = `media_file_assets`.`id` AND `service_asset_cores`.`service_asset_type` = 'MediaFileAsset' WHERE `service_asset_cores`.`archived` =
2.2.2 > MediaFileAsset.all.merge(ActsAsServiceAsset::ServiceAssetCore.unscope(where: :archived)).count
# output omitted, same error as pervious
That last two seem like a Rails (4.2.3) bug since the SQL generated is invalid. Maybe this is where I need to be but the bug is in my way. Is there another method I should be trying, or am I stuck with the horrors of the unavoidable default_scope?
Upvotes: 4
Views: 8826
Reputation: 9248
Starting from Rails 6.1, it is possible to unscope only the column in the specified table.
posts = Post.joins(:comments).group(:"posts.hidden")
posts = posts.where("posts.hidden": false, "comments.hidden": false)
posts.count
# => { false => 10 }
# unscope both hidden columns
posts.unscope(where: :hidden).count
# => { false => 11, true => 1 }
# unscope only comments.hidden column
posts.unscope(where: :"comments.hidden").count
# => { false => 11 }
Here is a link to the corresponding PR.
So, I suppose, the following can solve your problem:
MediaFileAsset.unscope(where: :"service_asset_cores.archived").where(custom_conditions)
Upvotes: 1
Reputation: 2238
You're pretty close.
MediaFileAsset.unscoped returns an ActiveRecord::Relation, meaning you can call .count, .where, .includes,
etc on it.
So the full query would be:
MediaFileAsset.unscoped.where(service_assset_cores: :archived).count
Or, you could define a named scope on MediaFileAsset
called archived, and rescope it after you unscope it:
class MediaFileAsset < ActiveRecord::Base
scope :archived, { where service_asset_cores: :archived }
# ...
end
MediaFileAsset.unscoped.archived.count
def self.without_default_scope
klass = self.dup
klass.default_scopes = []
klass
end
Upvotes: 0