I have these models:
class KlassOccurrence < ApplicationRecord
belongs_to :klass
end
class Klass < ApplicationRecord
has_many :klass_occurrences
belongs_to :user
def next_occurrence_date
self.klass_occurrences.order(scheduled_at: :desc).first
end
end
And I have this in a controller:
@klasses = Klass.includes(:klass_occurrences).where(user_id: current_user.id)
And in a view I have this:
<% @klasses.each do |klass| %>
<tr>
<td><%= klass.next_occurrence_date %></td>
</tr>
<% end %>
I would expect that because of the includes(:klass_occurrences)
I wouldn't be seeing an N+1, but this is what I see in the logs:
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 3], ["LIMIT", 1]]
↳ app/controllers/klasses_controller.rb:4:in `index'
Rendering klasses/index.html.erb within layouts/dashboard_layout
Klass Load (0.2ms) SELECT "klasses".* FROM "klasses" WHERE "klasses"."user_id" = $1 [["user_id", 3]]
↳ app/views/klasses/index.html.erb:13
KlassOccurrence Load (4.3ms) SELECT "klass_occurrences".* FROM "klass_occurrences" WHERE "klass_occurrences"."klass_id" IN ($1, $2) [["klass_id", 1], ["klass_id", 5]]
↳ app/views/klasses/index.html.erb:13
KlassOccurrence Load (1.8ms) SELECT "klass_occurrences".* FROM "klass_occurrences" WHERE "klass_occurrences"."klass_id" = $1 ORDER BY "klass_occurrences"."scheduled_at" DESC LIMIT $2 [["klass_id", 1], ["LIMIT", 1]]
↳ app/models/klass.rb:6:in `next_occurrence_date'
KlassOccurrence Load (0.3ms) SELECT "klass_occurrences".* FROM "klass_occurrences" WHERE "klass_occurrences"."klass_id" = $1 ORDER BY "klass_occurrences"."scheduled_at" DESC LIMIT $2 [["klass_id", 5], ["LIMIT", 1]]
↳ app/models/klass.rb:6:in `next_occurrence_date'
Why is it doing three queries for klass occurrences? I would expect just one query to load all the occurrences for all the klasses, but then I see two extra queries.
If you just need the highest value of that column you can also just select an aggregate off the join table:
class Klass < ApplicationRecord
has_many :klass_occurrences
belongs_to :user
def self.with_next_occurrence_date
self.select(
'klass.*',
'MAX(klass_occurrences.scheduled_at) AS next_occurrence_date'
)
.left_joins(:klass_occurrences)
.group(:id)
end
end
@klasses = Klass.with_next_occurrence_date.where(user_id: current_user.id)
<% @klasses.each do |klass| %>
<tr>
<td><%= klass.next_occurrence_date %></td>
</tr>
<% end %>
If you really need a model object you can do it by sorting the records in Ruby:
class Klass < ApplicationRecord
has_many :klass_occurrences
belongs_to :user
def next_occurrence_date
self.klass_occurrences.sort(&:scheduled_at).last
end
end
And there are also a bunch of novel tricks to get ActiveRecord to load just one record off the association that are somewhat out of scope for this question.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.