Programming Journal

学習したことの整理用です。

enum_helpでenumをi18n対応させる&ransackのプルダウン検索に対応させる

前提

enum&ransack&i18n導入済です。

AdminLTEを使って管理者用機能を実装する(トップページ) - Ruby on Rails Learning Diary
ransackで検索機能を実装する - Ruby on Rails Learning Diary

enum role: { general: 0, admin: 1 }

Userモデルのroleカラムで一般ユーザーと管理ユーザーを管理している。

実装したいこと

  • enumで定義したものを翻訳して表示させたい
  • ユーザー一覧画面で、翻訳した状態でプルダウン検索できるようにしたい

f:id:Study-Diary:20200905194554p:plain
ユーザー一覧画面

f:id:Study-Diary:20200905194624p:plain
プルダウン選択

実装の流れ

  • EnumHelp導入
  • 翻訳ファイルの設定
  • Controllerの設定
  • Viewの設定

Install EnumHelp

EnumHelpとは、Enumで定義した値を簡単に翻訳できるgemです。
公式に沿って導入していきます。

GitHub - zmbacker/enum_help: Help ActiveRecord::Enum feature to work fine with I18n and simple_form.

gem 'enum_help'

ターミナルでbundle installします。

I18n local file

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

enumsと複数系になります。
私はenumと記述してエラーを発生させてしまいました。

View

Viewではこのように呼び出すことができます。

<%= user.role_i18n %>

ransackのプルダウン検索実装

ここまでは簡単だったのですが、プルダウン形式で検索対応させるのに苦戦して時間がかかってしまいました。

Controller

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

View

<%= search_form_for @q, url: admin_users_path do |f| %> #送信先はadmin_users_path
  <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: '検索ワード' %>
      </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 t('defaults.search'), class: 'btn btn-primary col-auto' %>
      </div>
    </div>
  </div>
<% end %>

f.select

そもそも、f.selectが分かっていなかったです。

<%= f.select :保存されるカラム名, 
          [ ["表示される文字","保存される値"], 
          ["表示される文字","保存される値"], 
          {オプション}, 
          {htmlオプション} ] %> #class: "form-control"とか。

第三引数のオプションを定義する部分と第四引数のhtmlオプションは省略できます。 ただし、第三引数のオプションを定義しないで第四引数のhtmlオプションを定義するときは第三引数は省略できないので空の{ }として記述します。

第三引数にはpromptオプションやinclude_blankオプションを指定できます。 promptオプションは未選択の時に一番上に表示されるメッセージを定義できるオプションです。 include_blankオプションは先頭に表示されるメッセージに空白行を表示するオプションです。 include_blank: trueのような形で使用します。 使用しないときはfalseを定義します。 またtrue、falseの部分に文字列を入れるとその文字が表示されます。

pikawaka.com

【開発メモ】Ruby on Railsのform_forでドロップダウンリストの選択ボックスを設置する方法 | FREE SWORDER


難しかったところは、ここです。

<%= f.select :role_eq,
              User.roles_i18n.invert.map{|key, value| [key, User.roles[value]]},
              { include_blank: '指定なし' }, #第3引数。先頭に表示するメッセージ
              { class: 'form-control mr-1' } %> #第4引数。htmlオプション。

使用しているメソッドは…
eq(equals) ransackのメソッド。等しい値を持つレコードを返す。
invertメソッドはkeyとvalueを入れ替えてくれる。このメソッド知らなかった。
mapメソッドは、ブロックの戻り値を新しい配列の要素にして返してくれる。

invert (Hash) - APIdock Basic Searching · activerecord-hackery/ransack Wiki · GitHub


EnumHelpを導入したことにより、User.roles_i18nというクラスメソッドで、
{キー1 => 値1, キー2 => 値…}というハッシュを取得できる。

f:id:Study-Diary:20200906094450p:plain
User.roles_i18n

f.selectに合わせて、["表示される文字","保存される値"],の形にしなければらならない。今のままだと逆になっている。
そこで、invertメソッドでキーと値を入れ替えてあげる。

f:id:Study-Diary:20200906094758p:plain
User.roles_i18n.invert

そして、mapメソッドを使って、取得したハッシュを新しく配列にして返す。

配列.map { |変数| 実行する処理 }
mapメソッドは、配列だけではなくHashに対しても使用することができます。 キーがkeyに代入され、値がvalueに代入されます。 ただし、返り値はハッシュではなく、配列になるので注意してください。
【Ruby】mapメソッドの基礎から応用をマスターして、効率的なコード | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

f:id:Study-Diary:20200906093352p:plain
stringだとransackで対応してくれない

単純にこれ [key, value]だと、valueがgeneralやadminとなっている。

ransackではenumで定義したstringには対応しないので、integerにして渡してあげる必要がある。
だから、[key, User.roles[value]]こうしてvalueをintegerにする。

f:id:Study-Diary:20200906101501p:plain
User.roles['general']でintegerに

RansackはRailsのenumに対応していないっぽい | 地方でリモートワーク

f:id:Study-Diary:20200906093250p:plain
最終形

今回苦戦したのは、Rubyの文法が分かっていなかったからなので、チェリー本を早く終わらせます。
また、すぐ個人ブログを検索してコードを真似して実装しちゃいがちなのですが、公式だけで実装できるようになりたいです…

エラー

ユーザー編集ページで、権限を一般ユーザーと管理者で選択できるようにしたくて、このようにしたら1 is not valid roleとエラー。

<%= f.select :role,[['general',0], ['admin', 1]], {}, class: 'form-control' %>

これで登録できない??最初は数字をstringで読み込んでいるから??とか思ったけど、enumに合わせる必要があるみたいです。(何故かはよく分かってない) enum に登録した定数でないと、モデルに登録できない。さっきのransackとは逆でややこしいです。

<%= f.select :role, {一般: 'general', 管理者: 'admin'}, {}, class: 'form-control ' %> #これでもできるけど、

<%= f.select :role, User.roles_i18n.invert, {}, class: 'form-control' %> #こっちのがスマート

参考

Action View フォームヘルパー - Railsガイド

【Rails】enumでのセレクトボックス 作成~都道府県など~ - 未経験からのフルスタックエンジニア

検索用のgem"Ransack"の簡易チュートリアルが書き上がってしまったので公開する - Qiita

Ransackの導入時に躓いたり転んだりした。 - Qiita

ransackとenum_helpを使った検索フォームでの日本語化 | 自転車で通勤しましょ♪ブログ