ActiveModel::Model
をincludeしたクラスにenumを使いたい場面が出てきたけども、
ActiveRecordしか使えなさそうだったのと、検索しても出てこなかったので、
moduleを作成しました。
ActiveModel::Attributes
を使うのでrails 5.2
以上の環境です。
未検証ですがそれ以下でも一応動くかもしれないです。
やっつけで作っているので、
enumで生成されるメソッドやオプション全てをカバーしていないのと、integerの項目にしか使えないです。
app/models/concerns/active_model/enum.rb
# frozen_string_literal: true
# ActiveModel::Attributesのattributeでenumを利用可能にするモジュール
# ただしintegerにしか使えない
# enum_helpのgemと同様に#{attribute}_i18nメソッドも追加する
module ActiveModel
module Enum
extend ActiveSupport::Concern
module ClassMethods
def enum(definitions)
raise ArgumentError, 'enum attribute: { key: value, key, value, ...} の形式で指定してください' unless valid?(definitions)
attribute = definitions.keys.first
values = definitions.values.first
# getterを上書き
define_method(attribute.to_s) do
values.invert[attributes[attribute.to_s]]
end
# 既存のsetterを別名に退避
alias_method "#{attribute}_value=", "#{attribute}="
# setterを上書き
define_method("#{attribute}=") do |argument|
checked_argument = case argument
when Integer
values.values.include?(argument) ? argument : nil
when String
values[argument.to_sym]
when Symbol
values[argument]
else
raise ArgumentError, 'string, symbol, integerのいずれかを指定してください'
end
raise ArgumentError, "'#{argument}' is not a valid #{attribute}" if checked_argument.nil?
send("#{attribute}_value=", checked_argument)
end
# enum_helpの_i18n的なメソッド追加
define_method("#{attribute}_i18n") do
enumed_value = send(attribute)
I18n.t("enums.#{self.class.name.underscore}.#{attribute}.#{enumed_value}", default: enumed_value.to_s)
end
end
private
def valid?(definitions)
return false unless definitions.is_a?(Hash)
return false if definitions.keys.size != 1
values = definitions.values.first
return false unless values.is_a?(Hash)
return false unless values.keys.all? { |key| key.is_a?(Symbol) }
return false unless values.values.all? { |value| value.is_a?(Integer) }
true
end
end
end
end
使い方
app/models/sample.rb
class Sample
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveModel::Enum
attribute :column_name, :integer
enum column_name: { hoge: 1, piyo: 2, fuga: 3 }
end
config/locales/ja.yml
ja:
enums:
sample:
column:
hoge: ほげ
piyo: ぴよ
fuga: ふが
rails consoleで確認
irb(main):001:0> sample = Sample.new
=> #<Sample:0x000055ec983b15e8 @attributes=#<ActiveModel::AttributeSet:0x000055ec983b14a8 @attributes={"column"=>#<ActiveModel::Attribute::WithCastValue:0x000055ec983b1340 @name="column", @value_before_type_cast=nil, @type=#<ActiveModel::Type::Integer:0x00007f3e74051ea0 @precision=nil, @scale=nil, @limit=nil, @range=-2147483648...2147483648>, @original_attribute=nil>}>>
irb(main):002:0> sample.column = 1
=> 1
irb(main):003:0> sample.column
=> :hoge
irb(main):004:0> sample.column = 'piyo'
=> 'piyo'
irb(main):005:0> sample.column
=> :piyo
irb(main):006:0> sample.column_i18n
=> "ぴよ"
irb(main):007:0> sample.column = 4
Traceback (most recent call last):
2: from (irb):6
1: from app/models/concerns/active_model/enum.rb:37:in `block in enum'
ArgumentError ('4' is not a valid column)