概要
検索用フォームは、例えばクエリに付く変数名を短くしたいとか、複数のモデルにまたがるとか、単純にカラムに対応してない値で検索させたいなど、モデルを使わず実装することが多いと思います。
モデルを使わないとinput-textやtextareaならまだしも、select、checkboxなどは入力させた値をHTMLタグに反映させるのに、IF文で分岐して属性値を書き込むので結構結構めんどくさいです。数が増えればなおさら。編集フォームの表にサクッと値を戻したい!というのが今回のテーマです。
ポイント
ポイントは3つです。
- ActiveModel::ModelとActiveModel::Attributesを使って検索専用のモデルを作る。
- ActiveRecord::Type::Valueを継承したカスタムタイプを使う。
- パラメータにモデルの名前がつかないようにする。
検索用のモデル
# app/models/searches/sales_type.rb
module Searches
class SalesType
include ActiveModel::Model
include ActiveModel::Attributes
attribute :cid, :integer
attribute :itid, :integer
attribute :price_type, :string
attribute :exclude_percent, :string
attribute :user_relation, :string
attribute :share, :string
attribute :shop_ids_and, :boolean
attribute :shop_ids, :integer_array
end
end
読み込めるところだったらどこでもいいのですが、今回はapp/models/searches/
に作ってみました。型に関してですが、booleanなんかは飛んでくるのは'1'
、'0'
なんですけど、form_withがモデルを想定してるのでtrue
、false
でもうまく値を戻してくれます。もちろんここは:stringでも期待通りの動きをします。:integerになってるところも:stringでも大丈夫ですね。まあ、なんとなく実際の型に合わせといたほうが落ち着きがいいかなと思いこうしました。
問題はinteger_arrayになってるところですね。これは後述するカスタムタイプです。ここはフォームを作るところで下記のように生成しています。
<%= form.collection_check_boxes(:shop_ids, Shop.alived.ordered, :id, :name, include_hidden: false) do |cb|%>
<div class="form-check form-check-inline">
<%= cb.check_box class: 'form-check-input' %>
<%= cb.label class: 'form-check-label' do%>
<%= cb.text %>
<%end%>
</div>
<%- end -%>
こうするとidはintegerの配列になり、最終的なチェックはinclude?
で見ていますので文字列の配列だとチェックが戻りません(ここで結構ハマりました)。
カスタムタイプ
# config/initializers/active_model_types.rb
class IntegerArray < ActiveRecord::Type::Value
def cast_value(value)
value.map(&:to_i)
end
end
ActiveModel::Type.register(:integer_array, IntegerArray)
カスタムタイプはフルにモデルで使うなら多分もっと色々設定しないとダメだと思いますが、今回は値を戻すためだけなのでこれだけでOKです。
パラメータからモデル名を削除
で、このモデルをViewで使えるようにして
def index
@for_search = Searches::SalesType.new(params.permit(
:cid,
:itid,
:price_type,
:exclude_percent,
:user_relation,
:share,
:shop_ids_and,
shop_ids: []
))
<%= form_with model: @for_search, url: sales_type_index_path, local: true, method: :get do |form| %>
viewでこんな感じしてやれば値は戻るのですが、パラメターの名前にsearches_sales_type[cid]
というながったらしいscope名が付きます。まあ、これでもrequireしてやれば動きますが、項目が多かったり、選択肢の多い配列があるとURLが長くなっちゃいます(URLの長さについては色々な調査をおこっなってる方がいらっしゃりますし、SEO的な観点の意見も散見されます。気になるか方は一度調べてみて下さい。)。
そこでsearches_sales_type
を外せないの?と思ってソースを追ってみたらscopeに空文字を渡すとなくなるのがわかりました。
<%= form_with model: @for_search, scope: '', url: sales_type_index_path, local: true, method: :get do |form| %>
ここでscope ||= ...
となっているのでnilでなければ空文字になり、名前はcid
だけになります。値も無事戻りました。
ただ、ドキュメントにはないのでバージョンでどうなるかわかりません。そこは留意しておいてください。