Стоит ли отбрасывать полиморфную ассоциацию?

Мой код все еще находится в разработке, а не в производстве, и я натыкаюсь на стену, генерируя данные, которые мне нужны для некоторых представлений.

Не вдаваясь в подробности, я просто хочу пройтись по нескольким ассоциациям моделей, чтобы получить некоторую информацию на каждом уровне. Единственная ассоциация, вызывающая у меня проблемы, - это полиморфная own_to. Вот самые актуальные ассоциации

Model Post
  belongs_to :subtopic
  has_many :flags, :as => :flaggable

Model Subtopic
  has_many :flags, :as => :flaggable

Model Flag
  belongs_to :flaggable, :polymorphic => true

Я хотел бы отобразить несколько флагов в представлении индекса Flags #. Есть информация от других моделей, которую я тоже хочу отобразить, но я опускаю детали, чтобы упростить задачу.

В моем действии Flags_controller # index я сейчас использую @flags = paginate_by_sql для извлечения всего, что мне нужно, из базы данных. Я могу успешно получить данные, но не могу загрузить связанные объекты модели (хотя все данные, которые мне нужны, находятся в памяти). Сейчас я рассматриваю несколько вариантов:

  • переписать мои представления для работы с данными SQL в объекте @flags. Это должно сработать и предотвратит 5-6 SQL-запросов модели ассоциации на строку на странице индекса, но будет выглядеть очень хакерским. Я бы хотел этого избежать, если возможно

  • упростить мои представления и создать дополнительные страницы для более подробной информации, которая будет загружаться только при просмотре одного отдельного флага

  • изменить иерархию / определения модели с полиморфных ассоциаций на наследование. Фактически создайте модуль или класс FlaggableObject, который будет родительским для Subtopic и Post.

Я склоняюсь к третьему варианту, но не уверен, что смогу чисто извлечь всю нужную мне информацию, используя только помощники Rails ActiveRecord.

Я хотел бы узнать, сработает ли это, и, что более важно, есть ли у вас лучшее решение

РЕДАКТИРОВАТЬ: Некоторое придирчивое поведение include, с которым я столкнулся

@flags = Flag.find(:all,:conditions=> "flaggable_type = 'Post'", :include => [{:flaggable=>[:user,{:subtopic=>:category}]},:user]).paginate(:page => 1)

=> (valid response)


@flags = Flag.find(:all,:conditions=> ["flaggable_type = 'Post' AND 
  post.subtopic.category_id IN ?", [2,3,4,5]], :include => [{:flaggable=>
  [:user, {:subtopic=>:category}]},:user]).paginate(:page => 1)

=> ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :flaggable



Ответы (2)


Не отбрасывайте полиморфную ассоциацию. Используйте includes(:association_name) для активной загрузки связанных объектов. paginate_by_sql не будет работать, но paginate будет.

@flags = Flag.includes(:flaggable).paginate(:page => 1)

Он будет делать именно то, что вы хотите, используя по одному запросу из каждой таблицы.

См. Руководство по ассоциациям Active Record. Вы можете увидеть более старые примеры, использующие параметр :include, но метод includes - это новый интерфейс в Rails 3.0 и 3.1.

Обновление с исходного плаката:

Если вы получаете эту ошибку: Can not eagerly load the polymorphic association :flaggable, попробуйте что-нибудь вроде следующего:

Flag.where("flaggable_type = 'Post'").includes([{:flaggable=>[:user, {:subtopic=>:category}]}, :user]).paginate(:page => 1)

См. Комментарии для более подробной информации.

