我有一个很常见的habtm
关系:
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
{{1}} 1}}。所以,显然我做错了。
更新:只是为了在这里特别具体,我希望返回一个Arel节点,因为我正在使用一个gem(ransack),希望你传递它的搜索方法的Arel节点。 Ransack会将此Arel节点与其他节点链接在一起生成复杂的搜索查询。
Arel大师能否告诉我这是怎么做到的?
答案 0 :(得分:9)
很难找到好的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)
一样直接使用。
更新:
在查看了文档和一些搜索源之后,似乎没有任何自然的方法来定义涉及子查询的自定义谓词,这在您的情况下是必要的(因为谓词必须适合{{ 1}}子句)。解决此问题的一种方法可能是利用谓词使用的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
定义为生成上述arel节点的方法,但将tags_subquery(tag_ids)
替换为array
并在返回之前调用tag_ids
(格式化程序需要返回一个字符串,而不是一个节点。)
我没试过这个,所以如果它有效我会很激动!