【Rails】enum_helpを用いてi18n対応セレクトボックスを作成


はじめに


こんな感じのフォームを作ります。

環境

ruby 2.5.3
Rails 5.2.4.2
enum_help (0.0.17)
rails-i18n (5.1.3)

作成法

enumの設定

models/user.rb
class User < ApplicationRecord
  validates :name, presence: true, length: { maximum: 30 }
  validates :description, length: { maximum: 300 }
  validates :privacy, presence: true

  enum privacy: { published: 0, closed: 1 }
end

booleanにしていないのは、今後限定公開などの機能を追加することを想定しているためです。
このようにenumを設定することで、データベースで数値として扱っている値を、Railsでは文字列として擬似的に扱うことができます。

User.find_by(privacy: :published)
  User Load (4.2ms)  SELECT  `users`.* FROM `users` WHERE `users`.`privacy` = 0 LIMIT 1
=> #<User id: 1, name: "名前", description: "詳細", privacy: "published", created_at: "2020-04-10 12:41:31", updated_at: "2020-04-10 12:41:31">

↑SQLでは`privacy` = 0に変換されている。

また、この値は文字列でもシンボルでも指定できます。

user.privacy
=> "closed"
user.privacy = 0
=> 0
user.privacy
=> "published"
user.privacy = :closed
=> :closed
user.privacy
=> "closed"

i18nの設定

Gemfile
gem 'rails-i18n'
gem 'enum_help'

必要なgemを記述して、$ bundle installでインストールします。
そして、application.rbに以下の設定を追加します。

config/application.rb
module TestApp
  class Application < Rails::Application
    # 言語・タイムゾーンを日本に設定
    config.i18n.default_locale = :ja
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
    # config/locales/配下の全てのrb, ymlファイルを読み込み対象とする
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
  end
end

config/locales配下に日本語化用のファイルを用意します。(ファイルの場所や名前は自由)

config/locales/models/ja.yml
ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      id: ID
      created_at: 作成日時
      updated_at: 更新日時
      user:
        name: 名前
        description: 自己紹介
        privacy: 公開設定
  enums:
    user:
      privacy:
        published: 公開
        closed: 非公開

formの作成

form.html.slim
= form_with model: @user, local: true do |f|
  = f.label :name
  = f.text_field :name
  br
  = f.label :description
  = f.text_area :description
  br
  = f.label :privacy
  = f.select :privacy, User.privacies_i18n.invert
  br
  = f.submit

こんな感じです。ポイントはセレクトボックスの値です。
= f.select プロパティ名, 選択肢の配列とすればいいので、

  = f.select :privacy, [['公開', 0], ['非公開', 1]]

これでも可能ですが、なんか嫌です。モデルと関連付けたい。
そこで、gemの出番です。'enum_help'によってそれぞれ以下のようなインスタンスメソッドとクラスメソッドが使えるようになります。

user.privacy_i18n
=> "公開"
User.privacies_i18n
=> {"published"=>"公開", "closed"=>"非公開"}

これを利用して[[表示される選択肢, 渡される値], [表示される選択肢, 渡される値]]の形の配列に置き換えます。

User.privacies_i18n.map { |k, v| [v, k] }
=> [["公開", "published"], ["非公開", "closed"]]
User.privacies_i18n.invert.to_a
=> [["公開", "published"], ["非公開", "closed"]]

どちらも同じですが、後者の方が文字数が少なくシンプルかなと思います。

(追記)
これだけ書いておいて何ですが、配列に変更する必要はありませんでした。ハッシュのままでも行けました。
ということで、User.privacies_i18n.invertが最短です。
そして五年前の記事で既に言及していたので、ここに恥を晒しておきます。

リンク

Enums | Active Record クエリインターフェイス - Railsガイド
Rails 国際化 (i18n) API - Railsガイド