person Jonathan Tran    schedule 27.05.2011
comment
Причина, по которой я использую paginate_by_sql, заключается в том, что активная загрузка не работает с полиморфными ассоциациями. Я просто дважды проверил, чтобы убедиться, и получил эту ошибку Can not eagerly load the polymorphic association :flaggable - person Eric Hu; 27.05.2011
comment
Какую версию rails / will_paginate вы используете? Я тестировал это на Rails 3.0.7 и will_paginate 2.3.15, прежде чем опубликовать этот ответ. will_paginate ищет отсутствующий метод add_limit!, но после его взлома он работал нормально. - person Jonathan Tran; 28.05.2011
comment
Я на рельсах 3.0.7 и will_paginate 3.0.pre2. Я пробовал без will_paginate, g = Flag.includes(:flaggable).all, и это сработало. Добавление в вызов will_paginate создало ошибку. Что ты делал с add_limit!? - person Eric Hu; 28.05.2011
comment
Я допустил ошибку. @flags = Flag.includes(:flaggable).paginate(:page => 1) работает на 3.0.pre2. Но использование paginate_by_sql - нет. Можете ли вы переключиться на paginate? - person Jonathan Tran; 28.05.2011
comment
После поиска вашего сообщения об ошибке мне пришло в голову, что вы, вероятно, не учитываете релевантную часть своего запроса в опубликованном вопросе, b / c код в моем ответе действительно работает. Ознакомьтесь с этим и это. Можете ли вы обновить свой вопрос полным оператором query / paginate_by_sql? - person Jonathan Tran; 30.05.2011
comment
Я получаю сообщение об ошибке даже без команды paginate_by_sql, т.е. g = Flag.includes(:flaggable).paginate(:page => params[:page]). Однако, чтобы ответить на ваш вопрос, я включу свою команду paginate_by_sql в новый комментарий (вы увидите, почему). Я должен добавить, что я не использовал paginate_by_sql, пока не смог заставить includes работать с paginate. - person Eric Hu; 31.05.2011
comment
Одна из предоставленных вами ссылок была хорошим началом. Я могу сделать g = Flag.joins("INNER JOIN posts p ON p.id = flags.flaggable_id AND flags.flaggable_type = 'Post'").paginate(:page => params[:page]). Однако Rails по-прежнему выполняет SQL-запрос с g.first.flaggable. Если я изменю его на Flag.includes..., строка INNER JOIN не будет работать. Я продолжу играть с этим, чтобы увидеть, смогу ли я заставить его работать. В противном случае решение соединения также потребует уродливого кода Rails-SQL для предотвращения множества SQL-запросов. - person Eric Hu; 01.06.2011
comment
Я не уверен, почему includes работает на вас, но не на меня. Однако я нашел обходной путь: Flag.find(:all,:conditions=>"flaggable_type = 'Post'", :include => [{:flaggable=>[:user, {:subtopic=>:category}]}, :user]).paginate(:page =>5). Это нетерпеливо загружает все, что я хочу, но только для одного типа ассоциации. Я могу жить с этим. Я ценю вашу помощь и отмечу ваш ответ как принятый, если вы можете отредактировать свой ответ, включив в него этот фрагмент кода, чтобы другим не пришлось читать эти комментарии. - person Eric Hu; 01.06.2011
comment
Теперь я чувствую себя глупо. Этот запрос решает проблему нетерпеливой загрузки при использовании will_paginate, но я забыл, что хочу фильтровать объекты Flag на основе их категории (либо (flaggable) post.subtopic.category, либо (flaggable) subtopic.category). Возможно, мне стоит просто задать новый вопрос - person Eric Hu; 01.06.2011
comment
Я не уверен. Я понимаю, что вы хотите использовать ActiveRecord, а не опускаться в SQL, но AR предоставляет способ перейти в SQL b / c, иногда это просто проще. Вы изучали использование подзапросов? Иногда это упрощает ваш SQL. Кроме того, в будущем, вместо того, чтобы комментировать огромные фрагменты кода, вам следует отредактировать свой вопрос и добавить к нему обновления, особенно если это относится ко всему вашему вопросу. Прокомментируйте ответ, если он имеет отношение к конкретному ответу или если вы хотите уведомить ответчика. - person Jonathan Tran; 01.06.2011

Проблемы: Подсчитайте полиморфную ассоциацию.

@flags = Flag.find(:all,:conditions => ["flaggable_type = 'Post' AND post.subtopic.category_id IN ?",
[2,3,4,5]], :include => [{:flaggable => [:user, {:subtopic=>:category}]},:user])
.paginate(:page => 1)

Попробуйте следующее:

@flags = Flag.find(:all,:conditions => ["flaggable_type = 'Post' AND post.subtopic.category_id IN ?",
[2,3,4,5]], :include => [{:flaggable => [:user, {:subtopic=>:category}]},:user])
.paginate(:page => 1, :total_entries => Flag.count(:conditions => 
["flaggable_type = 'Post' AND post.subtopic.category_id IN ?", [2,3,4,5]]))
person tony    schedule 20.06.2012