🌐

【Ruby on Rails】enum の日本語化は gem なしで実現できる

2023/02/04に公開

はじめに

こんにちは、 FarStep です。
Rails でアプリケーションを作成していると enum の値を日本語化する場面があると思います。
そんなとき enum_help を導入していませんか?

もちろん enum_help を用いた enum の日本語化も間違った実装ではありません。
しかし、アプリケーションにインストールする gem は少ないに越したことはありません。
なぜなら、

  • gem が多いとアプリケーションの保守が大変になる(Rails との依存関係等)
  • gem はメンテナンスがいつ終わるか分からない

といった理由があるからです。

そこで本記事では gem を使わずに enum の値を日本語化する方法を紹介します。
それでは、始めましょう 🚀

環境

最初に、動作確認を行う環境について説明します。
Ruby と Ruby on Rails のバージョンは下記の通りです。

名前 バージョン
Ruby 3.2.0
Ruby on Rails 7.0.4

本記事では、Rails 7.0.4Ruby 3.2.0 に対応した Docker 化されたサンプルアプリを使います。Nick Janetakis 氏が公開しているこちらのリポジトリを採用しました。

https://github.com/nickjj/docker-rails-example

本記事の完成版のソースコードは下記の通りです。適宜ご活用ください。

https://github.com/FarStep131/enum_ja

今回は Rails の scaffold を使って作成した CRUD アプリを題材にして enum の日本語化について解説します。作成した CRUD アプリは本の投稿機能を備えており、本には

  • 下書き
  • 無料で公開
  • 有料で公開

というステータスが存在すると想定してください。このステータスを enum で管理します。

enum の日本語化

日本語化前

最初に enum の日本語化を行う前の状況を確認します。
enum はモデルで下記のように定義されています。

app/models/book.rb
class Book < ApplicationRecord
  enum status: {
    draft: 0,
    free_pub: 1,
    paid_pub: 2,
  }
end

このままの状態で本のステータスを表示すると、下記のようになります。

ステータスを表示しているコード
<%= book.status %>

enum で定義した名前がそのまま表示されていますね。

デフォルトの言語を日本語に設定

それでは、enum の値の日本語化を行います。
まずは、デフォルトの言語を日本語に設定してください。
config/application.rb を開いて下記コードを追加しましょう。

config/application.rb
class Application < Rails::Application
  # ...略
  
+ config.i18n.default_locale = :ja
    
  # ...略
end

日本語は ja となります。

ロケールファイルの作成

続いて、ロケールファイルを作成します。
Rails には ロケール(local) という多言語化用の言語ファイルが存在します。

rails new コマンドを実行すると、デフォルトで config/locales/en.yml が作成されるはずです。この en.yml がロケールファイルです。config/locales 配下の YAML ファイルが、ローケルファイルとして Rails に認識されます。

今回は、日本語化を行うため config/locales/ja.yml を作成しましょう。

$ touch config/locales/ja.yml

ja.yml 内の書き方

ja.yml が作成できたら、下記コードを記述してください。

ja.yml
ja:
  activerecord:
    models:
      book:attributes:
      book:
        title: タイトル
        body: テキスト
        status: ステータス
      book/status:
        draft: 下書き
        free_pub: 無料で公開
        paid_pub: 有料で公開

書き方のルールは下記の通りです。

  • モデル名を対応させるときは models の階層を作って日本語にする文字を記入。
  • カラム名を対応させるときは attributes の階層を作って日本語にする文字を記入。
  • enum の値を対応させるときには、モデル名/カラム名 の階層を作って日本語にする文字を記入。

今回特に重要なのが、enum の値を日本語に対応させる部分です。

book/status:
  draft: 下書き
  free_pub: 無料で公開
  paid_pub: 有料で公開

draftfree_pubpaid_pub はそれぞれ app/models/book.rb で定義されている名前に一致している必要があります。タイポにご注意ください。

これで enum の値を日本語に対応させることができました。

日本語化された enum の取得

それでは、日本語化された enum の値を取得してみましょう。

下記コマンドを実行して、コンソールを起動しましょう。

$ rails c

コンソールを起動できましたら、下記コマンドを実行してみましょう。

$ Book.human_attribute_name('status.draft')
=> "下書き"

"下書き" が返って来れば成功です 👏
上記コマンドも実行すると、他のステータスも日本語化されているのが確認できるはずです。

$ Book.human_attribute_name('status.free_pub')
=> "無料で公開"
$ Book.human_attribute_name('status.paid_pub')
=> "有料で公開"

ここで、human_attribute_name というメソッドが登場しました。
human_attribute_name とは、ActiveRecord::Base のクラスメソッドであり、内部的に I18n モジュールを利用してくれるメソッドです。I18n モジュールは、ある言語の文言を別の言語の文言に翻訳してくれるモジュールです。
human_attribute_name メソッドの引数にロケールファイルで定義した attributes を渡すと、よしなに翻訳してくれるというわけです。

