2

How can one preload an association with a limit in Rails?

For example:

class Comment < ActiveRecord::Base
 belongs_to :post
end

class Post < ActiveRecord::Base
 has_many :comments
end

This works fine:

Post.all.preload(:comments)

But how I can preload only one COMMENT for each POST. (Ideally one RANDOM COMMENT for each POST)

Something like this:

Post.all.preload(:comments.limit(1))
SRack
  • 11,495
  • 5
  • 47
  • 60
Serhii Danovskyi
  • 385
  • 3
  • 17

1 Answers1

2

you can create custom associations below, with order as random and limit just 1 note: if you using mysql change RANDOM() to RAND()

class Post < ActiveRecord::Base
 has_many :comments
 has_one  :random_comment, -> { order("RANDOM()").limit(1) }, class_name: "Comment"
end

then you can do

Post.all.preload(:random_comment)
SRack
  • 11,495
  • 5
  • 47
  • 60
widjajayd
  • 6,090
  • 4
  • 30
  • 41
  • Don't forget to access it through `post.random_comment`. If you'll call `post.comments` it will still result in a 1+N query. – 3limin4t0r Feb 14 '19 at 10:20
  • 1
    You should drop the `.limit(1)` since having a `has_one` association already establishes that. – 3limin4t0r Feb 14 '19 at 10:25
  • I use *has_one :random_comment, class_name: "Comment"* but I have Post Load (0.8ms) SELECT "posts".* FROM "posts" ↳ app/views/posts/index.html.erb:16 Comment Load (96.9ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (?, ?, ?, ?, ?, ?, ?) [["post_id", 53], ["post_id", 54], ["post_id", 55], ["post_id", 56], ["post_id", 57], ["post_id", 58], ["post_id", 59]] ↳ app/views/posts/index.html.erb:16 in the same like ** Post.all.preload(:comments)** but we show only one comment – Serhii Danovskyi Feb 14 '19 at 11:51
  • @SerhiiDanovskyi ^ Pop quiz. What SQL-query would you use to fetch one comment of every post in one single query? (Assuming you already have the posts.) – 3limin4t0r Feb 14 '19 at 13:00
  • I've given you some time to think. The reason Rails requests all comments (for each post) is because the query is pretty hard to generate. From the retrieved records only the first for each post is instantiated. Have a look at this question: https://stackoverflow.com/questions/3800551/select-first-row-in-each-group-by-group – 3limin4t0r Feb 14 '19 at 15:31