【问题标题】:Chaining Rails 3 scopes in has_many through association通过关联在 has_many 中链接 Rails 3 范围
【发布时间】:2011-08-09 07:21:00
【问题描述】:

这可行吗?

我的范围如下:

class Thing < ActiveRecord::Base

scope :with_tag, lambda{ |tag| joins(:tags).where('tags.name = ?', tag.name)
                                           .group('things.id') }

def withtag_search(tags)
  tags.inject(scoped) do |tagged_things, tag|
    tagged_things.with_tag(tag) 
  end
end

如果使用Thing.withtag_search(array_of_tags) 传入的标签数组中有一个标签,我会得到一个结果,但如果我在该数组中传递多个标签,我会得到一个空关系作为结果。如果有帮助:

Thing.withtag_search(["test_tag_1", "test_tag_2"])

SELECT "things".* 
FROM "things" 
INNER JOIN "things_tags" ON "things_tags"."thing_id" = "things"."id" 
INNER JOIN "tags" ON "tags"."id" = "things_tags"."tag_id" 
WHERE (tags.name = 'test_tag_1') AND (tags.name = 'test_tag_2') 
GROUP BY things.id

=> [] # class is ActiveRecord::Relation

Thing.withtag_search(["test_tag_1"])

SELECT "things".* 
FROM "things" 
INNER JOIN "things_tags" ON "things_tags"."thing_id" = "things"."id" 
INNER JOIN "tags" ON "tags"."id" = "things_tags"."tag_id" 
WHERE (tags.name = 'test_tag_1') 
GROUP BY things.id

=> [<Thing id:1, ... >, <Thing id:2, ... >] # Relation including correctly all 
                                            # Things with that tag

我希望能够将这些关系链接在一起,以便(除其他原因外)我可以使用 Kaminari gem 进行分页,它只适用于关系而不是数组 - 所以我需要返回一个范围。

【问题讨论】:

    标签: ruby-on-rails scope associations chaining kaminari


    【解决方案1】:

    我也遇到了这个问题。问题不是 Rails,问题肯定是 MySQL:

    您的 SQL 将创建以下临时 JOIN 表(仅显示必要的字段):

    +-----------+-------------+---------+------------+
    | things.id | things.name | tags.id | tags.name  |
    +-----------+-------------+---------+------------+
    |     1     |     ...     |    1    | test_tag_1 |
    +-----------+-------------+---------+------------+
    |    1      |     ...     |    2    | test_tag_2 |
    +-----------+-------------+---------+------------+
    

    所以不是将所有Tags 加入到一个特定的Thing,而是为每个Tag-Thing 组合生成一行(如果您不相信,只需在此 SQL 语句上运行COUNT(*)) . 问题是您的查询条件如下所示:WHERE (tags.name = 'test_tag_1') AND (tags.name = 'test_tag_2') 将针对每一行进行检查,并且永远不会是真的。 tags.name 不可能同时等于 test_tag_1test_tag_2

    标准的 SQL 解决方案是使用 SQL 语句 INTERSECT... 但不幸的是,不使用 MySQL。

    最好的解决方案是为每个标签运行Thing.withtag_search,收集返回的对象,并仅选择每个结果中包含的对象,如下所示:

    %w[test_tag_1 test_tag_2].collect do |tag|
      Thing.withtag_search(tag)
    end.inject(&:&)
    

    如果您想将此作为ActiveRecord 关系,您可以这样做:

    ids = %w[test_tag_1 test_tag_2].collect do |tag|
      Thing.withtag_search(tag).collect(&:id)
    end.inject(&:&)
    Things.where(:id => ids)
    

    另一个解决方案(我正在使用)是将标签缓存在Thing 表中,并对其进行 MySQL 布尔搜索。如果您愿意,我会为您提供有关此解决方案的更多详细信息。

    无论如何,我希望这会对您有所帮助。 :)

    【讨论】:

    • 干杯 - 这看起来很有帮助,感谢您发现问题...我会尝试上述方法
    【解决方案2】:

    这乍一看相当复杂,但根据你的 SQL,你想要:

    WHERE (tags.name IN ('test_tag_1', 'test_tag_2'))

    我对 Rails 3 的处理不多,但如果您可以适当地调整您的 JOIN,这应该可以解决您的问题。您是否尝试过类似的解决方案:

     joins(:tag).where('tags.name IN (?), tags.map { |tag| tag.name })
    

    这样,您将以您期望的方式加入(UNION 而不是 INTERSECTION)。我希望这是思考这个问题的有用方法。

    【讨论】:

    • 试过了。不,这给了我所有带有 EITHER 标签而不是两个标签的东西的列表 :-( ...至少据说它返回了一个范围,所以这对于我想通过任何输入的标签搜索的功能很有用 - 所以谢谢你:-) ...现在我需要看看我是否能做到,这样我才能得到一个范围,让我通过输入所有标签来缩小范围......有什么想法吗?
    • 哦,rails 正在合并您的 JOIN?如何使连接超出 Rails 检测到的唯一性?代替 joins(:tags),尝试 joins('JOIN tags t1 ON t1.id = things_tags.id WHERE t1.name = #{tag.name}')。同样,我一直在使用 Rails 2 而不是 3,所以我无法获得完美的语法。手动指定连接肯定会完成这个难题!
    【解决方案3】:

    似乎无法找到解决此问题的方法。所以,我没有使用 Kaminari 并滚动我自己的标记,而是切换到 Acts-as-taggable-on 和 will-paginate

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-05
      • 1970-01-01
      • 1970-01-01
      • 2012-07-09
      • 2014-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多