資料查找,原來 Ruby on Rails 的 Scope 是這樣用的!

#Scope #ruby #Rails
哲昱
技術文章
資料查找,原來 Ruby on Rails 的 Scope 是這樣用的!

前言

在學習 Ruby on Rails 的過程中,筆者初次與 Scope 的相遇,不是一個很友善的結果,只知道 Scope 在 Rails models 中,可以用來查詢資料庫資料,但是某些符號和其背後的運作,要真正去了解後,才會用的順手,因此,我們藉由這篇文章帶大家一起來了解吧!

Scope 前導,先來認識 Block & Proc & lambda

何謂 Block (程式碼區塊)?

Block (程式碼區塊) 呈現方式為大括號 { ... } 以及 do ... end

Ruby 是一款物件化很徹底的程式語言,在 Ruby 的世界裡面幾乎都是物件,而 Block 就是其中少數的例外之一,它不能獨立存在,需要依附在方法或物件後面,連指定到變數都會造成語法錯誤。

{ puts "Hello, World" }            # 會產生語法錯誤
say_hello = { puts "Hello, World" }   # 會產生語法錯誤

我們經常使用 Block 的方式是依附在方法後面,而我們也可以將 Block 給物件化,那就是使用 Proclambda

如何使用 Proc & lambda ?

直接先來 Proc 的使用方式:

def proc_example
  example = Proc.new { p "I am Proc" }
  example.call
end
proc_example

# 當執行 proc_example 時,會印出 "I am Proc"

這邊我們使用 Proc 類別把 Block 物件化,接下來使用 call 方法來執行。

而 lambda 則是:

def lambda_example
  example = lambda { p "I am lambda" }
  example.call
end
lambda_example

# 當執行 lambda_example 時,會印出 "I am lambda"

有發現嗎? 感覺起來是不是和 Proc 很像!沒錯,因為 lambdaProc 的實體,所以它不用 new,我們是如何知道的呢? 以下這段 code 即可證明 。

def check_method(item)
  puts "A item is a #{item.class}"
  puts "A item instance: #{item.inspect}"
  item.call
end
proc_item   = Proc.new { p 'This is a proc' }
lambda_item = lambda { p 'This is a lamnda' }

check_method proc_item 
check_method lambda_item

# A block is a Proc
# A block instance: #<Proc:0x00007f90d6825208@example.rb:12>
# "This is a proc"
# A block is a Proc
# A block instance: #<Proc:0x00007f90d6824e20@example.rb:13 (lambda)>
# "This is a lamnda"

帶入參數方法

Proc 帶入參數方法

say_hello = Proc.new { |name| puts "你好,#{name}"}
say_hello.call("CY")

lambda 帶入參數方法

say_hello = lambda { |name| puts "你好,#{name}"}
say_hello.call("CY")

lambda 和 Proc 的差異

這兩個幾乎一模一樣,主要只有兩個差異 控制權順序檢查參數

1.控制權順序不同:

def proc_example
  example = Proc.new { return "I am Proc" }
  example.call

  "Proc not return"
end

def lambda_example
  example = lambda { return "I am lambda" }
  example.call

  "Lambda not return"
end

p proc_example # "I am Proc"
p lambda_example # "Lambda not return"

由上範例我們可以得知 lambda 會將控制權丟回呼叫 method 本身,繼續往下執行該程式碼,而 Procreturn 則不會,是立即跳出。

2.檢查參數:

lambda { |name| puts "Hi, #{name}"}.call
=> ArgumentError (wrong number of arguments (given 0, expected 1))

Proc.new { |name| puts "Hi, #{name}"}.call
=> Hi,
nil

從這個範例可以發現 lambda 會對參數進行檢查,如果是 nil 則會直接拋出 error,而 Proc 則是以 nil 當參數帶入。

因此接下來要介紹的 Scope 運用在 RailsActiveRecord Model,要撈取資料時,都會使用 lambda 來進行,原因就是 lambda 更謹慎。

