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
フィルター DSL を提供するには、コントローラーにモジュールFilterameter::DeclarativeFilters
を含めます。これを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
の場合、クエリ上で個別のメソッドが呼び出されます。
制限事項:同じテーブルに複数の関連付けがあり、両方の関連付けがクエリの一部である可能性がある場合、ネストされたフィルターを直接使用することはできません。代わりに、関連付けを明確にするスコープを構築し、そのスコープに対してフィルターを構築します。
フィルター値を検証する必要がある場合は、ActiveModel 検証とともにvalidates
オプションを使用します。サイズを制限するために使用される包含バリデーターの例を次に示します。
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] } }
inclusion
バリデーターは、追加オプションallow_multiple_values
提供するためにオーバーライドされました。 true の場合、値は配列にすることができ、配列内の各エントリが検証されます。フィルターで 1 つ以上の値を指定できる場合にこれを使用します。
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
フィルターが部分検索 (SQL のLIKE
) を実行する必要がある場合は、部分オプションを指定します。部分オプションでは、ハッシュを受け入れて検索動作を指定します。利用可能なオプションは次のとおりです。
ショートカットは 2 つあります。 : 部分的なオプションはtrue
で宣言でき、デフォルトを使用するだけです。または、部分オプションはpartial: :from_start
のように match オプションを使用して直接宣言できます。
filter :description , partial : true
filter :department_name , partial : :from_start
filter :reason , partial : { match : :dynamic , case_sensitive : true }
match
オプションは、検索する場所を定義します (ワイルドカードが表示される場所を制御します)。
範囲オプションを指定すると、範囲、最小値、または最大値による検索が可能になります。 (これらはすべて含まれます。最小値 $10.00 を検索すると、価格 $10.00 のすべてのアイテムが含まれます。)
利用可能なオプションは次のとおりです。
range オプションを使用すると、属性フィルターに加えて、最小および最大のクエリ パラメーターも指定できることになります。パラメーター名は、属性名に接尾辞_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
ソートには単数の関連付けのみが有効です。コレクションの関連付けは複数の値を返す可能性があり、並べ替えが不定になります。
並べ替えに使用されるスコープは 1 つの引数を受け入れる必要があります。パラメータに応じて、 :asc
または:desc
のいずれかが渡されます。
上記のスコープの例は、次のように定義できます。
def self . by_created_at ( dir )
order ( created_at : dir )
end
デフォルトの並べ替えは、 default_sort
を使用して宣言できます。引数では、宣言された並べ替えフィルターまたは並べ替え可能なフィルターを名前で 1 つ以上指定する必要があります。デフォルトでは、順序は昇順です。降順が必要な場合は、列名のシンボルを :desc にマップできます。
default_sort updated_at : :desc , :description
一貫した結果を提供するために、ソートは常に適用されます。デフォルトが指定されていない場合は、主キーの降順が使用されます。
フィルターを適用してクエリを構築するには、どの程度の制御や可視性が必要かに応じて 2 つの方法があります。
build_filtered_query
を使用するbuild_query_from_filters
手動で呼び出すbuild_filtered_query
を使用するクエリを構築する必要があるコントローラー アクションのアクション コールバックbuild_filtered_query
の前に追加します。これは、 ApplicationController
内で行うことも、ケースバイケースで行うこともできます。
コールバックを使用する場合、変数名はモデル名を複数形にしたものになります。たとえば、Photo モデルは変数@photos
使用してクエリを保存します。変数名は、 filter_query_var_name
を使用して明示的に指定できます。たとえば、クエリが@data
として保存されている場合は、次を使用します。
filter_query_var_name :data
さらに、 filter_model
コマンドは変数名を指定するためのオプションの 2 番目のパラメーターを受け取ります。モデルと変数名の両方をこのショートカットで指定できます。たとえば、Picture モデルを使用し、結果を@data
として保存するには、次を使用します。
filter_model 'Picture' , :data
ハッピー パスでは、WidgetsController がウィジェットを提供し、サイズと色でフィルタリングできます。コントローラーは次のようになります。
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
手動で呼び出すクエリを手動で生成するには、 callback を使用する代わりに、 build_query_from_filters
直接呼び出します。
ここでもウィジェット コントローラーを示します。今回はクエリを手動で構築します。
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 は Photo モデルに対してクエリを構築します。コントローラーに名前空間が指定されている場合、モデルは最初に名前空間を使用せずに検索され、次に名前空間を使用して検索されます。
規則で正しいモデルが提供されない場合は、次のようにモデルに明示的に名前を付けることができます。
filter_model 'Picture'
重要: filter_model
宣言を使用する場合は、フィルターまたはソート宣言の前に置く必要があります。
次の 3 つの構成オプションがあります。
構成オプションは、イニシャライザ、環境ファイル、または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 が合格していることを確認するためのアクションに注目してください。 Code Climate は、コードラインを評価するために手動でも使用されます。
バグを報告するには、問題トラッカーを使用して次の情報を提供してください。
テストで問題を再現できた場合は、金の星が与えられます。
テストは RSpec で記述され、ダミー アプリは Docker データベースを使用します。スクリプトbin/start_db.sh
が開始され、テスト データベースが準備されます。これは、テストを実行する前に 1 回限りのステップです。
bin/start_db.rb
bundle exec rspec
テストは、評価を使用して Ruby と Rails のすべての組み合わせに対して実行することもできます。インストールも 1 回限りのステップです。
bundle exec appraisal install
bundle exec appraisal rspec
この gem は、MIT ライセンスの条件に基づいてオープン ソースとして利用できます。