模型中的方法以匹配所有关联值

时间:2013-08-09 17:42:42

标签: ruby-on-rails ruby ruby-on-rails-3 postgresql activerecord

我有一个模型,其中有一个方法,我试图“匹配用户选择的所有”标签。但是,只要我在我的应用程序中选择了两个标签,并将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

3 个答案:

答案 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