Scope 起手式

使用 Scope 時,首先需注意參數問題,每個 Scope 接受兩個參數:

  1. 第一個是 name,這是你要去呼叫這個 Scope 的名字。
  2. 第二個是 lambda (也可以用 -> 表示),這是我們要執行的程式碼,例如:搜尋條件。

寫起來會像,如以下的程式碼

class Book < ApplicationRecord
  scope :with_author, -> { where(author: "CY") }
end

當我們去執行這個 with_author Scope 時,就會拿到 ActiveRecord::Relation 的物件。

這時候我們也可以來使用組合技

class Book < ApplicationRecord
  scope :with_author, -> { where(author: "CY") }
  scope :with_page_count, -> { where("page_count > 100") }
end

ex.
ruby=
Book.with_author.with_page_count.last(3)

這裡指的是我要尋找作者是 CY 且頁數是大於 100 頁的書,再從這些尋找的書裡面,取最後 3 本。

Scope 加入參數用法

接下來我們要讓 Scope 更靈活一點,這時我們可以加入參數。
沿用剛剛的例子:

class Book < ApplicationRecord
  scope :with_author, -> { where(author: "CY") }
  scope :with_page_count, -> { where("page_count > 100") }
end

當我呼叫 with_author 時,我會去找屬於 CY 的書,但如果我要找的作者,不就還要寫另外的 Scope 或者修改 with_author 這個 Scope ,因此為了解決這個問題,我們可以加入參數的用法,如下:

class Book < ApplicationRecord
  scope :with_author, ->(Name) { where(author: name) }
  scope :with_page_count, ->(count) { where("page_count > ?", count) }
end

如果像搜尋頁數,只要在條件字串裡面加上問號,後面帶入 count 這參數即可。

Scope 和 Class method 比較

其實 ScopeClass method 一樣,都只是個方法(method),
並沒有比較特別或神奇的地方,而 Scope 可以做到的,Class method 也可以,如下:

class Book
  def self.with_author
    where(author: "CY")    
  end
end

既然如此,我們使用 Scope 又有什麼好處呢?
1. 使用 Scope 可以讓我們程式碼更乾淨更好閱讀一點。
2. Scope 只做一件事情,所以當我們在呼叫時,不用擔心會插入其他的事情。
3. 經 Scope 出來一定是 ActiveRecord::Relation,但 Class method 不一定是。

Default Scopes

這個就有神奇的小地方,當我們在 bookModel
ruby=
default_scope { where(published: true) }

這時候我們要撈取 book 資料時,都只會撈到 published 欄位為 true 的 book。

但這時候,如果我們想要撈取 published 欄位為 false 的 book 呢?

這時候就需要使用 unscoped 的方法支援,如下:

unscoped_book = Book.unscoped.find(book_id)

使用時機:大部份用於固定條件查詢。

大家 Search 關於 Default Scopes 資訊的時候,可能會有些建議說不要用,是因為我們設了 Default Scopes,過了一陣子,要再撈取相關資料時,卻忘了這事,導致我們 debug 不易,加上可能要花不少時間來去調整,但我還是秉持著只要知道自己在做什麼,而確信不會為未來的自己造成麻煩,用 Default Scopes 是沒有問題的。

結語

最後複習一下 Scope 這個超酷的功能。Scope 將常用的查詢條件先宣告起來,以備隨時都可以取用,進一步也讓程式變得乾淨易讀,更厲害的是可以串接使用,對於新手可能一開始不容易懂,但一旦理解真的事半功倍。

以上是對 Scope 初步介紹,此篇文章有任何問題歡迎 來信 與我討論。

最後,希望透過這篇文章,能讓大家對 Scope 不再陌生。

謝謝你的閱讀!


👩‍🏫 課務小幫手:

✨ 想掌握 Ruby on Rails 觀念和原理嗎?

我們有開設 🏓 Ruby on Rails 實戰課程 課程唷 ❤️️