使用實體變數instance variable增進rails效能

我們曾經在Ruby面試精選30題 - Day12 千變萬化的變數: class variable, class instance variable 與 instance variable討論過實體變數,可用在實體方法instance method,今天要探討instance variable在rails裡增進效能。


重點摘要:


A. 增進效能前,查詢的記憶體位置一直改變

我們在application.rb寫一個class method可查詢登入的目前user:

application.rb:

class ApplicationController < ActionController
  def current_user
    User.find(session[:user_id])
  end
end

這個基本的類別方法想必大家都很熟悉,我也曾在Ruby面試精選30題 - Day04 玩弄Ruby的方法:instance method與class method這篇文章中舉例各自的差別。類別方法的接收者就是類別本身。(相較於類別方法,需要在類別外再new新物件的當變數接收者的是實體方法。)

這時候去script/console查詢Database中,User的id屬性為1的單筆資料 (:user_id:symbol符號)。儘管我們送出的程式碼都是User.find,但傳回的User記憶體位置不斷改變。

script/console

 >> User.find(1)
 => #<User:0x335c624 @attributes=("name" => "Ting", "id" => "1")>
 >> User.find(1)
 => #<User:0x335a900 @attributes=("name" => "Ting", "id" => "1")>
 >> User.find(1)
 => #<User:0x335b350 @attributes=("name" => "Ting", "id" => "1")>

而在Development log內,相同的指令查詢跑了3次,造成效能低落。

Development log

  Processing ProjectsController#index (for 127.0.0.1 at 2019-01-17)
    Session ID: eb5d28exxxxx
    Parameters: {"action"=> "index", "controller"=>"projects"}
    Project Load (0.000259) SELECT * FROM projects

  Rendering projects/index
  Completed in 0.00619 (161 reqs/sec) | Rendering: 0.00071 (11%) | DB: 0.00026 (4%) | 200 OK [http://localhost/projects/]

  SQL (0.000134) SET SQL_AUTO_IS_NULL=0
  User_Columns (0.001251) SHOW FIELDS FROM users
  User Load (0.000346) SELECT * FROM users WHERE (users.id = 1)
  User Load (0.000293) SELECT * FROM users WHERE (users.id = 1)
  User Load (0.000314) SELECT * FROM users WHERE (users.id = 1)

B. 使用instance variable與or-equals指定運算式增進效能

還記得a ||= b嗎? 如果a尚未初始化/或為空值nil/或為false,a等於b; 其他情況下,a值不變。(請參考:Ruby面試精選30題 - Day09 Ruby的 or-equals 是什麼意思呢?)

我們將
User.find(session[:user_id]) 改寫為
@current_user ||= User.find(session[:user_id])

代表若User.find(session[:user_id])可以查詢到資料(非空值),則指定給@current_user實體變數,讓記憶體位置可以維持固定不變。

application.rb:

class ApplicationController < ActionController
  def current_user
    @current_user ||= User.find(session[:user_id])
  end
end
>> @current_user ||= User.find(1)
#@current_user第一次尚未初始化,因此在資料庫內查詢。
=> #<User:0x335a890 @attributes=("name" => "Ting", "id" => "1")>
>> @current_user ||= User.find(1)
#@current_user已非空值,傳回instance variable屬性,其記憶體位置相同。
=> #<User:0x335a890 @attributes=("name" => "Ting", "id" => "1")>

Development log

  SQL (0.000134) SET SQL_AUTO_IS_NULL=0
  User_Columns (0.001251) SHOW FIELDS FROM users
  User Load (0.000346) SELECT * FROM users WHERE (users.id = 1)
  # 僅需查詢一次

總結

剛開始熟悉的rails的應用就綜合了三篇在2018年寫的Ruby面試文章,這不但是一次有趣的複習,也代表以前做過的努力,都不會白費!

Ref: