我想知道是否有人有使用Ransack与HABTM关系的经验.我的应用程序photos与habtm关系terms(术语就像标签).这是我正在经历的简化解释:
我有两张照片:照片1和照片2.他们有以下条款:
照片1:A,B,C
照片2:A,B,D
我构建了一个ransack表单,并在搜索表单中为所有术语创建了复选框,如下所示:
- terms.each do |t|
= check_box_tag 'q[terms_id_in][]', t.id
Run Code Online (Sandbox Code Playgroud)
如果我使用:q[terms_id_in][]并且我检查"A,C"我的结果是照片1和照片2.我只想要照片1,因为我要求A和C,在这个查询中我不关心B或D但我想要A和C都存在于给定的结果中.
如果我使用q[terms_id_in_all][]我的结果是零,因为两张照片都不包含A和C.或者,也许,因为每个连接只有一个术语,所以没有连接匹配A和C.无论如何,我只想要返回照片1.
如果我使用任何种类的q[terms_id_eq][]我从来没有得到任何结果,所以我认为这不适用于这种情况.
因此,给定一个habtm连接,如何在忽略未给定值的同时搜索与给定值匹配的模型?
或者,对于任何不熟悉Ransack的rails/sql专家,你怎么可能去创建一个搜索表单,比如我正在描述一个带有habtm连接的模型?
更新:根据相关问题的答案,我现在已经构建了一个正确匹配它的Arel查询.不知何故,你应该能够使用Arel节点作为搜索者,或者像cdesrosiers所指出的那样,作为自定义谓词,但到目前为止我还没有得到这样的工作.
根据该答案,我设置了以下ransack初始化程序:
Ransack.configure do |config|
config.add_predicate 'has_terms',
:arel_predicate => 'in',
:formatter => proc {|term_ids| Photo.terms_subquery(term_ids)},
:validator => proc {|v| v.present?},
:compounds => true
end
Run Code Online (Sandbox Code Playgroud)
...然后在Photo上设置以下方法:
def self.terms_subquery(term_ids)
photos = Arel::Table.new(:photos)
terms = Arel::Table.new(:terms)
photos_terms = Arel::Table.new(:photos_terms)
photos[:id].in(
photos.project(photos[:id])
.join(photos_terms).on(photos[:id].eq(photos_terms[:photo_id]))
.join(terms).on(photos_terms[:term_id].eq(terms[:id]))
.where(terms[:id].in(term_ids))
.group(photos.columns)
.having(terms[:id].count.eq(term_ids.length))
).to_sql
end
Run Code Online (Sandbox Code Playgroud)
不幸的是,这似乎不起作用.在terms_subquery生成正确的SQL时,结果Photo.search(:has_terms => …
我有一个非常普遍的habtm关系:
Photo has_and_belongs_to_many :tags
Tag has_and_belongs_to_many :photos
Run Code Online (Sandbox Code Playgroud)
在我的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
Run Code Online (Sandbox Code Playgroud)
这按预期工作.
现在,为了更好地与我正在使用的其他库集成,我需要在Arel中重写它.(让它成为Arel节点?,不确定你通常称之为什么).
我一直在尝试这个,但说实话我以前从未尝试过使用过Arel,所以我有点失落.我一直在试验控制台并尝试:
t = Photo.arel_table
q = t.join(:tags).on(t[:tags_id].in(array))
Photo.where(q)
Run Code Online (Sandbox Code Playgroud)
但是,(1)我认为q首先不是正确的查询,并且(2)它创建了一个Arel::SelectManager,当传递给调用的地方时Cannot visit Arel::SelectManager.所以,显然我做错了.
更新:只是为了在这里特别具体,我希望返回一个Arel节点,因为我正在使用一个gem(ransack),希望你传递它的搜索方法的Arel节点.Ransack将此Arel节点与其他节点链接在一起,以生成复杂的搜索查询.
Arel大师可以告诉我这是怎么做的吗?