将ActiveRecord habtm查询转换为Arel

我有一个非常普遍的关系:

 Photo has_and_belongs_to_many :tags Tag has_and_belongs_to_many :photos 

在我的Photo模型中,我有一个“带标签”的方法,我用它来查找用一组给定的tag_ids标记的照片。 此查询只需匹配包含所有给定标记的照片,但无需考虑是否存在任何其他标记。 这是我的方法:

 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大师可以告诉我这是怎么做的吗?

很难找到好的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)


更新:

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

我没试过这个,所以如果有效的话我会很激动!