6

Я использую метод AR includes выполнить левое внешнее соединение между объектами пользователя и Строительство, где пользователь может или не может иметь ассоциацию здание:Неожиданное поведение с ActiveRecord включает

users = User.includes(:building).references(:buildings) 

Поскольку я использую references, любые связанные объекты здания будут загружены.

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

Эти запросы, очевидно, излишни, поскольку ассоциация была бы загружена во время первоначального соединения и, кажется, побеждает целую цель активной загрузки с включением/ссылками, так как теперь я просматриваю N раз число запросов, равных к числу пустых ассоциаций.

users.each do | user | 

    # This will trigger a new query when building is not present: 
    # SELECT "buildings".* FROM "buildings" WHERE "buildings"."address" = $1 LIMIT 1 [["address", "123 my street"]] 
    if user.building 
    puts 'User has building' 
    else 
    puts 'User has no building' 
    end 

end 

Класс пользователя:

class User < ActiveRecord::Base 
    belongs_to :building, foreign_key: 'residence_id' 
end 

Есть ли способ, чтобы проверить наличие строительной ассоциации пользователей без запуска дополнительных запросов?


на рельсах 4.2.0/POSTGRES


UPDATE:

Спасибо @BoraMa за составление этого test. Похоже, мы получаем различное поведение на все последние версии Rails:

OUTPUT (РЕЛЬСЫ 4.2.0):

User 1 has building 
User 2 has building 
User 3 has no building 
D, [2016-05-26T11:48:38.147316 #11910] DEBUG -- : Building Load (0.2ms) SELECT "buildings".* FROM "buildings" WHERE "buildings"."id" = $1 LIMIT 1 [["id", 123]] 
User 4 has no building 

OUTPUT (РЕЛЬСЫ 4.2.6)

User 1 has building 
User 2 has building 
User 3 has no building 
User 4 has no building 

OUTPUT (РЕЛЬСЫ 5.0 .0)

User 1 has building 
User 2 has building 
User 3 has no building 
User 4 has no building 

Возьмите Визитки:

  • Этот вопрос был ограничен «оборванным внешние ключи (т.е. столбец residence_id не ноль, но там нет соответствующего здания объекта)» (БЛАГОДАРЯ @FrederickCheung)
  • Проблема была решена, как в Rails 4.2. 6
+0

Я бы ожидал ошибку, похожую на «ActiveRecord :: AssociationNotFoundError: Ассоциация с именем« здания »не была найдена на пользователе». Можете ли вы подтвердить синтаксис запроса, а также определение User-> Building association? – messanjah

+0

@messanjah Лично я ожидал бы, что 'user.building' вернет nil, если ассоциация не существует, и это то, что происходит при последующих вызовах, но при первом вызове он всегда запускает SQL-запрос. – Yarin

+0

Странно, я не наблюдаю этого поведения - мои ничтожные ассоциации просто возвращают нуль, без дальнейших запросов. Является ли код, который вы задали в вопросе, действительно минимальным рабочим примером проблемы? – BoraMa

ответ

3

Похоже, у вас есть немного по bug в Active Record, которая была зафиксирована в рельсах 4.2.3.

В случае, когда столбец был равен нулю. Активная запись уже знает, что даже не нужно пытаться загрузить связанный объект. Остальные случаи были затронуты этой ошибкой

+0

Для победы! Приятная находка, спасибо за вашу помощь, это убило меня – Yarin

+1

Ааа, слишком поздно! Но ты это заслужил, Фредерик :) Рад, что мы все пригвоздили эту интересную ошибку. – BoraMa

0

Похоже опечатка, пожалуйста, обратите внимание building вместо buildings: User.includes(:building).references(:buildings)

Это должно вызвать большой запрос, который использует формат AS tX_rY для каждой ассоциации и табл е.

+0

Спасибо за указание опечатки, но это не решает проблему – Yarin

0

Похоже, что с рельсов 4.1 существуют потенциальные столкновения с тем, как должны быть неявные #includes, см. Следующие open issue.

Этот код все непроверенные синтаксиса, но было бы два подхода я бы попробовать:

1/Make жадной загрузки неявное

users = User.eager_load(:building).preload(:buildings) 

2/выделить два типа пользователей, в которых здание прикреплено, что означает, что вы даже не пытаетесь предварительно загрузить здание, устраняя неэффективность.

users = User.includes(:building).where.not(residence_id: nil).references(:buildings) 

users.each do | user| 
    puts "User has building: #{user} #{user.building}" 
end 

# No additional references needed to be eager-loaded. 
users = User.where(residence_id: nil) 
users.each do | user | 
    puts "#{User} has no building." 
end 
+0

Спасибо за предложения. # 1 не изменил результаты - первоначальный запрос был таким же, как и при обращении к отсутствующим ассоциациям. Что касается №2, разделение его на два отдельных запроса будет работать для этого ограниченного примера, но не вариант в реальных приложениях, где нам нужно будет выполнять операции фильтрации/подкачки по запросу в целом – Yarin

Смежные вопросы