2

我有一个模型,其方法是尝试“匹配所有”用户选择的标签。但是,一旦我在我的应用程序中选择了两个标签,并将参数传递给下面的方法,它就不会返回任何结果。一种选择效果很好。

如您所见,我有一个 Style 模型,它通过 Tagizations 具有_many 个标签。我想“匹配所有”用户选择的标签,我在 filter_with_params 定义下的第三行是我尝试正确查询。

因此,如果我将 params[:t] = ["green", "yellow", "red"] 传递给过滤器方法,我只想返回具有所有这三个标签的样式。

这是模型的一个片段:

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/jgranas/6195528

使用 .TO_SQL

单标签(作品):

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

4

3 回答 3

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]. 因此,例如,我们将有一行 forstyle1 - tag1和另一行 for style1 - tag2。然后我们按样式 id 对行进行分组,这样我们每个唯一样式只能得到一条记录。但我们也在计算每组中有多少行。我们只想要标签集中每个标签都有一行的记录,所以我们丢弃其余的。

注意:上面的代码适用于 MySQL,但不适用于 postgres。postgres 的文档SELECT说:

“输出列的名称可用于在 ORDER BY 和 GROUP BY 子句中引用列的值,但不能在 WHERE 或 HAVING 子句中;您必须在此处写出表达式。”

所以我们需要scoped = scoped.having("count(*) = #{params[:t].size}")

于 2013-08-09T17:52:34.970 回答
0

我想我更喜欢为每个标签注入存在的 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
于 2013-08-12T16:15:19.910 回答
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
于 2013-08-13T10:29:28.500 回答