human_attribute_name は下記コードで定義されています。
メソッドの中で I18n モジュールが使われているのがわかります。

https://github.com/rails/rails/blob/main/activemodel/lib/active_model/translation.rb#L46

human_attribute_name を使うことで、日本語化された値を取得することができました。

enum の値を日本語で取得するメソッドの作成

日本語化された enum の値が取得方法がわかったところで、これをメソッドとして定義して使えるようにしましょう。

app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class

+ def self.human_attribute_enum_value(attr_name, value)
+   return if value.blank?
+   human_attribute_name("#{attr_name}.#{value}")
+ end

+ def human_attribute_enum(attr_name)
+   self.class.human_attribute_enum_value(attr_name, self.send("#{attr_name}"))
+ end
end

定義したメソッドを解説します。

self.human_attribute_enum_value メソッド

self.human_attribute_enum_valueself はクラスを表します。
self を使うことで様々なモデルに対してこのメソッドを使うことができます。
メソッドの内容は、下記の通りです。

  1. attr_name(カラム名)と value(enumの値)を引数として受け取る。
  2. value が空白(空白とは、空であることに加えて nilfalse、 空白文字で構成されているということを含む)であるか確認し、空白だった場合には return する。
  3. human_attribute_name メソッドを使って日本語化された enum の値を取得する
  4. 日本語化された enum の値を返す

human_attribute_name を使って、日本語化された enum の値を返しているんですね。

human_attribute_enum メソッド

メソッドの内容は、下記の通りです。

  1. attr_name(カラム名)を引数として受け取る。
  2. self.class に対して先ほど定義した self.human_attribute_enum_value を呼び出す。
  3. カラム名である attr_name と、self.send を用いて取得した カラムに格納されている値self.human_attribute_enum_value に渡す。
  4. self.human_attribute_enum_value から enum の値が返ってくる。

send とは、レシーバの持っているメソッドを呼び出し、そのメソッドの戻り値を返します。
例えば、下記のように status カラムが "draft" の空のインスタンスを作成します。

$ book = Book.new(status: "draft")

ここで、変数 book に対して send メソッドを実行すると下記のようになります。

$ book.send("status")
=> "draft"

send メソッドの引数にカラムの名前を渡すことで、そのカラムに格納されている値が取得できます。
この取得した値とカラム名を self.human_attribute_enum_value に渡すことで、無事日本語化された enum が得られるというわけですね。

ここで一つ疑問が湧きます。
なぜわざわざ send メソッドを使うのか。self.send("#{attr_name}") ではなく、self.attr_name と書いてはいけないのか。

結論からお伝えすると、self.attr_name と書くと NoMethodError が発生します。
なぜなら、self.attr_name とすると self に対して attr_name メソッドを実行する と解釈されてしまうからです。当然、self には attr_name メソッドが定義されていません。したがって、self.attr_name と書くことはできないのです。
では、self."#{attr_name}" と書けばよいのではないでしょうか。
残念ながらこちらも動作しません。SyntaxError となります。

以上より、今回は send メソッドを使うのがベストとなります。

ビューに反映

メソッドが定義できましたので、早速ビューで使ってみましょう。
本の一覧を表示するビューで使ってみます。

app/views/books/index.html.erb
<% @books.each do |book| %>
  ...略
  <%= book.human_attribute_enum(:status) %>
  ...略
<% end %>

上記のコードを記述すると、下記のように enum の値が日本語化されるはずです。

gem なしで enum の値を日本語化することができました 🎉

おわりに

いかがだったでしょうか。
gem を使わずに enum の値を日本語化する一例を示しました。
手軽に導入できると思いますので、是非使ってみてください。

補足 - セレクトボックスに渡したい場合

日本語化した enum の値をセレクトボックスにも反映させたい場合があると思います。
その場合は、下記のようなメソッドを app/models/application_record.rb 定義すれば OK です。

def self.enum_options_for_select(attr_name)
  self.send(attr_name.to_s.pluralize).map { |k, _| [self.human_attribute_enum_value(attr_name, k), k] }.to_h
end

定義したメソッドは、下記のように使用することができます。

$ Book.enum_options_for_select(:status)
=> {"下書き"=>"draft", "無料で公開"=>"free_pub", "有料で公開"=>"paid_pub"}

キーが日本語化された enum の値、バリューが元々の enum の値であるハッシュが返ってきます。
このハッシュをセレクトボックスに渡せば、表示が日本語になるはずです。

フォームで使ってみましょう。

<%= form_with model: book  do |f| %>
  ...略
  <%= f.select :status, Book.enum_options_for_select(:status) %>
  ...略
<% end %>

上記のコードを記述すると、下記のように enum の値が全て日本語化されるはずです。

参考文献

https://api.rubyonrails.org/classes/ActiveModel/Translation.html

Discussion