【问题标题】:How to unscope a where clause on a joined ActiveRecord model如何在加入的 ActiveRecord 模型上取消 where 子句的范围
【发布时间】:2015-11-13 20:20:00
【问题描述】:

在我的模型 MediaFileAsset 上,我通过包含的库添加了以下内容:

default_scope ->{eager_load(:service_asset_core).where(service_asset_cores: {archived: false})}

抛开不使用 default_scopes 的原因,有没有办法使用 unscope 从查询中删除此范围?

例如,也许有人经过我

assets = MediaFileAsset.where(custom_conditions)

我想计算所有这些忽略service_asset_cores 上的archived 字段。如果archivedmedia_file_assets 的字段,我可以使用

assets.unscope(where: :archived).count

但这在这里不起作用,因为archivedServiceAssetCore 上的一个字段,而不是MediaFileAsset

使用unscopedunscope(:where) 可以删除存档条件,但这些删除的不仅仅是那个条件,这不是我想要的。我相信这就是为什么首先添加 unscope() 方法的原因,我正在尝试了解是否可以使用它来删除连接表上的查询条件。

供参考:

2.2.2 > MediaFileAsset.unscoped.count
   (505.8ms)  SELECT COUNT(*) FROM `media_file_assets`
 => 3078951

为了取消具体的 where: :archived 子句的范围,我已经尝试过:

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

最后两个似乎是 Rails (4.2.3) 错误,因为生成的 SQL 无效。也许这就是我需要去的地方,但错误就在我的路上。我应该尝试另一种方法,还是我被不可避免的 default_scope 的恐怖所困扰?

【问题讨论】:

  • 我没用过unscoped,但也许unscoped.where(service_asset_cores: :archived)
  • 不幸的是无法工作。 unscoped 删除所有以前附加的范围和查询方法。 unscope(*args) 可以删除特定的查询,但我无法删除连接模型上的查询,这就是问题所在。
  • 啊,我的意思是坐在unscope 而不是unscoped,但我猜这仍然行不通。如果有任何方法可以删除默认范围,但我认为您还有其他考虑因素会阻止...
  • 是的。该库将包含在数十个普遍存在的模型中,并且整个开发团队都非常倾向于不必在任何地方指定诸如“活动”之类的明确范围,因为大多数其他开发人员对存档资产不感兴趣.我试图让他们留在身边以分析错误和其他故障,但我必须对其他开发人员的担忧透明地这样做。一种可能的解决方案是放弃 service_asset_cores 表并将列添加到包含该模块的每个模型中,但这是 (a) 大量迁移,并且 (b) 对结构更改的鲁棒性较差。

标签: ruby-on-rails ruby-on-rails-4 activerecord rails-activerecord


【解决方案1】:

指定表上的取消范围列(Rails 6.1+)

从 Rails 6.1 开始,可以只取消指定表中的列的范围。

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 }

这里是a link to the corresponding PR

所以,我想,以下可以解决你的问题:

MediaFileAsset.unscope(where: :"service_asset_cores.archived").where(custom_conditions)

【讨论】:

    【解决方案2】:

    你已经很接近了。

    MediaFileAsset.unscoped 返回一个ActiveRecord::Relation,这意味着您可以在其上调用.count, .where, .includes, 等。

    所以完整的查询是:

    MediaFileAsset.unscoped.where(service_assset_cores: :archived).count
    

    或者,您可以在MediaFileAsset 上定义一个名为archived 的命名范围,并在取消其范围后重新调整范围:

    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
    

    【讨论】:

    • 我会澄清这个问题:我想要做的是删除存档范围,但仅此而已。例如,我想计算归档字段的所有 MediaFileAsset 行不管,但保留之前可能已施加的任何范围。
    • @AndrewSchwartz 啊,我明白了。我相信 unscope 只能采用键值,而不是完全解析的 where 条件。我认为 MediaFileAsset.unscope(where: :service_asset_cores) 将是最接近的。
    • 是的,我担心这是真正的答案,我不能做我想做的事。即使您的 unscope 建议似乎也不起作用,我仍然通过包含存档条件的查询获得 10 的计数。我支付的是:klass = MediaFileAsset.dup; klass.default_scopes=[]; klass.count - 但我仍然不确定如何将其连接到实例化 ActiveRecord::Relation obkect 上的先前查询。
    • 你的类的复制不是一个坏的解决方案,但是你会合并到你传入的 Relation 对象中?我很好奇你是怎么做到的。
    • 嗯,我担心不太好。这样做似乎仍然会删除我设置的 where(id: (0..100000)) 条件,所以我猜 default_scopes= 做的比我想象的要多。 MediaFileAsset.where(id: (0..100000)).without_default_scope.count == MediaFileAsset.unscoped.count,其中在 without_default_scope 方法中我使用了 klass = self.dup; klass.default_scopes=[]; self.unscoped.merge(klass.all)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-22
    • 1970-01-01
    • 2013-09-24
    • 2012-02-22
    相关资源
    最近更新 更多