🦓

[Rails]enum_helpとransackを使ったプルダウン検索 20/20

2023/07/10に公開

はじめに

管理者画面のユーザ一覧画面で、ransackenum_helpを使ってプルダウン検索機能を実装していきます。
ransackをすでにインストール済みなので、enum_helpのインストールから行なっていきます。

環境:

Rails 6.1.7.3
ruby 3.0.0

enum_helpをインストールする

Gemfile
gem 'enum_help'
bundle install

訳文を追加する

ja:
  activerrecord:
    enums:
      user:
        role:
          general: '一般'
          admin: '管理者'

コンソールでi18nメソッドを使って確認します。

irb(main):002:0> User.roles
=> {"general"=>0, "admin"=>1}
irb(main):003:0> User.roles_i18n
=> {"general"=>"一般", "admin"=>"管理者"}

検索オブジェクトを作成する

app/controllers/admin/users_controller.rb
class Admin::UsersController < Admin::BaseController
  skip_before_action :check_admin
  before_action :set_user, only: %i[show edit update destroy]

  def index
    @search = User.ransack(params[:q])
    @users = @search.result(distinct: true).order(created_at: :desc).page(params[:page])
  end

検索フォームを作成する

app/views/admin/users/_search_form.html.erb
<%= search_form_for @search, url: admin_users_path do |f| %>
  <div class="row">
    <div class="form-inline align-items-center mx-auto">
      <div class="col-auto">
        <%= f.search_field :first_name_or_last_name_cont, class: 'form-control', placeholder: t('defaults.search_word') %>
      </div>
      <div class="col-auto">
        <%= f.select :role_eq, User.roles_i18n.invert.map{|key, value| [key, User.roles[value]]}, { include_blank: t('defaults.unspecified') }, { class: 'form-control mr-1' } %>
      </div>
      <div class="col-auto">
        <%= f.submit class: 'btn btn-primary' %>
      </div>
    </div>
  </div>
<% end %>

search_form_for メソッドは、検索用のフォームを作成するためのヘルパーメソッドです。

@search オブジェクトを使用して検索フォームを作成しています。@search は、検索のためのパラメーターを格納するために使用されるオブジェクトです。
url: admin_users_path は、フォームが送信された際のアクション先のURLを指定しています。

<%= f.select :role_eq, ...%> は、ロールの絞り込み用のプルダウンを表示しています。:role_eq は絞り込みのためのカラム名を指定しています。

eqransackのメッチャとなります。
role_eqroleカラムに等しいものを探すとのことです。

invertはハッシューのキーと値を入れ替えうメソッドです。
https://docs.ruby-lang.org/ja/latest/method/Hash/i/invert.html

https://activerecord-hackery.github.io/ransack/getting-started/search-matches/

f.select

f.selectは、Railsのフォームヘルパーメソッドの一つで、ドロップダウンリスト(セレクトボックス)を生成するために使用されます。

例えば、Userモデルにroleという列挙型(enum)があるとします。このrole列挙型には、admingeneralの2つの値があります。その場合、f.selectを使用してroleを選択するドロップダウンリストを作成することができます。

<%= f.select :role, User.roles.keys, prompt: 'ロールを選択してください', class: 'form-control' %>

User.roles.keysは、Userモデルのrole列挙型の全ての値(admingeneral)を配列として取得します。

irb(main):003:0> User.roles.keys
=> ["general", "admin"]

promptオプションは、ドロップダウンリストの最初の選択肢として表示されるテキストを指定します。classオプションは、ドロップダウンリストのスタイルを指定するためのCSSクラスを指定します。

他のオプションについて公式ガイドを参考にしてみてください。
https://railsdoc.com/page/select

User.roles_i18n.invert.map{|key, value| [key, User.roles[value]]}

ransackはenumで定義した文字列に対応しておらず、保存される値のvalueを0や1などのinteger形式で渡す必要があります。

irb(main):004:0> User.roles_i18n
=> {"general"=>"一般", "admin"=>"管理者"}
irb(main):005:0> User.roles_i18n.invert
=> {"一般"=>"general", "管理者"=>"admin"}
irb(main):006:0> User.roles_i18n.invert.map { |value, key| [value, key] }
=> [["一般", "general"], ["管理者", "admin"]]
irb(main):007:0> User.roles["general"]
=> 0
irb(main):008:0> User.roles["admin"]
=> 1
irb(main):009:0> User.roles_i18n.invert.map {|key,value| [key,User.roles[value]]}
=> [["一般", 0], ["管理者", 1]]

HTMLでは値が01になってますね。

訳文を追加する

config/locales/views/ja.yml
ja:
    unspecified: '指定なし'

コンソールで動作を確認します。

Started PATCH "/admin/users/2" for ::1 at 2023-07-10 12:59:28 +0900
Processing by Admin::UsersController#update as HTML
  Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"email"=>"user@test.com", "first_name"=>"", "profile_cache"=>"", "role"=>"admin"}, "commit"=>"更新する", "id"=>"2"}
  User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/application_controller.rb:10:in `set_current_user'
  User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  TRANSACTION (0.1ms)  begin transaction
  ↳ app/controllers/admin/users_controller.rb:15:in `update'
  User Exists? (0.2ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = ? AND "users"."id" != ? LIMIT ?  [["email", "user@test.com"], ["id", 2], ["LIMIT", 1]]
  ↳ app/controllers/admin/users_controller.rb:15:in `update'
  # integerに保存される
  User Update (2.1ms)  UPDATE "users" SET "updated_at" = ?, "role" = ? WHERE "users"."id" = ?  [["updated_at", "2023-07-10 03:59:28.534792"], ["role", 1], ["id", 2]]
  ↳ app/controllers/admin/users_controller.rb:15:in `update'
  TRANSACTION (0.8ms)  commit transaction
  ↳ app/controllers/admin/users_controller.rb:15:in `update'
Redirected to http://localhost:3000/admin/users/2
Completed 302 Found in 16ms (ActiveRecord: 3.4ms | Allocations: 6118)

終わりに

プルダウンの実装がよくあるので慣れていきましょう。

Discussion