iT邦幫忙

2021 iThome 鐵人賽

DAY 24
0
自我挑戰組

初級紅寶石魔法師心得分享。系列 第 24

D-6. Model scope & 建立搜索功能

建立搜索用gem 'ransack'不好嗎? 完整又便利。
真的.....但是有些小東西,自己刻一個出來,還是蠻好玩的。


簡單複習一下。

scope

Model裡的scope也是新手常會被問的內容,畢竟對程式碼維護及重構還是有所幫助,所以稍微複習一下。
當商業邏輯是簡單搜索條件時,多用scope
當商業邏輯有複雜運算時,使用類別方法

第一個Model建立時,可能其相對應的controller會直接打上。

def index
  @roles = Role.all
end

隨著開發,可能index需要不同的排列方式,可能希望只有包含特定資料。

def index
  @roles = Role.order(id: :desc)
end

def some_action
  @roles = Role.where('address LIKE ?', "%屏東%")
end

需求越來越多時,查詢語句也就越來越長。

def some_action
  @roles = Role.where('address LIKE ? OR phone_number LIKE ?', "%屏東%", "%878%").order(id: :desc)
end

scope這個方法可以讓你的controller變乾淨(DRY)。

可以直接在Model

class Role
  scope :scope_name_you_like, -> {where('address LIKE ?', "%屏東%")}
end

controller就可以改寫。

def some_action
  @roles = Role.scope_name_you_like
end

還記得lambda嗎? 就是它。
D-26.Block、Proc、lambda https://ithelp.ithome.com.tw/articles/10260028
所以如果有需求,也可以帶參數。例如

class Role
  scope :whos_pay__high, ->(100000) { where["Pay > ?", 100000] }
end

等等搜索功能就是會利用到帶參數


scopescope之間是可以連在一起使用的。

為你自己學Ruby on Rails
class Product < ApplicationRecord
scope :available, -> { price_over(0).where(is_available: true) }
scope :price_over, ->(p) { where(["price > ?", p]) }
end


建立scope不要為了單一搜索。例如

  scope :first_pingtung_role, -> { where('address LIKE ?', "%屏東%").first }

有需求單獨show可以這樣處理。

class Role
  scope :pingtung_role, -> { where('address LIKE ?', "%屏東%") }
end

# controler
def some_show
  @role = Role.pingtung_role.first
end

保有原本的scope :pingtung_role,也可以重複利用。


直接以scope建立簡單搜索功能。

簡單的搜索功能至少要有一個簡單的搜索框。

view

<%= form_tag roles_path, method: :get do %>
  <%= text_field_tag :search, params[:search], placeholder: "請輸入" %>
  <%= submit_tag "送出", name: nil %>
<% end %>

modle

class Role
  # 這一個是原本給index用的。
  scope :by_last_name, -> { order(:last_name) }
  # 這一個是搜索功能的。
  scope :any_name_you_want, -> (search) { where('address LIKE ?', "%#{search}%") if search.present?}
end

記得兩個%%喔,沒有會變成,address一定要 == #{search}。

controller

def index
  begin params[:search]
    @roles = Role.any_name_you_want(params[:search])
  rescue
    @roles = Role.by_last_name
  end
end

這樣就完成了一個關於地址的簡單搜索,也可以稍稍加工,讓這個搜索對另一個欄位也有反應。

class Role
  scope :any_name_you_want, -> (search) { where('address LIKE ? OR first_name LIKE ?', "%#{search}%", "%#{search}%") if search.present?}
end

scoperender還有callback一起運用,可以再讓code整潔一點。

可能view除了index,還會需要再多一些頁面是跟關聯性有關,或是有某些特定資料的。
例如:

def index
  begin params[:search]
    @roles = Role.any_name_you_want(params[:search])
  rescue
    @roles = Role.by_last_name
  end
end

def phone_number
  @roles = Role.order(:phone_number)
end

def job
  @roles = Role.order(:job)
end

但是這三頁可能長得都一樣,那就不如直接將原本的index.html.erb改為_index.html.erb,將這三頁都render上去。
例如:

index.html.erb

<% render 'index'%>

phone_numbe.html.erb

<% render 'index'%>

job.html.erb

<% render 'index'%>

這時我們再將controller修改一下。

before_action :roles_list, only: [:index, :phone_number, :job]
def index
end

def phone_numbe
end

def job
end

private
def roles_list
  @roles = case action_name
           when "phone_number"
             Role.order(:phone_number)
           when "job"
             Role.order(:job)
           else
             Role.by_last_name
           end.any_name_you_want(params[:search])
end

這三頁除了初始會看到的資料不同,但搜索功能一樣可以共用。
這樣利用scope客製化搜索方法,簡單又兼顧code整潔的搜索功能就完成啦。


上一篇
D-7. Rails API認證功能 && Find All Numbers Disappeared in an Array
下一篇
D-5.Rails route scope
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言