Chain Rails 3 выходит в has_many через ассоциацию - PullRequest
1 голос
/ 09 августа 2011

Это выполнимо?

У меня есть следующий объем:

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

1010 * тогда *

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 для разбиения на страницы, который работает только с отношениями, а не с массивами - поэтому мне нужно вернуть область видимости.

Ответы [ 3 ]

2 голосов
/ 13 августа 2011

Я тоже столкнулся с этой проблемой. Проблема не в Rails, проблема определенно в MySQL:

Ваш SQL создаст следующую временную JOIN-таблицу (показаны только необходимые поля):

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

Таким образом, вместо объединения всех Tag с одной конкретной Thing, она генерирует одну строку для каждой комбинации 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 для каждого из ваших тегов, собрать возвращаемые объекты и выбрать только объекты, включенные в каждый из результатов, например:

%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. Я дам вам более подробную информацию об этом решении, если вы хотите.

В любом случае, я надеюсь, что это поможет вам. :)

0 голосов
/ 13 августа 2011

Похоже, не удается найти решение этой проблемы.Итак, вместо того, чтобы использовать Kaminari и использовать собственные теги, я переключился на Acts-as-taggable-on и will-paginate

0 голосов
/ 09 августа 2011

На первый взгляд, это довольно сложно, но на основе вашего SQL вы хотите:

ГДЕ (tags.name IN ('test_tag_1', 'test_tag_2'))

Я не особо разбирался с Rails 3, но если вы сможете соответствующим образом настроить JOIN, это должно решить вашу проблему. Вы пробовали решение, похожее на:

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

Таким образом, вы присоединитесь к тому, что ожидаете (СОЮЗ вместо ПЕРЕКЛЮЧЕНИЯ). Я надеюсь, что это полезный способ думать об этой проблеме.

...