通过关联在 has_many 中链接 Rails 3 作用域
这可行吗?
我有以下范围:
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 进行分页,它只适用于关系而不是数组 - 所以我需要返回一个范围。
Is this doable?
I have the following scope:
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
I get a result if there's a single tag in the array of tags passed in with Thing.withtag_search(array_of_tags)
but if I pass multiple tags in that array I get an empty relation as the result. In case it helps:
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
whereas
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
I want to be able to chain these relations together so that (among other reasons) I can use the Kaminari gem for pagination which only works on relations not arrays - so I need a scope to be returned.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我也遇到了这个问题。问题不是 Rails,问题肯定是 MySQL:
您的 SQL 将创建以下临时 JOIN 表(仅显示必要的字段):
因此,将所有
Tag
加入到一个特定的Thing< /code>,它为每个
Tag
-Thing
组合生成一行(如果您不相信,只需运行COUNT(*)
这条 SQL 语句)。问题是您的查询条件如下所示:
WHERE (tags.name = 'test_tag_1') AND (tags.name = 'test_tag_2')
将针对每一行进行检查,并且永远不会将会是真的。tags.name
不可能同时等于test_tag_1
和test_tag_2
!标准的 SQL 解决方案是使用 SQL 语句 INTERSECT...但不幸的是 MySQL 不这样做。
最好的解决方案是为每个标签运行
Thing.withtag_search
,收集返回的对象,并仅选择每个结果中包含的对象,如下所示:如果您想将其获取为一个
ActiveRecord
关系,您可能可以这样做:另一个解决方案(我正在使用)是将标签缓存在
Thing
表中,并执行 MySQL 布尔搜索在它上面。如果您愿意,我将为您提供有关此解决方案的更多详细信息。无论如何,我希望这对你有帮助。 :)
I also ran into this problem. The problem is not Rails, the problems is definitely MySQL:
Your SQL will create following temporary JOIN-table (only neccesary fields are shown):
So instead joining all
Tag
s to one specificThing
, it generates one row for eachTag
-Thing
combination (If you don't believe, just runCOUNT(*)
on this SQL statement).The problem is that you query criteria looks like this:
WHERE (tags.name = 'test_tag_1') AND (tags.name = 'test_tag_2')
which will be checked against each of this rows, and never will be true. It's not possible fortags.name
to equal bothtest_tag_1
andtest_tag_2
at the same time!The standard SQL solution is to use the SQL statement
INTERSECT
... but unfortunately not with MySQL.The best solution is to run
Thing.withtag_search
for each of your tags, collect the returning objects, and select only objects which are included in each of the results, like so:If you want to get this as an
ActiveRecord
relation you can probably do this like so:The other solution (which I'm using) is to cache the tags in the
Thing
table, and do MySQL boolean search on it. I will give you more details on this solution if you want.Anyways I hope this will help you. :)
乍一看这相当复杂,但根据你的 SQL,你想要:
我没有过多处理 Rails 3,但如果您可以适当调整 JOIN,这应该可以解决您的问题。您是否尝试过类似于以下的解决方案:
这样,您将按照您期望的方式加入(并集而不是交集)。我希望这是思考这个问题的一种有用的方式。
This is rather complicated at a glance, but based on your SQL, you want:
I haven't dealt much with Rails 3, but if you can adjust your JOIN appropriately, this should fix your issue. Have you tried a solution akin to:
This way, you will JOIN the way you are expecting (UNION instead of INTERSECTION). I hope this is a helpful way of thinking about this problem.
似乎无法找到解决这个问题的方法。因此,我不再使用 Kaminari 并滚动自己的标签,而是改用 Acts-as-taggable-on 和 will-paginate
Don't seem to be able to find a solution to this problem. So, instead of using Kaminari and rolling my own tagging I've switched to Acts-as-taggable-on and will-paginate