【Ruby on Rails】enum の日本語化は gem なしで実現できる
はじめに
こんにちは、 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.4 と Ruby 3.2.0 に対応した Docker 化されたサンプルアプリを使います。Nick Janetakis 氏が公開しているこちらのリポジトリを採用しました。
本記事の完成版のソースコードは下記の通りです。適宜ご活用ください。
今回は Rails の scaffold を使って作成した CRUD アプリを題材にして enum の日本語化について解説します。作成した CRUD アプリは本の投稿機能を備えており、本には
- 下書き
- 無料で公開
- 有料で公開
というステータスが存在すると想定してください。このステータスを enum で管理します。
enum の日本語化
日本語化前
最初に enum の日本語化を行う前の状況を確認します。
enum はモデルで下記のように定義されています。
class Book < ApplicationRecord
enum status: {
draft: 0,
free_pub: 1,
paid_pub: 2,
}
end
このままの状態で本のステータスを表示すると、下記のようになります。
ステータスを表示しているコード
<%= book.status %>
enum で定義した名前がそのまま表示されていますね。
デフォルトの言語を日本語に設定
それでは、enum の値の日本語化を行います。
まずは、デフォルトの言語を日本語に設定してください。
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:
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: 有料で公開
draft
、free_pub
、paid_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 モジュールが使われているのがわかります。
human_attribute_name
を使うことで、日本語化された値を取得することができました。
enum の値を日本語で取得するメソッドの作成
日本語化された enum の値が取得方法がわかったところで、これをメソッドとして定義して使えるようにしましょう。
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_value
の self
はクラスを表します。
self
を使うことで様々なモデルに対してこのメソッドを使うことができます。
メソッドの内容は、下記の通りです。
-
attr_name
(カラム名)とvalue
(enumの値)を引数として受け取る。 -
value
が空白(空白とは、空であることに加えてnil
、false
、 空白文字で構成されているということを含む)であるか確認し、空白だった場合には return する。 -
human_attribute_name
メソッドを使って日本語化された enum の値を取得する - 日本語化された enum の値を返す
human_attribute_name
を使って、日本語化された enum の値を返しているんですね。
human_attribute_enum
メソッド
メソッドの内容は、下記の通りです。
-
attr_name
(カラム名)を引数として受け取る。 -
self.class
に対して先ほど定義したself.human_attribute_enum_value
を呼び出す。 - カラム名である
attr_name
と、self.send
を用いて取得した カラムに格納されている値 をself.human_attribute_enum_value
に渡す。 -
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 メソッドを使うのがベストとなります。
ビューに反映
メソッドが定義できましたので、早速ビューで使ってみましょう。
本の一覧を表示するビューで使ってみます。
<% @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 の値が全て日本語化されるはずです。
参考文献
Discussion