Skip to content

Instantly share code, notes, and snippets.

@alpaca-tc
Created May 23, 2021
Embed
What would you like to do?
ActiveModel用でEnum-likeな定義を使う拡張
# app/models/concerns/enum_attribute.rb
module EnumAttribute
extend ActiveSupport::Concern
class_methods do
# Example
# Model.enum(:state, [:pending, :complete])
# Model.states #=> { 'pending' => 'PENDING', 'complete' => 'COMPLETE' }
#
# Model.new(state: 'PENDING').state #=> 'pending'
# Model.new(state: 'PENDING').pending? #=> true
def enum(name, values, _prefix: false, _suffix: false, **options)
name = name.to_s
enum_values = ActiveSupport::HashWithIndifferentAccess.new
if values.respond_to?(:each_pair)
values.each { |key, value| enum_values[key.to_s] = value }
else
values.each { |value| enum_values[value.to_s] = value.to_s }
end
attribute(name, ActiveModelType::Enum.new(name, enum_values), **options)
define_singleton_method(:"#{name.pluralize}") { enum_values }
enum_values.each_key do |label|
define_enum_query_method(name, label, prefix: _prefix, suffix: _suffix)
end
end
private
def define_enum_query_method(name, label, prefix:, suffix:)
prefix = if prefix == true
"#{name}_"
elsif prefix
"#{prefix}_"
end
suffix = if suffix == true
"_#{name}"
elsif suffix
"_#{suffix}"
end
value_method_name = "#{prefix}#{label}#{suffix}"
# def pending?(); state == "pending" end
define_method(:"#{value_method_name}?") { attribute(name) == label }
end
end
end
# ActiveModel::Type.register(:enum, Enum)
class Enum < ActiveModel::Type::String
# @param [String] name カラム名
# @param [Array<String>] mapping Enumの値の配列
def initialize(name, mapping)
super()
@name = name
@mapping = mapping
end
# @param [any] original_value 変換したい値
#
# @raise [ArgumentError] 許可されていない値の場合は例外を吐く
#
# @return [nil] 変換できなかった場合
# @return [String] 変換できた場合
def cast(original_value)
string_value = super(original_value)
return if string_value.blank?
if mapping.key?(string_value)
string_value.to_s
elsif mapping.value?(original_value)
mapping.key(original_value)
elsif mapping.value?(string_value)
mapping.key(string_value)
else
assert_valid_value(original_value)
end
end
# @raise [ArgumentError] 許可されていない値の場合は例外を吐く
def assert_valid_value(value)
unless value.blank? || mapping.key?(value) || mapping.value?(value)
raise ArgumentError, "'#{value}' is not a valid #{name}"
end
end
private
attr_reader :name, :mapping
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment