July 30, 2023

Viiisit [Ruby on Rails] - 常見的查詢方法!

#ruby on rails

怎麼用 Ruby 語法查詢資料?

find

find 方法只能使用 primary key(id) 值來查找,也就是從 id 去找,
返回查詢對象或拋出 exception(例外訊息)!
exception 意即:如果找不到對應的記錄,則會拋出 ActiveRecord::RecordNotFound 錯誤。

1
2
3
# Find the article with primary key (id) 6.
article = Article.find(6)
# => #<Article id: 6, title: "Hello World">

轉換成 SQL 語法:

1
SELECT * FROM articles WHERE (articles.id = 6) LIMIT 1

也可以用陣列方式抓取多個 id:

1
2
3
# Find the articles with primary keys 1 and 6.
article = Article.find([1, 6]) # Or even Article.find(1, 6)
# => [#<Article id: 1, title: "Hey">, #<Article id: 6, title: "Hello World">]

轉換成 SQL 語法:

1
SELECT * FROM articles WHERE (articles.id IN (1,6))
Remark:
若不是所有提供的主鍵都有找到匹配的物件,則 find 方法會拋出 ActiveRecord::RecordNotFound。

有 exception(例外訊息)就可以使用 begin … rescue 來捕捉:

1
2
3
4
5
6
7
def show
begin
article = Article.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to show_path, notice: "沒有這個 Record!"
end
end

find_by

find_by 方法使用 key: value 的 Hash 當作參數來查找記錄,
返回符合條件的第一個查詢對象或 nil。

1
2
3
4
def show
user = User.find_by(name: params[:name])
user.articles # 顯示與該使用者相關的文章
end

find_by!

find_by! 方法其實跟 find_by 差異在如果沒有查到符合條件的資料會回傳 exception(例外訊息)也就是 ActiveRecord::RecordNotFound 錯誤!

where

where 方法接收一個條件可以使用 字串、陣列、物件(key : value)作為參數,
並返回所有符合條件的查詢對象,是一個 ActiveRecord 查詢集合(Relation)。
如果找不到紀錄,會是一個空的 ActiveRecord 查詢結果 []。

Remark:
純字串的條件下,若字串內帶有變數,會有 SQL injection 的風險,所以通常會改用陣列處理!
1
2
3
4
# 不推薦的寫法,容易有 SQL injection 的風險
Model.where("name = '%#{params[:name]}%'")
# 推薦的寫法,使用陣列方式處理
Model.where("name = ?", params[:name])

# SQL injection

SQL injection 是一種常見的安全漏洞,發生在應用程式沒有適當處理用戶輸入的情況下。
當應用程式將用戶輸入直接嵌入 SQL 查詢中,而沒有對輸入進行適當的驗證和處理時,
攻擊者可以利用這一點注入惡意的 SQL 代碼,從而在應用程式的資料庫上執行未授權的操作。

假設一個應用程式接收用戶的輸入並將其直接嵌入 SQL 查詢中:

1
2
3
4
5
6
# 抓取 user 輸入的值
user_input = params[:name]
# SQL 語法
sql_query = "SELECT * FROM users WHERE name = '#{user_input}'"
# 出來的結果會用 SQL 語法來抓
results = ActiveRecord::Base.connection.execute(sql_query)

如果用戶在輸入中插入惡意的 SQL 代碼,攻擊者可能會干擾 SQL 查詢的結構,
從而執行未授權的操作,例如刪除資料、查詢敏感資料或修改資料庫內容。

# SQL Injection Based on 1=1 is Always True

1=1 is Always True 就是所謂惡意的 SQL 代碼,
假如攻擊者在 user_input 輸入: Attacker OR 1=1
這時,攻擊者就可以在毫無驗證的情況下直接通過了,
這個 SQL 查詢的條件永遠為真,導致查詢結果返回所有用戶的資料,而不僅僅是符合用戶輸入的資料。

# 防止 SQL injection:

使用參數化查詢或預處理語句,而不是直接將用戶輸入嵌入 SQL 查詢中。
使用 Active Record 的方式處理條件,例如使用陣列方式處理:

1
Model.where("name = ?", params[:name])

Rails 會將 ? 換成 params[:name] 做查詢。條件式後的元素,對應到條件裡的每個 ?。

where vs find_by

1
2
3
4
5
6
7
# 使用 where 方法
users = User.where(name: 'John')
# users 不會是 nil,即使沒有找到符合條件的使用者,它會是一個空的 ActiveRecord 查詢結果 []

# 使用 find_by 方法
user = User.find_by(name: 'John')
# 如果找到了名為 'John' 的使用者,user 將是該使用者的記錄;如果找不到,user 將是 nil

Summary


參考資料:
Active Record 查詢
【Ruby】每天一點 Rails:find()、find_by()、where()
ActiveRecord中的find、find_by和where方法的差異在哪?
SQL Injection