【问题标题】:Convert ActiveRecord habtm query to Arel将 ActiveRecord habtm 查询转换为 Arel
【发布时间】:2012-11-05 10:12:30
【问题描述】:

我有一个很常见的habtm 关系:

Photo has_and_belongs_to_many :tags
Tag has_and_belongs_to_many :photos

在我的照片模型中,我有一个“带有标签”的方法,我用它来查找带有给定 tag_id 集的照片。此查询只需要匹配具有所有给定标签的照片,而不考虑是否存在任何其他标签。这是我的方法:

def self.with_terms( array )
  select('distinct photos.*').joins(:tags).where('tags.id' => array).group("photos." + self.column_names.join(', photos.')).having("count(*) = #{array.size}")
end

这按预期工作。

现在,为了更好地与我正在使用的其他一些库集成,我需要在 Arel 中重新编写它。 (让它成为一个 Arel 节点?不知道你通常怎么称呼它)。

我一直在尝试这个,但老实说我以前从未尝试过使用 Arel,所以我有点迷茫。我一直在控制台中进行试验并尝试过:

t = Photo.arel_table
q = t.join(:tags).on(t[:tags_id].in(array))
Photo.where(q)

但是,(1) 我不认为q 首先是正确的查询,并且(2) 它创建了一个Arel::SelectManager,当传递给 where 调用时会引发Cannot visit Arel::SelectManager。所以,显然我做错了。

更新:只是在这里更具体一点,我希望返回一个 Arel 节点,因为我正在使用一个 gem(ransack),它希望您将 Arel 节点传递给它以用于搜索方法。 Ransack 将把这个 Arel 节点与其他节点链接起来以生成复杂的搜索查询。

Arel 大师能告诉我如何正确地做到这一点吗?

【问题讨论】:

  • 这不是答案,但试试 Squeel! AR 的默认设置在 imo 中相当糟糕,虽然 AREL 功能强大得多,但它的文档记录很糟糕,而且对开发人员不太友好。 squeel gem 非常适合做这样的事情!

标签: ruby-on-rails activerecord arel


【解决方案1】:

很难找到好的 Arel 文档,但 @Philip C 整理了一些有用的 slides,在他对 question 的回答中引用。

以下应该是您要查找的内容:

photos = Arel::Table.new(:photos)
tags = Arel::Table.new(:tags)
photo_tags = Arel::Table.new(:photo_tags)

q = photos[:id].in(
   photos.project(photos[:id])
  .join(photo_tags).on(photos[:id].eql(photo_tags[:photo_id]))
  .join(tags).on(photo_tags[:tag_id].eql(tags[:id]))
  .where(tags[:id].in(array))
  .group(photos.columns)
  .having(tags[:id].count.eq(array.length))
)

这会产生一个Arel::Nodes::In 实例,您应该可以像在Photo.where(q) 中那样直接使用它。


更新:

在查看文档和一些 ransack 的源之后,似乎没有任何自然的方法来定义涉及子查询的自定义谓词,这在您的情况下是必要的(因为谓词必须适合 @987654328 @ 子句)。解决此问题的一种方法可能是利用您的谓词使用的:formatter,如下所示:

Ransack.configure do |config|
  config.add_predicate 'with_tag_ids',
                   :arel_predicate => 'in',
                   :formatter => proc {|tag_ids| tags_subquery(tag_ids) },
                   :validator => proc {|v| v.present?},
                   :compounds => true
end

您可以将tags_subquery(tag_ids)定义为生成上述arel节点的方法,但将array替换为tag_ids并在返回之前对其调用.to_sql(格式化程序需要返回一个字符串,而不是一个节点)。

我还没有尝试过,所以如果它有效,我会很高兴!

【讨论】:

  • 真棒——这个查询有效,但是你如何将它作为一个 Arel 节点返回,以便它可以直接传递到 where 子句中?原因是我正在尝试编写一个可供依赖 Arel 的库使用的查询方法,特别是该方法需要返回一个 Arel 节点。即,如果Photo.where(q) 返回正确的结果,它将满足我的要求。
  • q 中的查询相当复杂。如果你想强制它进入一个where 子句,它将涉及一个子查询。如果您同意,实现此目的的一种方法是将q 更改为"photos"."id" IN (subquery returning photo ids) 形式。请参阅上面的编辑以获取等效的 Arel。
  • 你想用这个定义一个自定义的 ransack 谓词吗?
  • 是的,@cdesrosiers,这正是我想要做的。如果您能提供帮助,我将不胜感激。另见问题:stackoverflow.com/questions/13409627/…
  • 好的,我把它放在照片模型上,并在初始化程序中添加 'add_predicate' 配置选项。此代码运行,查询运行,但由于某种原因,它不会返回与您自己运行查询时相同的结果(当使用更新规定的方式时)。那好吧。我认为,鉴于您努力向我解释 Arel 方面,您已经掌握了这个问题的要点。如果您想继续专门试验 ransack,请继续在另一个问题上这样做。谢谢!!!
猜你喜欢
  • 1970-01-01
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
  • 2018-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多