module AttributeEnum::ClassMethods

Attributes

enums[R]

Public Instance Methods

enum(enumerated, values) click to toggle source

Turns a dot-accessible class attribute to an enum (a.k.a bytefield). Class must have read'n'write attribute named [what's being enumerated].

@example

class Payment
  include AttributeEnum
  attr_accessor :status

  def initialize(status = 0)
    @status = status
  end

  enum :status, [ :active, :inactive ]
end

payment = Payment.new

payment.get_status # => :active
payment.inactive? # => false
payment.set_status(:inactive) # => true
payment.set_status(:invalid) # => ArgumentError('unknown value invalid')
payment.active? # => false
payment.active! # => true
payment.statuses # => [:active, :inactive]
payment.get_statuses # => [:active, :inactive]

Payment.statuses # => [:active, :inactive]
Payment.get_statuses # => [:active, :inactive]
Payment.get_status(1) # => :inactive
Payment.get_status(:active) # => 0
Payment.get_status('invalid') # => ArgumentError('valid argument is either Integer or Symbol')
# File lib/attribute_enum.rb, line 48
def enum(enumerated, values)
  if values.is_a? Hash
    values.each do |key,val|
      raise ArgumentError, "index should be numeric, #{key} provided wich it's a #{key.class}" unless key.is_a? Integer
      raise ArgumentError "value should be a symbol, #{val} provided wich it's a #{val.class}" unless val.is_a? Symbol
    end
  elsif values.is_a? Array
    values = Hash[values.map.with_index { |v, i| [i,v] }]
  else
    raise ArgumentError, "#enum expects the second argument to be an array of symbols or a hash like { index => :value }"
  end

  # Symbolize values
  #
  values.each do |key, val|
    s_key = key.to_s
    values[key]   = values[key].to_sym   if values[key]
    values[s_key] = values[s_key].to_sym if values[s_key]
  end

  pluralized = enumerated.to_s.pluralize

  # ###
  # Instance methods
  # ###

  # obj.set_status :active
  #
  define_method "set_#{enumerated}" do |value|
    index = self.class.enums[enumerated].rassoc(value)
    raise ArgumentError.new("unknown value #{ value }") unless (index && (index = index.first))
    self.send("#{enumerated}=", index)
    true
  end

  # obj.get_status # => :active
  #
  define_method "get_#{enumerated}" do
    self.class.enums[enumerated].fetch(self.send(enumerated), nil)
  end

  values.each do |key, value|
    # obj.inactive? # => false
    #
    define_method "#{value}?" do
      self.send("get_#{enumerated}") == value
    end

    # obj.inactive! # => true
    #
    define_method "#{value}!" do
      self.send("set_#{enumerated}", value)
    end
  end

  # obj.statuses # => [:active, :inactive]
  #
  define_method pluralized do
    self.class.enums[enumerated].map{ |k, v| v }
  end

  # obj.get_statuses # => [:active, :inactive]
  #
  alias_method "get_#{pluralized}", pluralized

  # ###
  # Class methods
  # ###

  # Obj.statuses # => [:active, :inactive]
  #
  define_singleton_method pluralized do
    self.enums[enumerated].map{ |k, v| v }
  end

  # Obj.get_statuses # => [:active, :inactive]
  #
  singleton_class.class_eval do
    alias_method "get_#{pluralized}", pluralized
  end

  # Obj.get_status(0) # => :active
  # Obj.get_status(:active) # => 0
  #
  define_singleton_method "get_#{enumerated}" do |arg|
    if arg.is_a? Integer
      self.enums[enumerated].fetch(arg, nil)
    elsif arg.is_a? Symbol
      key = self.enums[enumerated].rassoc(arg)
      key ? key[0] : nil
    else
      raise ArgumentError.new('valid argument is either Integer or Symbol')
    end
  end

  # Fin
  enums[enumerated] = values
end