44
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

Railsでgem無しに手軽にenumをi18nに対応させる

ActiveRecordのenumをi18n対応させたり、formのselectでの表示に対応するためにgemが幾つかありますが、そこまで複雑なことがやりたかったわけでは無いので、手軽に自分で実装してみます。

ja.yml

モデルのattributeのi18nは通常、'activerecord.attributes.#{model_name}.#{attribute}'に定義します。例えば、Userモデルのnameに適用する場合には、

ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        name: 名前

という形で定義し、User.human_attribute_name(:name)という形でアクセスします。

それでは、例えばfemalemaleというを値を持つgenderという属性を定義します。

class User < ApplicationRecord
  enum gender: [:female, :male]
end

この場合、それぞれの値に対する国際化対応の手段は標準では用意されていません。正確には、定義の仕方は用意されていますが簡単にアクセスできる手段はありません。

定義する場合には、以下のように直接定義することは出来ません。

ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        gender: 
          female: 女性
          male: 男性
        # このネストの仕方は、複数値で形が変わる英語のような言語のための機能とバッティングします

この場合、以下のような形でモデルと同じ階層に定義してあげる必要があります。(http://guides.rubyonrails.org/i18n.html)

ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        name: 名前
        gender: 性別
      user/gender: 
        female: 女性
        male: 男性

このように定義することで、User.human_attribute_name('gender.female')のようにアクセス出来るようになります。また、この場合User.human_attribute_name('gender')も問題なく呼ぶことが出来ます。

Model

定義とアクセスの方法がわかったので、後はモデルにメソッドを追加していきます。

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def self.human_attribute_enum_value(attr_name, value)
    human_attribute_name("#{attr_name}.#{value}")
  end

  def human_attribute_enum(attr_name)
    self.class.human_attribute_enum_value(attr_name, self[attr_name])
  end
end

これで、以下のようにアクセスすることが出来ます。

u = User.new(gender: 'female')
u.human_attribute_enum(:gender) #=> 女性

User.human_attribute_enum_value(:gender, :male) #=> 男性

selectに値を渡したい場合には

User.genders.map {|k, _| [User.human_attribute_enum_value(:gender, k), k] }.to_h #=> {"女性"=>"female","男性"=>"male"}

のように定義出来ます。或いはメソッドを更に追加しても良いかもしれません。

class ApplicationRecord < ActiveRecord::Base
  # ...

  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
end

User.enum_options_for_select(:gender) #=> {"女性"=>"female","男性"=>"male"}

まとめ

むやみにgemを追加していくことは避けたいと思うので、国際化対応が出来ればそれでOKくらいの用途であればこのくらい手軽な定義でも困らないんじゃないかなと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
44
Help us understand the problem. What are the problem?