iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0
Software Development

SQL rookie 之天天魯一下系列 第 15

Day 15 - CollectionProxy 是什麼咧?(1)

  • 分享至 

  • xImage
  •  

嗨,大家好~!

昨天研究到可以利用to_sql來將會回傳ActiveRecord::Relation 的query 轉換為SQL query statements;後來回顧了一下做專案期間query 的結果及其資料形態,總覺得好像漏了什麼,後來終於想起來、當時很常看到ActiveRecord::Associations::CollectionProxy,索性來了解一下究竟是什麼吧!

# 如當我們要計算屬於first user 的posts 資料筆數,我們會這麼查:

irb(main):002:0> User.first.posts.count
  User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
   (5.0ms)  SELECT COUNT(*) FROM "posts" WHERE "posts"."userId" = $1  [["userId", 1]]
> 10

而這就是今天要研究的目標啦~~

irb(main):003:0> User.first.posts.class
  User Load (0.7ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
Post::ActiveRecord_Associations_CollectionProxy < ActiveRecord::Associations::CollectionProxy # !!!

CollectionProxy!

我們先來看看Rails API 的定義:

Collection proxies in Active Record are middlemen between an association, and its target result set. For example, given

class User < ActiveRecord::Base # 偷修改一下Class 名稱以符合自己的的Db
  has_many :posts
end

user = User.first

The collection proxy returned by user.posts is built from a :has_many association, and delegates to a collection of posts as the target.

This class delegates unknown methods to the association's relation class via a delegate cache.

The target result set is not loaded until needed. For example,

user.posts.count

is computed directly through SQL and does not trigger by itself the instantiation of the actual post records.

翻譯蒟蒻:Collection proxies 為集合代理人,是associations 的中間人和目標結果集;如user.posts會透過user 和posts 的associations 方法形成一集合代理人,該代理人代表了query 目標posts 的結果集

後面的解釋有點困難,如delegate cache 和實體化的過程,這我們明天再研究好了 XD

...

延續自上面的查詢,我就好奇啦,屬於first user 的posts 資料應該也可以透過where 取得吧,那跟透過association 方法形成集合代理人又有什麼差別呢?

我們先來看一下他回傳結果的資料形態:

irb(main):004:0> Post.where(userId: 1).class
Post::ActiveRecord_Relation < ActiveRecord::Relation

irb(main):006:0> User.first.posts.class
  User Load (3.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
Post::ActiveRecord_Associations_CollectionProxy < ActiveRecord::Associations::CollectionProxy

嗯,我們可以看到一個是回傳ActiveRecord_Relation,另一個則是ActiveRecord::Associations::CollectionProxy,也可以看到一個沒涉及query,但另一個有;但不知道是不是因為query 的起始和目標不同所致。

我們先來試試計算資料筆數:

irb(main):005:0> Post.where(userId: 1).count
   (1.0ms)  SELECT COUNT(*) FROM "posts" WHERE "posts"."userId" = $1  [["userId", 1]]
10

irb(main):007:0> User.first.posts.count
  User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
   (0.4ms)  SELECT COUNT(*) FROM "posts" WHERE "posts"."userId" = $1  [["userId", 1]]
10

這邊就能看到其他差異:

  • ActiveRecord_Relation query 的速度比透過代理人的方式慢了些,且這次涉及到了資料表的query
  • CollectionProxy 的速度加總雖然跟前者一樣,但卻分成了兩段執行,先load 才count,因此count 的速度才比較快

在進一步研究CollectionProxy load 機制時,意外發現Rails API 在Rails 5.1.7 對CollectionProxy 的解釋有點不同,我們先來看看:

Association proxies in Active Record are middlemen between the object that holds the association, known as the @owner, and the actual associated object, known as the @target.

The kind of association any proxy is about is available in @reflection. That's an instance of the class ActiveRecord::Reflection::AssociationReflection.

雖然有部分一樣,但卻多提到了三個名詞,owner、target、Reflection

the association proxy in blog.posts has the object in blog as @owner, the collection of its posts as @target, and the @reflection object represents a :has_many macro.

This class delegates unknown methods to @target via method_missing.

對應到上面的例子,owner 為User.first、target 為posts,而reflection object 代表了has_many 的巨集?

不知道這意思是說,CollectionProxy 類別與Reflection 類別有關?這我們明天再繼續研究好了!

今天就先研究到這邊吧!


上一篇
Day 14 - 魯魯亂入SQL 領域(2) - 續闖ActiveRecord::Base 篇
下一篇
Day 16 - CollectionProxy 是什麼咧?(2)
系列文
SQL rookie 之天天魯一下30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言