Filterameter 為 Rails 控制器提供聲明式過濾器,以減少樣板程式碼並提高可讀性。您看過(或寫過)此控制器操作幾次?
def index
@films = Films . all
@films = @films . where ( name : params [ :name ] ) if params [ :name ]
@films = @films . joins ( :film_locations ) . merge ( FilmLocations . where ( location_id : params [ :location_id ] ) ) if params [ :location_id ]
@films = @films . directed_by ( params [ :director_id ] ) if params [ :director_id ]
@films = @films . written_by ( params [ :writer_id ] ) if params [ :writer_id ]
@films = @films . acted_by ( params [ :actor_id ] ) if params [ :actor_id ]
end
這是多餘的程式碼,編寫和維護起來有點痛苦。更不用說 RuboCop 會對此說什麼了。如果您可以聲明控制器接受的過濾器不是很好嗎?
filter :name , partial : true
filter :location_id , association : :film_locations
filter :director_id , name : :directed_by
filter :writer_id , name : :written_by
filter :actor_id , name : :acted_by
def index
@films = build_query_from_filters
end
使用 Filterameter 使濾波器參數具有聲明性,從而簡化並加速 Rails 控制器的開發。
這個 gem 需要 Rails 6.1+,並與 ActiveRecord 一起使用。
將此行新增至應用程式的 Gemfile 中:
gem 'filterameter'
然後執行:
$ bundle install
或自己安裝:
$ gem install filterameter
在控制器中包含模組Filterameter::DeclarativeFilters
以提供過濾器 DSL。它可以包含在ApplicationController
中以使所有控制器都可以使用該功能,也可以根據具體情況混合使用。
filter :color
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
filter :brand_name , association : :brand , name : :name
filter :on_sale , association : :price , validates : [ { numericality : { greater_than : 0 } } ,
{ numericality : { less_than : 100 } } ]
沒有選項的過濾器可以使用filters
一次聲明:
filters :color ,
:size ,
:name
可以為每個過濾器指定以下選項。
如果參數的名稱與屬性或範圍的名稱不同,則使用 name 參數指定屬性或範圍的名稱。例如,如果屬性名稱為current_status
但過濾器僅以status
公開,請使用下列命令:
filter :status , name : :current_status
此選項對於嵌套過濾器也很有幫助,以便查詢參數可以使用模型名稱作為前綴。請參閱association
選項的範例。
如果屬性或範圍是嵌套的,則可以透過命名關聯來引用它。例如,如果 manager_id 屬性位於員工的部門記錄中,請使用下列內容:
filter :manager_id , association : :department
屬性或範圍可以嵌套多於一層。使用依序指定關聯的陣列來聲明篩選器。例如,某位員工屬於某個部門,而某個部門又屬於某個業務單位,則可以使用下列指令查詢業務單位名稱:
filter :business_unit_name , name : :name , association : [ :department , :business_unit ]
如果關聯是has_many
則在查詢上呼叫不同的方法。
限制:如果同一個表有多個關聯,且兩個關聯都可以是查詢的一部分,則不能直接使用巢狀篩選器。相反,建立一個消除關聯歧義的範圍,然後針對該範圍建立一個過濾器。
如果應驗證篩選器值,請使用validates
選項和 ActiveModel 驗證。以下是用於限制大小的包含驗證器的範例:
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] } }
inclusion
驗證器已重寫,以提供附加選項allow_multiple_values
。當為 true 時,該值可以是數組,並且數組中的每個條目都將被驗證。當過濾器可以指定一個或多個值時使用此選項。
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
如果篩選器應執行部分搜尋(SQL 的LIKE
),請指定部分選項。部分選項接受哈希值來指定搜尋行為。以下是可用的選項:
有兩種捷徑: :可以使用true
聲明部分選項,它只使用預設值;或者可以直接使用 match 選項聲明partial 選項,例如partial: :from_start
。
filter :description , partial : true
filter :department_name , partial : :from_start
filter :reason , partial : { match : :dynamic , case_sensitive : true }
match
選項定義您要搜尋的位置(然後控制通配符出現的位置):
指定範圍選項以啟用按範圍、最小值或最大值進行搜尋。 (所有這些都包含在內。搜尋最低價格為 10.00 美元的商品將包括所有售價為 10.00 美元的商品。)
以下是可用的選項:
使用範圍選項意味著除了屬性篩選器之外,還可以指定最小和最大查詢參數。參數名稱是屬性名稱加上後綴_min或_max 。
filter :price , range : true
filter :approved_at , range : :min_only
filter :sale_price , range : :max_only
在第一個範例中,查詢參數可以包括price 、 price_min和price_max 。
預設情況下,大多數過濾器都是可排序的。若要防止屬性篩選器可排序,請將該選項設為 false。
filter :price , sortable : false
以下過濾器不可排序:
對於不帶參數的作用域,篩選器應提供一個布林值來指示是否應呼叫該作用域。例如,想像一個名為high_priority
的範圍,其標準可識別高優先級記錄。該範圍將由查詢參數high_priority=true
呼叫。
傳遞high_priority=false
將不會呼叫作用域。這使得添加帶有複選框 UI 的過濾器變得很容易。
接受參數的作用域必須編寫為類別方法,而不是內聯作用域。例如,想像一個名為recent
作用域,它會採用截止日期作為參數。看起來可能是這樣的:
def self . recent ( as_of_date )
where ( 'created_at > ?' , as_of_date )
end
如上所述,大多數屬性過濾器預設是可排序的。如果沒有為屬性聲明過濾器,則可以使用sort
聲明。根據需要使用相同的name
和association
選項。
例如,可以在活動控制器上使用以下聲明,以允許按建立的項目對活動進行排序。
sort :project_created_at , name : :created_at , association : :project
不帶選項的排序可以使用sorts
一次性聲明:
sorts :created_at ,
:updated_at ,
:description
範圍可用於排序,但必須使用sort
(或sorts
)聲明。例如,如果模型包含名為by_created_at
範圍,您可以將以下內容新增至控制器以公開它。
sort :by_created_at
也可以使用name
和association
選項。例如,如果範圍位於專案模型上,則也可以使用association
選項在子活動控制器上使用:
sort :by_created_at , association : :project
只有單數關聯才對排序有效。集合關聯可能會傳回多個值,使排序不確定。
用於排序的範圍必須接受單一參數。根據參數,它將傳遞:asc
或:desc
。
上面的範例範圍可能定義如下:
def self . by_created_at ( dir )
order ( created_at : dir )
end
可以使用default_sort
聲明預設排序。參數應按名稱指定一種或多種已宣告的排序或可排序篩選器。預設情況下,順序為升序。如果需要降序排列,可以將列名符號對應為:desc。
default_sort updated_at : :desc , :description
為了提供一致的結果,請始終應用排序。如果沒有指定預設值,它將使用主鍵降序。
有兩種方法可以應用篩選器和建置查詢,具體取決於需要多少控制和/或可見性:
build_filtered_query
build_query_from_filters
build_filtered_query
在操作回調build_filtered_query
之前新增應建置查詢的控制器操作。這可以在ApplicationController
中完成,也可以根據具體情況進行。
使用回呼時,變數名稱是複數模型名稱。例如,照片模型將使用變數@photos
來儲存查詢。變數名稱可以使用filter_query_var_name
明確指定。例如,如果查詢儲存為@data
,請使用下列命令:
filter_query_var_name :data
此外, filter_model
指令採用可選的第二個參數來指定變數名稱。模型和變數名稱都可以使用此捷徑指定。例如,若要使用 Picture 模型並將結果儲存為@data
,請使用下列命令:
filter_model 'Picture' , :data
在幸福的道路上,WidgetsController 提供 Widgets 服務,可以根據大小和顏色進行過濾。控制器可能如下所示:
class WidgetsController < ApplicationController
include Filterameter :: DeclarativeFilters
before_action :build_filtered_query , only : :index
filter :size
filter :color
def index
render json : @widgets
end
end
build_query_from_filters
若要手動產生查詢,您可以直接呼叫build_query_from_filters
而不是使用回呼。
這是 Widgets 控制器,這次是手動建立查詢:
class WidgetsController < ApplicationController
include Filterameter :: DeclarativeFilters
filter :size
filter :color
def index
@widgets = build_query_from_filters
end
end
此方法可選擇採用起始查詢。如果有一個活動小部件的控制器應該只傳回活動小部件,則可以將以下內容作為起點傳遞到該方法中:
def index
@widgets = build_query_from_filters ( Widget . where ( active : true ) )
end
起始查詢也是提供任何包含以啟用預先載入的好地方:
def index
@widgets = build_query_from_filters ( Widgets . includes ( :manufacturer ) )
end
請注意,起始查詢提供模型,因此不會尋找模型,也不需要model_name
聲明。
Rails 約定用於決定控制器的模型。例如,PhotosController 會針對照片模型建立查詢。如果控制器是命名空間的,則將首先在沒有命名空間的情況下尋找模型,然後在使用命名空間的情況下尋找模型。
如果約定未提供正確的模型,則可以使用以下方式明確命名模型:
filter_model 'Picture'
重要提示:如果使用filter_model
聲明,它必須位於任何過濾器或排序聲明之前。
有以下三個配置選項:
配置選項可以在初始化程式、環境檔案或application.rb
中設定。
可以直接設定選項...
Filterameter.configuration.action_on_undeclared_parameters = :log
....或者可以產生配置:
Filterameter . configure do | config |
config . action_on_undeclared_parameters = :log
config . action_on_validation_failuer = :log
config . filter_key = :f
end
當過濾器參數包含任何未定義的鍵時發生。有效操作為:log
、 :raise
和false
(不執行操作)。預設情況下,開發將記錄日誌,測試將引發,而生產將不執行任何操作。
當過濾器參數驗證失敗時發生。有效操作為:log
、 :raise
和false
(不執行操作)。預設情況下,開發將記錄日誌,測試將引發,而生產將不執行任何操作。
預設情況下,過濾器參數會嵌套在:filter
鍵下。使用此設定可以覆蓋該密鑰。
如果過濾器參數不嵌套,則將其設為 false。這樣做會將過濾器參數限制為僅那些已聲明的參數,這意味著未聲明的參數將被忽略(並且 action_on_undeclared_parameters 配置選項不會發揮作用)。
可以針對每個控制器測試聲明,以捕獲拼字錯誤、錯誤定義的範圍或任何其他問題。方法declarations_validator
被加入到每個控制器,並且可以新增單一控制器測試來驗證該控制器的所有聲明。
RSpec 測試可能如下所示:
expect ( WidgetsController . declarations_validator ) . to be_valid
在 Minitest 中它可能看起來像這樣:
validator = WidgetsController . declarations_validator
assert_predicate validator , :valid? , -> { validator . errors }
過濾器參數從控制器參數中提取,嵌套在鍵filter
下(預設情況下;請參閱配置以更改過濾器鍵)。例如,對大型藍色小工具的請求可能在 URL 上具有以下查詢參數:
?filter[size]=large&filter[color]=blue
在通用搜尋表單上, form_with
表單助理採用允許對參數進行分組的選項scope
:
<%= form_with url: "/search", scope: :filter, method: :get do |form| %>
<%= form.label :size, "Size:" %>
<%= form.text_field :size %>
<%= form.label :color, "Color:" %>
<%= form.text_field :color %>
<%= form.submit "Search" %>
<% end %>
排序也嵌套在過濾器鍵下面:
/widgets?filter[sort]=size
使用數組傳遞多個排序。參數的順序是排序的應用順序。例如,以下首先按大小排序,然後按顏色排序:
/widgets?filter[sort]=size&filter[sort]=color
預設排序是升序,但可以使用可以添加的前綴來控制排序:
+
升序(預設)-
下降例如,以下按大小降序排序:
/widgets?filter[sort]=-size
歡迎反饋、功能請求和提議的更改。請使用問題追蹤器來取得回饋和功能請求。若要直接提出更改,請分叉儲存庫並開啟拉取請求。密切注意操作以確保測試和 Rubocop 順利通過。代碼氣候也被手動用來評估代碼線。
若要報告錯誤,請使用問題追蹤器並提供以下資訊:
如果您能夠透過測試重現該問題,將獲得金星獎勵。
測試是用 RSpec 編寫的,虛擬應用程式使用 Docker 資料庫。腳本bin/start_db.sh
啟動並準備測試資料庫。這是運行測試之前的一次性步驟。
bin/start_db.rb
bundle exec rspec
也可以使用評估在所有 ruby 和 Rails 組合上執行測試。安裝也是一次性步驟。
bundle exec appraisal install
bundle exec appraisal rspec
該 gem 根據 MIT 授權條款作為開源提供。