我有一个模型,其中有一个方法,我试图“匹配用户选择的所有”标签。但是,只要我在我的应用程序中选择了两个标签,并将params传递给下面的方法,它就不会返回任何结果。一个选择工作正常。
正如您所看到的,我有一个样式模型,它通过标记有很多标记。我希望“匹配所有”用户选择的标签,我在filter_with_params定义下的第三行是我尝试正确查询。
所以,如果我将params [:t] = [“green”,“yellow”,“red”]传递给filter方法,我只想返回包含所有这三个标签的样式
以下是该模型的摘录:
class Style < ActiveRecord::Base
has_many :tagizations, :dependent => :destroy
has_many :tags, :through => :tagizations
def self.filter_with_params(params)
scoped = self.where("styles.name != ''")
scoped = scoped.includes(:tags)
scoped = params[:t].inject(scoped){|memo, val| memo.where(:tags => {:name => val})} if params[:t]
scoped
end
end
这是一个Gist,其中包含两个请求的开发日志输出:
https://gist.github.com/jgrannas/6195528
使用.TO_SQL
SINGLE TAG(作品):
Style.filter_with_params({:t => ["engagement"]}).to_sql
=> "SELECT \"styles\".* FROM \"styles\" WHERE \"tags\".\"name\" = 'engagement' AND (styles.name != '')"
多个标签(不起作用)
Style.filter_with_params({:t => ["engagement", "Halo"]}).to_sql
=> "SELECT \"styles\".* FROM \"styles\" WHERE \"tags\".\"name\" = 'engagement' AND \"tags\".\"name\" = 'Halo' AND (styles.name != '')
我正在使用postgres
答案 0 :(得分:2)
这是你的问题:
WHERE "tags"."name" = 'engagement' AND "tags"."name" = 'Halo'
问题在于,您正在寻找具有所有 3个名称的单个标记的内容,这是不可能的。你想要的是每个名字都有标签的东西。
不完全确定这会有效,但请尝试以下方法:
def self.filter_with_params(params)
scoped = self.select("distinct styles.*, count(*) AS count")
scoped = scoped.where("styles.name != ''")
scoped = scoped.joins(:tags)
if params[:t]
scoped = scoped.where(tags: { name: params[:t] })
scoped = scoped.group('styles.id')
scoped = scoped.having("count = #{params[:t].size}")
end
scoped
end
我们在这里做的主要是我们正在构建一个表格,其中每个样式标记组合有一行,但仅适用于params[:t]
中的标记。因此,例如,我们将为style1 - tag1
添加一行,为style1 - tag2
添加另一行。然后我们按样式id对行进行分组,这样我们每个唯一样式只能获得一条记录。但我们还要计算每组中有多少行。我们只想要那些在标签集中有每个标记行的记录,因此我们放弃其余的。
注意:上面的代码适用于MySQL,但不适用于postgres。 The documentation for postgres SELECT
说:
“输出列的名称可用于在ORDER BY和GROUP BY子句中引用列的值,但不能在WHERE或HAVING子句中引用;在那里必须写出表达式。”
所以我们需要scoped = scoped.having("count(*) = #{params[:t].size}")
答案 1 :(得分:0)
我想我更喜欢为每个标签注入exists SQL子句。输出SQL应该看起来像那样
SELECT styles.*
FROM styles
WHERE styles.name != ''
AND exists(SELECT * from tags where style_id = styles.id AND tags.name = 'red')
AND exists(SELECT * from tags where style_id = styles.id AND tags.name = 'green')
据我所知,您需要手动注入SQL才能实现这一目标。因此是这样的:
def self.filter_with_params(params)
scoped = self.where("styles.name != ''")
scoped = scoped.includes(:tags)
params[:t].each do |tag|
scoped.where('EXISTS(select 1 FROM tags where tags.name = ? and tags.style_id = styles.id )', tag)
end
scoped
end
答案 2 :(得分:0)
试试这个:
def self.with_tags *tags
joins(:tags).where(:tags => {:name => tags}).group("#{self.table_name}.id").having("COUNT(DISTINCT tags.name) = ?", tags.length)
end