lib/inum/base.rb
module Inum
require 'active_support/inflector'
require 'i18n'
# Inum base class.
# @abstract Inum::Base class should be a inheritance of Inum::Base.
# @example
# class FruitType < Inum::Base
# define :APPLE, 0
# define :BANANA, 1
# define :ORANGE, 2
# end
class Base
extend Enumerable
include Comparable
# initialize Inum::Base with value.
# @note The instance of Enum Member is singleton.
#
# @param label [Symbol] label of Enum.
# @param value [Integer] value of Enum.
def initialize(label, value)
@label = label.freeze
@label_string = label.to_s.freeze
@label_downcase = @label_string.downcase.freeze
@label_upcase = @label_string.upcase.freeze
@label_underscore = ActiveSupport::Inflector.underscore(@label_string).freeze
@value = value.freeze
@i18n_namespace = (self.class.name ? ActiveSupport::Inflector.underscore(self.class.name).tr('/', '.') : '').freeze
end
# Compare object.
#
# @param object [Object] parsable object.
# @return [Integer] same normal <=>.
def <=> (object)
if (other = self.class.parse(object))
@value <=> other.to_i
else
nil
end
end
# plus object.
#
# @param value [Integer] plus value.(call to_i in this method.)
# @return [Inum::Base] object after plus value.
def + (value)
self.class.parse(@value + value.to_i)
end
# minus object.
#
# @param value [Integer] plus value.(call to_i in this method.)
# @return [Inum::Base] object after plus value.
def - (value)
self.class.parse(@value - value.to_i)
end
# Compare object.
#
# @param [Object] object parsable object.
# @return [Boolean] result.
def eql?(object)
self.equal?(self.class.parse(object))
end
# Label of Enum.
#
# @return [Symbol] Label of Enum.
def label
@label
end
# Enum to String.
#
# @return [String] Label(String).
def to_s
@label_string
end
# Downcase label.
#
# @return [String] downcase label.
def downcase
@label_downcase
end
# Upcase label.
#
# @return [String] uppercase label.
def upcase
@label_upcase
end
# Underscore label.
#
# @return [String] underscore label.
def underscore
@label_underscore
end
# Translate Enum to localized string.(use i18n)
# @note find default `namespace.enum_class_name.enum_label`
#
# @return [String] localized string of Enum.
def translate
I18n.t(self.class.i18n_key(@i18n_namespace, @label_underscore))
end
alias_method :t, :translate
# Value of Enum.
#
# @return [Integer] Value of Enum.
def value
@value
end
alias_method :to_i, :value
# Execute the yield(block) with each member of enum.
#
# @yield [enum] execute the block with enums.
# @yieldparam [Inum::Base] enum enum.
def self.each(&block)
@enums.each(&block)
end
# Get items for rails form.
# @note Type of usable with a Rails form helper.
# @example
# f.select :name, Enum.form_items
# f.select :name, Enum.form_items(except:[:HOGE]) # Except Enum::HOGE
# f.select :name, Enum.form_items(only:[:HOGE, :FUGA]) # Only Enum::HOGE and Enum::FUGA
#
# @param option [Hash] Options.
# @option option [Array<Symbol>] except Except enum.
# @option option [Array<Symbol>] only Limit enum.
# @return [Array<Array>] collection. ex `[["HOGE", 0], ["FUGA", 1]]`
def self.form_items(option = {})
map { |e|
next if option[:except] and option[:except].include?(e.label)
next if option[:only] and !option[:only].include?(e.label)
[e.translate, e.to_s]
}.compact
end
# Override the rule of i18n keys.
# @abstract if change the rule of i18n keys.
#
# @param underscore_class_path [String] underscore class name.
# @param underscore_label [String] underscore label.
# @return [String] i18n key.
def self.i18n_key(underscore_class_path, underscore_label)
"#{underscore_class_path}.#{underscore_label}"
end
# get all labels of Enum.
#
# @return [Array<Symbol>] all labels of Enum.
def self.labels
@enums.map(&:label)
end
# get Enum length.
#
# @return [Integer] count of Enums.
def self.length
@enums.length
end
# Parse object to Enum.
#
# @param object [Object] string or symbol or integer or Inum::Base.
# @return [Inum::Base, Nil] enum or nil.
def self.parse(object)
case object
when String
if /^\d+$/.match(object)
parse(object.to_i)
else
underscore = object.underscore
find { |e| e.underscore == underscore }
end
when Symbol
parse(object.to_s)
when Integer
find { |e| e.value == object }
when self
object
else
nil
end
end
# Parse object to Enum.
# @raise [Inum::NotDefined] raise if not found.
#
# @param object [Object] string or symbol or integer or Inum::Base.
# @return [Inum::Base] enum.
def self.parse!(object)
parse(object) || raise(Inum::NotDefined)
end
# return array of Enums.
#
# @return [Array<Inum>] sorted array of Enums.
def self.to_a
@enums.dup
end
# get all values of Enum.
#
# @return [Array<Integer>] all values of Enum.
def self.values
@enums.map(&:value)
end
# Define Enum.
#
# @param label [Symbol] label of Enum.
# @param value [Integer] value of Enum.(default:autoincrement for 0.)
def self.define(label, value = @enums.size)
validate_enum_args!(label, value)
new(label, value).tap do |enum|
const_set(label, enum)
@enums.push(enum)
@enums.sort! {|a, b| a.to_i <=> b.to_i }
end
end
# Initialize inherited class.
def self.inherited(child)
if self == Inum::Base
child.instance_variable_set(:@enums, Array.new)
else
child.instance_variable_set(:@enums, self.to_a)
end
end
# Validate enum args.
# @raise [ArgumentError] If argument is wrong.
#
# @param label [Object] label of Enum.
# @param value [Integer] value of Enum.
def self.validate_enum_args!(label, value)
unless label.instance_of?(Symbol)
raise ArgumentError, "#{label} isn't instance of Symbol."
end
unless value.kind_of?(Numeric)
raise ArgumentError, "#{value} isn't instance of Integer."
end
if labels.include?(label)
raise ArgumentError, "#{label} already exists label."
end
if values.include?(value)
raise ArgumentError, "#{value} already exists value."
end
end
private_class_method :new, :define, :validate_enum_args!
end
end