edy hub

プログラミングやライフスタイルについて書き綴っています

ActiveRecordのenumで定義した要素を元にセレクトボックスを作成する

はじめに

ActiveRecordenumで定義した要素をセレクトボックスを作りたいときの用法用量です。 ※gemを追加しても同等のことができますがコード量も少ないので自作で進めます。

i18n(Internationalization)について

モデルのattributeのi18nは通常、'activerecord.attributes.#{model_name}.#{attribute}'に定義します。 例えば、Userモデルのnameに適用する場合には、config/locales/ja/activerecord/user.ymlのようなファイルを作成します。

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

取得時は User.human_attribute_name(:name) という形でアクセスします。

enumを管理する場合

本題に移ります。

今回は例として、Userがgenderという属性を定義しているとします。 genderにはfemaleとmaleというを値を持たせます。 - female(女性) - male(男性)

class User < ApplicationRecord
  enum gender: {
    female: 0,
    male: 1
  }
end

この後にしがちな(想定しそうな)yamlの定義の仕方としては下記のような記述でしょうか。

ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        gender: 
          female: 女性
          male: 男性

このようにuesr > gender > attributeの順にネストして直接定義することは出来ません。

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

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

定義方法に注目してください。

これにより下記のようなアクセスが可能になります。

User.human_attribute_name('gender.female')
# => 女性

User.human_attribute_name('gender')
# => 性別
# こちらも問題なく呼べる

Modelに便利メソッドを定義

基底モデルにenum専用のメソッドを追加していきます。 変更するファイルはapp/models/application_record.rbです。

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)
    class.human_attribute_enum_value(attr_name, [attr_name])
  end

end

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

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

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

form用のメソッドも追加

最後にselectに値を渡していきます。 特にメソッドを使わなくともこのように書くことも出来ます。

app/views/users/_form.html.slim

# テンプレートはslimを使用
# app/views/users/_form.html.slimの呼び出し元でUserオブジェクトのインスタンスをローカル変数で定義してあります

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

メソッドを追加して記述するとすると下記のようになります。

app/models/application_record.rb

class ApplicationRecord < ActiveRecord::Base

  # ...

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

end

※ このように使います。

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

最後にformに追加します。

app/views/users/_form.html.slim

= form_for @user do |f|
  = f.select :gender, User.enum_options_for_select(:gender)

これで [["女性", "female"], ["男性", "male"]]の組み合わせのセレクトボックスを生成することができました!