Module: MagicEnum::ClassMethods

Defined in:
lib/magic_enum/class_methods.rb

Instance Method Summary collapse

Instance Method Details

#define_enum(name, opts = {}) ⇒ Object

Method used to define ENUM attributes in your model. Examples:

STATUSES = {
  :draft     => 0,
  :published => 1,
  :approved  => 2,
}
define_enum :status

Before using define_enum, you should define constant with ENUM options. Constant name would be pluralized enum attribute name. Additional constant named like YourEnumInverted would be created automatically and would contain inverted hash.

Please note: nil and 0 are not the same values!

You could specify additional options:

  • :default - value which will be used when current state of ENUM attribute is invalid or wrong value received. If it has not been specified, min value of the ENUM would be used.

  • :raise_on_invalid - if true an exception would be raised on invalid enum value received. If it is false, default value would be used instead of wrong one.

  • :simple_accessors - if true, additional methods for each ENUM value would be defined in form value?. Methods returns true when ENUM attribute has corresponding value.

  • :enum - string with name of the ENUM hash.

Look the following example:

STATUSES = {
  :unknown   => 0,
  :draft     => 1,
  :published => 2,
  :approved  => 3,
}
define_enum :status, :default => 1, :raise_on_invalid => true, :simple_accessors => true

This example is identical to:

STATUSES = {
  :unknown   => 0,
  :draft     => 1,
  :published => 2,
  :approved  => 3,
}
STATUSES_INVERTED = STATUSES.invert

def self.status_value(status)
  STATUSES[status]
end

def self.status_by_value(value)
  STATUSES_INVERTED[value]
end

def status
  self.class.status_by_value(self[:status]) || self.class.status_by_value(1)
end

def status=(value)
  raise ArgumentError, "Invalid value \"#{value}\" for :status attribute of the #{self.class} model" unless STATUSES.key?(value)
  self[:status] = STATUSES[value]
end

def unknown?
  status == :unknown
end

def draft?
  status == :draft
end

def published?
  status == :published
end

def approved?
  status == :approved
end


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/magic_enum/class_methods.rb', line 85

def define_enum(name, opts = {})
  opts = opts.reverse_merge({
    :raise_on_invalid => false,
    :simple_accessors => false,
    :named_scopes     => false,
    :scope_extensions => false,
  })
  name = name.to_sym

  opts[:enum] ||= name.to_s.pluralize.upcase
  enum = opts[:enum]
  enum_inverted = "#{enum}_INVERTED"
  enum_value = const_get(enum)

  if opts.key?(:default)
    opts[:default] = enum_value[opts[:default]] if opts[:default].is_a?(Symbol)
  else
    opts[:default] = enum_value.values.sort do |a, b|
      if a.nil? and b.nil?
        0
      elsif a.nil?
        -1
      elsif b.nil?
        1
      else
        a <=> b
      end
    end.first
  end

  enum_inverted_value = enum_value.invert
  if !const_defined?(enum_inverted)
    const_set(enum_inverted, enum_value.invert)
  elsif const_get(enum_inverted) != enum_inverted_value
    raise ArgumentError, "Inverted enum constant \"#{enum_inverted}\" is already defined and contains wrong values"
  end

  class_eval <<-RUBY
    def self.#{name}_value(name)
      name = name.to_sym if name.is_a?(String)
      #{enum}[name]
    end

    def self.#{name}_by_value(value)
      #{enum_inverted}[value]
    end

    def #{name}
      self.class.#{name}_by_value(self[:#{name}]) || self.class.#{name}_by_value(#{opts[:default].inspect})
    end

    def #{name}_name
      self.#{name}.to_s
    end

    def #{name}_value
      self[:#{name}] || #{opts[:default].inspect}
    end

    def #{name}=(value)
      value = value.to_sym if value.is_a?(String)
      if value.is_a?(Integer)
        raise ArgumentError, "Invalid value \\"\#{value}\\" for :#{name} attribute of the #{self.name} model" if #{!!opts[:raise_on_invalid]} and !#{enum_inverted}.key?(value)
        self[:#{name}] = value
      else
        raise ArgumentError, "Invalid value \\"\#{value}\\" for :#{name} attribute of the #{self.name} model" if #{!!opts[:raise_on_invalid]} and !#{enum}.key?(value)
        self[:#{name}] = self.class.#{name}_value(value) || #{opts[:default].inspect}
      end
    end
  RUBY

  if opts[:simple_accessors]
    enum_value.keys.each do |key|
      class_eval "def #{key}?() #{name} == :#{key} end"
    end
  end

  # Create named scopes for each enum value
  if opts[:named_scopes]
    scope_definition_method = respond_to?(:named_scope) ? :named_scope : :scope

    enum_value.keys.each do |key|
      define_enum_scope(enum, key.to_s.pluralize, name, key, opts[:scope_extensions])
    end

    define_enum_scope(enum, "of_#{name}", name, nil, opts[:scope_extensions])
  end

end