昨天 [Day 16] 關聯資料表加載,解決 N+1 Query,我們得到了includes這個方法可以解決N+1 query,但是背後的原理是什麼? 就是我今天的主題。
class Product < ApplicationRecord
has_many :styles, class_name: 'ProductStyle' # 關聯取名為styles
end
class ProductStyle < ApplicationRecord
belongs_to :product
end
雖然只是簡單的加上has_many關聯,Rails已經幫我們產生很多黑魔法,包含最常用的關聯方法:styles,只要Product.last.styles 就可以呼叫出最後一個商品的所有款式。
其實:styles方法會先看記憶體cache裡面是不是已經有@association_cache這個實體變數,有的話就直接從@association_cache裡面把資料拉出來,如果沒有才用SQL語法跟DB要資料。
@association_cache來比較是否使用eager_loading(預加載),兩種情況的@association_cache
eager_loadingproduct = Product.last
product.instance_variable_get(:@association_cache) # => {}
@association_cache回傳一個空的hash,所以@association_cache預設值是空hash
eager_loadingproduct = Product.includes(:styles).first
product.instance_variable_get(:@association_cache)
# => {:styles=>#<ActiveRecord::Associations::HasManyAssociation:0x00007fa3083dc978...
# ...落落長...省略...@association_ids=nil, @association_scope=nil>}
@association_cache回傳一個key是:styles 的hash
class Type < ApplicationRecord
end
class Product < ApplicationRecord
has_many :styles, class_name: 'ProductStyle'
has_many :types
end
一次關聯兩個資料表
product = Product.includes([:styles, :types]).last
product.instance_eval{@association_cache}.keys
# => [:styles, :types]
@association_cache會用關聯的名字作為一個個hash_key,把預加載的資料放在對應的hash value。
透過includes方法把資料cache在@association_cache裡,就可以減少query資料庫的次數,是非常好用的技巧。