Eu estava olhando meu código Rails 3.2.13 para reduzir as consultas de banco de dados quando encontrei algo muito estranho.
# user.rb
class User < ActiveRecord::Base
has_many :attendances
has_many :events, :through => :attendances
end
Busquei um usuário e queria verificar se havia eventos:
u = User.includes([:attendances, :events]).find(1) # => #<User>
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Attendance Load (0.9ms) SELECT "attendances".* FROM "attendances" WHERE "attendances"."user_id" IN (1)
Event Load (0.7ms) SELECT "events".* FROM "events" WHERE "events"."id" IN (14, 15, 16)
u.events.any? # => true
# (no query happening)
u.attendances.any? # => true
(1.1ms) SELECT COUNT(*) FROM "attendances" WHERE "attendances"."user_id" = 1
Por que o ActiveRecord fez uma consulta de contagem quando já deveria estar com os atendimentos carregados?
Então, mudei a ordem dos includes:
u = User.includes([:events, :attendances]).find(1) # => #<User>
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Attendance Load (0.6ms) SELECT "attendances".* FROM "attendances" WHERE "attendances"."user_id" IN (1)
Event Load (0.5ms) SELECT "events".* FROM "events" WHERE "events"."id" IN (14, 15, 16)
Attendance Load (0.4ms) SELECT "attendances".* FROM "attendances" WHERE "attendances"."user_id" IN (1)
u.events.any? # => true
# (no query happening)
u.attendances.any? # => true
# (no query happening)
Agora ActiveRecord não fez nenhuma consulta de contagem, mas em vez disso, consultou os atendimentos inicialmente DUAS VEZES? E nem mesmo recorreu a um cache para a mesma consulta?
O que você deveria fazer?
Bem, escolha entre praga ou cólera:
Peste: Se incluir a associação: through (presenças) antes da associação mais distante (eventos), a associação mais próxima NÃO será carregada.
Cólera: Mas se você incluir a associação: through após a associação mais distante, a associação mais próxima será carregada DUAS VEZES.
Você escolhe. (Eu prefiro cólera)