BUG 觸發條件
第一個觸發該 BUG 的情境是,載入過 association 後又改變其 foreign_key。
post = Post.take
post.user.do_something # 載入 association
post.user_id = 123 # foreign_key 被改變
post.save
# 會多花一個 query 去載入 user
# => SELECT `users`.* FROM `users` WHERE `users`.`id` = 123 LIMIT 1
第二個觸發情境是,查詢過某個 association 是否載入過,卻沒有真的載入。
post = Post.take
post.association(:user).loaded?
post.save
# 會多花一個 query 去載入 user
# => SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
BUG 狀態
Rails 4.2.x 都有問題
Rails 5 後 belongs_to
已修復: #23498 - Don’t unnecessarily load a belongs_to when saving.。但 has_one
還未修復。
BUG 原因
在呼叫 association 時,會先從 association_cache
內找,假如找不到的話會 create 新的 association 物件,並寫入 association_cache。此時只會有 association 物件,model 資料還沒有載入,association.loaded?
為 false。ref
def association(name) #:nodoc:
association = association_instance_get(name)
if association.nil?
raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name)
association = reflection.association_class.new(self, reflection)
association_instance_set(name, association)
end
association
end
在物件儲存時,rails 會檢查哪些 association 需要 autosave
。會去 association_cache 取出 association 物件,然後呼叫 load_target
取出 model 內容。但如果物件資料還沒有載入時,load_target
會下 query 去載入 model 資料。這其實是不需要的,因為假如子物件沒有載入過,代表子物件不可能有變動,不需要 autosave
。ref
def save_belongs_to_association(reflection)
association = association_instance_get(reflection.name)
record = association && association.load_target
# ...
end
Workarounds
可以在 initializers
內加上以下程式碼
module ActiveRecord
module Associations
def association_loaded?(name)
association = association_instance_get(name)
return false if association == nil
return association.loaded?
end
end
end
用這個函式取代原本的寫法,以避開 BUG
# before
association(:user).loaded?
# after
association_loaded?(:user)