2013-10-14 3 views
2

В очень простом форуме сделал из приложения Rails, я получаю 30 тему из базы данных в акции индекса, как этотжадной загрузка первой запись ассоциации

def index 

@topics = Topic.all.page(params[:page]).per_page(30) 

end 

Однако, когда я перечисляю их в представлениях /topics/index.html.erb, я также хочу иметь доступ к первому сообщению в каждой теме для отображения в всплывающей подсказке, чтобы при прокрутке пользователей они могли читать первое сообщение, не нажимая на ссылку. Поэтому, в связи с каждой должности в индексе, я добавляю следующее атрибутом данных

topic.posts.first.body 

каждая из ссылок выглядит следующим образом

<%= link_to simple_format(topic.name), posts_path(
:topic_id => topic), :data => { :toggle => 'tooltip', :placement => 'top', :'original-title' => "#{ topic.posts.first.body }"}, :class => 'tool' %> 

В то время как это работает хорошо, я волнуюсь что это п + 1 запрос, а именно, что если есть 30 тем, что он делает это в 30 раз

User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1 
    Post Load (0.4ms) SELECT "posts".* FROM "posts" WHERE "posts"."topic_id" = $1 ORDER BY "posts"."id" ASC LIMIT 1 [["topic_id", 7]] 

Я заметил, что Rails делает автоматического кэширования на некоторых из них, но я думаю, что там может быть способ писание e действие индекса по-разному, чтобы избежать некоторых из этих проблем n + 1, но я могу понять, как это сделать. Я узнал, что я могу

include(:posts) 

для нетерпеливога нагрузки постов, как этого

@topics = Topic.all.page(params[:page]).per_page(30).includes(:posts) 

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

Я пытался сделать

.includes(:posts).first 

но сломал код

ответ

0

Это, кажется, работает для меня, чтобы дать этому выстрел и посмотреть, если он работает для вас:

Topic.includes(:posts).where("posts.id = (select id from posts where posts.topic_id = topics.id limit 1)").references(:posts) 

Это создаст зависимую подзапрос, в котором posts topic_id в подзапросе сопоставляется с id topics в родительском запросе. С предложением limit 1 в подзапросе результат состоит в том, что каждая строка Topic будет содержать только 1 подходящую строку Post, которая загружается благодаря includes(:post).

Обратите внимание, что при переходе к SQL строки .where, которая ссылается на нетерпеливое заряженное отношение, метод references должен быть приложен сообщить ActiveRecord, что мы ссылающийся ассоциацию, так что он знает, для выполнения соответствующих соединений в последующем запросе ,По-видимому, это технически работает без этого метода, но вы получаете предупреждение об устаревании, так что вы можете также бросить его, чтобы вы не столкнулись с проблемами в будущих обновлениях Rails.

+0

Но количество запросов не уменьшается, вместо этого они идут в большой запрос :) Кстати, поскольку я помню, что 'select' в' where' не является хорошим стилем в SQL. –

+0

@BillyChan Попробуйте, это наверняка уменьшит количество запросов. Вниз от 30 до 1, это должно быть. Вы правы, что это входит в один «больший» запрос, который (если я не понял), что хотел OP; для исключения дополнительного запроса, выполняемого с помощью 'topic.posts.first'. Кроме того, я не совсем уверен, почему 'select' не будет хорошим стилем в предложении' where', так как я не уверен, как вы могли бы выполнить подзапрос (или даже любой запрос) без него. Может быть, я вас неправильно понял, вы можете уточнить? –

+0

Тиг, я забыл, где я прочитал эту заявку и не могу найти надежный источник в настоящее время. В любом случае, в этом случае я бы предпочел не использовать 'includes', если ему нужен длинный запрос для исправления :) –

0

Насколько мне известно, вы не можете. Пользовательская ассоциация часто используется, чтобы разрешить условия для включений, кроме limit.

Если вы хотите загрузить связь с указанным пределом, она будет проигнорирована, возвращая все связанные объекты. http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

class Picture < ActiveRecord::Base 
    has_many :most_recent_comments, -> { order('id DESC').limit(10) }, 
           class_name: 'Comment' 
end 

Picture.includes(:most_recent_comments).first.most_recent_comments 
# => returns all associated comments. 
Смежные вопросы