将ActiveRecord habtm查询转换为Arel



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

Photo has_and_belongs_to_many :tags
Tag has_and_belongs_to_many :photos

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

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中重写它。(让它成为一个战神节点?,不确定你通常怎么称呼它)。

我一直在尝试这个,但说实话,我以前从未尝试过使用战神,所以我有点迷失了方向。我一直在控制台上进行实验,并尝试过:

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(transak),它希望您将它传递给Arel节点以用于搜索方法。Ransack将在生成复杂的搜索查询时将这个Arel节点与其他节点链接起来

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

很难找到好的Arel文档,但@Philip C已经整理了一些有用的幻灯片,在他回答这个问题时引用了这些幻灯片。

以下应该是你正在寻找的:

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)中那样直接使用该实例。


更新:

在查看了文档和一些transak的源代码后,似乎没有任何自然的方法来定义涉及子查询的自定义谓词,这在您的情况下是必要的(因为谓词必须适合where子句)。解决此问题的一种方法可能是利用谓词使用的: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(格式化程序需要返回字符串,而不是节点)。

我还没有试过,所以如果它能起作用,我会很兴奋的!

相关内容

  • 没有找到相关文章

最新更新