2016-05-23 56 views
6

我使用的AR includes方法來執行左外對象用戶大廈,其中用戶可以或可以不具有一個建築協會之間JOIN:用ActiveRecord意外行爲包括

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

由於我使用的是references,因此任何關聯的Building對象都將被加載。

我的期望是,我可以遍歷用戶列表,並檢查用戶是否擁有與其關聯的建築物而不觸發其他查詢,但是我發現實際上每當我嘗試訪問建築物時沒有一個用戶的屬性,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 

有沒有一種方法來檢查用戶的建築協會的存在,而不會觸發額外的查詢?


ON RAILS 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:在用戶上找不到名爲'Buildings'的關聯」錯誤。你能否確認查詢語法以及用戶 - >建立關聯定義? – messanjah

+0

@messanjah就我個人而言,如果關聯不存在,那麼'user.building'將返回nil,這就是後續調用中發生的情況,但在第一次調用時總會觸發SQL查詢。 – Yarin

+0

奇怪的是,我沒有觀察到這種行爲 - 我的無關聯只是返回零,沒有進一步的查詢。您在問題中給出的代碼確實是問題的最小工作示例? – BoraMa

回答

3

聽起來像是你在活動錄製bug了一點,即固定在軌道4.2.3。

在列爲零的情況下,Active Record已經知道它甚至不需要嘗試加載關聯的對象。其餘的案例是受此bug影響的案例

+0

對於獲勝!很高興找到,謝謝你的幫助,這讓我很難過 – Yarin

+1

啊,太遲了!但你應得的,弗雷德裏克:)很高興我們都釘住了這個有趣的bug。 – BoraMa

0

似乎是一個錯字,請注意building而不是buildingsUser.includes(:building).references(:buildings)

應該觸發使用的AS tX_rY格式每個關聯和TABL大查詢即

+0

感謝您指出錯字,但這並不能解決問題 – Yarin

0

看來,由於導軌4.1有潛在的衝突,應該如何隱含#includes,請參閱以下open issue

這個代碼是所有未經驗證的語法,但仍然會有兩種方法我會嘗試:

1 /充分利用預先加載隱含

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