Class: IS::Enum

Inherits:
Object
  • Object
show all
Extended by:
Enumerable
Includes:
Comparable
Defined in:
lib/is-enum.rb,
lib/is-enum/info.rb

Overview

Note:

Thread safety

Enum definition (Enum.define) and finalization (Enum.finalize!) are thread-safe. Lookup operations are thread-safe after definition.

Note:

Class variables

Uses class variables ( @@enums, @@mutex ) shared across inheritance hierarchy. All enum classes register in global @@enums for Enum.parse.

Note:

Custom attributes

Additional attributes passed to Enum.define are stored in ‘@attrs`. Subclasses may access this hash directly to implement custom properties.

class Status < IS::Enum
  define :error, 1, http_code: 500, retryable: false

  def http_code
    @attrs[:http_code]
  end

  def retryable?
    @attrs[:retryable]
  end
end

Defined Under Namespace

Modules: Info

Instance Attribute Summary collapse

Conversion collapse

Collection collapse

DSL collapse

Ordering collapse

Instance Attribute Details

#descriptionString? (readonly)

Returns optional value description.

Returns:

  • (String, nil)

    optional value description



287
288
289
# File 'lib/is-enum.rb', line 287

def description
  @description
end

#nameSymbol (readonly)

Returns value name.

Returns:

  • (Symbol)

    value name



284
285
286
# File 'lib/is-enum.rb', line 284

def name
  @name
end

#order_noInteger (readonly)

Note:

Non-unique order numbers Multiple values may be defined with the same order_no. This affects sorting order (undefined when equal) and comparison behavior. See #<=> for comparison semantics.

Order No for sorting and comparison

Returns:

  • (Integer)


281
282
283
# File 'lib/is-enum.rb', line 281

def order_no
  @order_no
end

Class Method Details

.[](name_or_order) ⇒ IS::Enum?

Lookup by name or order number. Returns nil if not found. See of for strict lookup that raises on missing value

Parameters:

  • name_or_order (String, Symbol, Integer)

Returns:



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/is-enum.rb', line 117

def [](name_or_order)
  case name_or_order
  when String, Symbol
    key = name_or_order.to_sym
    @values[key] || @aliases[key]
  when Integer
    @values.values.find { |v| v.order_no == name_or_order }
  else
    raise ArgumentError, "Invalid value for name or order_no: #{name_or_order.inspect}", caller_locations
  end
end

.aliasesHash<Symbol, IS::Enum>

Returns hash of alias names to target values.

Returns:

  • (Hash<Symbol, IS::Enum>)

    hash of alias names to target values



142
143
144
# File 'lib/is-enum.rb', line 142

def aliases
  @aliases
end

.define(name, order_no = nil, **attrs) ⇒ IS::Enum (protected)

Defines new enum value or alias.

Examples:

Define values

class Status < IS::Enum
  define :pending, 1, description: "Waiting for processing"
  define :active, 2
  define :archived, alias: :active
end    

Parameters:

  • name (Symbol, String)

    name of the value

  • order_no (Integer, nil) (defaults to: nil)

    explicit order number (auto-generated if nil)

  • attrs (Hash)

    additional attributes

Options Hash (**attrs):

  • :alias (IS::Enum, Symbol, String, nil)

    create alias to existing value

  • :description (String, nil)

    description of the value

Returns:

  • (IS::Enum)

    defined value (or aliased value for alias)

Raises:

  • (ArgumentError)

    on duplicate name, invalid alias, or invalid order_no



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/is-enum.rb', line 192

def define name, order_no = nil, **attrs
  @mutex ||= Thread::Mutex::new
  @mutex.synchronize do
    @sorted = nil
    @values ||= {}
    @aliases ||= {}
    case name
    when String
      name = name.to_sym
    when Symbol
      # do nothing
    else
      raise ArgumentError, "Invalid name: #{ name.inspect }", caller_locations
    end
    raise ArgumentError, "Duplicate value name: #{ name.inspect }", caller_locations if @values.has_key?(name) || @aliases.has_key?(name)
    als = attrs.delete :alias
    case als
    when self
      @aliases[name] = als
      define_singleton_method name do
        als
      end
      return als
    when Symbol, String
      als = als.to_sym
      als_value = @values[als] || @aliases[als]
      raise ArgumentError, "Invalid alias #{ als.inspect }: value not found", caller_locations unless als_value
      @aliases[name] = als_value
      define_singleton_method name do
        als_value
      end
      return als_value
    when nil
      # do nothing
    else
      raise ArgumentError, "Invalid alias value: #{ als.inspect }", caller_locations
    end
    case order_no
    when Integer
      # do nothing
    when nil
      order_no = (@values.values.map(&:order_no).max || 0) + 1
    else
      raise ArgumentError, "Invalid order_no value: #{ order_no.inspect }", caller_locations
    end
    description = attrs.delete :description
    raise ArgumentError, "Invalid description value: #{ description.inspect }", caller_locations unless description.nil? || description.is_a?(String)
    value = new(order_no, name, description, **attrs).freeze
    @values[name] = value
    define_singleton_method name do 
      value
    end
    value
  end
end

.eachEnumerator, self

Returns:

  • (Enumerator, self)


130
131
132
133
134
# File 'lib/is-enum.rb', line 130

def each
  return to_enum(__method__) unless block_given?
  @values.values.sort_by { |v| v.order_no }.each { |v| yield v }
  self
end

.finalize!void (protected)

This method returns an undefined value.

Freezes internal structures, preventing further modifications. After calling, define will raise RuntimeError.



252
253
254
255
256
257
258
# File 'lib/is-enum.rb', line 252

def finalize!
  @mutex ||= Thread::Mutex::new
  @mutex.synchronize do
    @values.freeze
    @aliases.freeze
  end
end

.firstIS::Enum?

Returns first value by order_no, or nil if empty.

Returns:

  • (IS::Enum, nil)

    first value by order_no, or nil if empty



152
153
154
# File 'lib/is-enum.rb', line 152

def first
  values.first
end

.from(value) ⇒ IS::Enum, ...

Converts various types to enum values.

Examples:

Convert range

MyEnum.from(:alpha..:gamma)  # => range of enum values

Convert array

MyEnum.from([:alpha, :beta]) # => [MyEnum.alpha, MyEnum.beta]

Parameters:

  • value (IS::Enum, nil, Range, Set, Enumerable, Symbol, String, Integer)

Returns:



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/is-enum.rb', line 89

def from value
  case value
  when nil
    nil
  when self
    value
  when Range
    Range::new from(value.begin), from(value.end), value.exclude_end?
  when Set
    Set[*value.map { |v| from(v) }]
  when Enumerable
    value.map { |v| from(v) }
  else
    result = self[value] 
    raise ArgumentError, "Invalid value of #{ self }: #{ value.inspect }", caller_locations unless result
    result
  end
end

.lastIS::Enum?

Returns last value by order_no, or nil if empty.

Returns:

  • (IS::Enum, nil)

    last value by order_no, or nil if empty



147
148
149
# File 'lib/is-enum.rb', line 147

def last
  values.last
end

.of(name) ⇒ IS::Enum

Strict lookup by name. Raises if not found. See [] for lenient lookup

Parameters:

  • name (Symbol)

Returns:

Raises:

  • (ArgumentError)

    if name is not a Symbol or value not found



74
75
76
77
78
79
# File 'lib/is-enum.rb', line 74

def of name
  raise ArgumentError, "Invalid name of #{ self }: #{ name.inspect }" unless name.is_a?(Symbol)
  val = @values[name] || @aliases[name]
  raise ArgumentError, "Invalid name of #{ self }: #{ name }" unless val
  return val
end

.parse(source) ⇒ IS::Enum

Note:

Security consideration Converts strings to Symbols internally. Do not use with untrusted user input to avoid memory exhaustion from symbol creation.

In specific enum class: get enum value by name; in IS::Enum itself: parse string like “Class.name” to enum value.

Parameters:

  • source (String)

    the string to parse

Returns:

Raises:

  • (ArgumentError)

    if source is not a String or value not found



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/is-enum.rb', line 51

def parse source
  raise ArgumentError, "Invalid source for parsing: #{ source.inspect }", caller_locations unless source.is_a?(String)
  if self == IS::Enum
    parts = source.split '.'
    raise ArgumentError, "Parsing error from #{ source.inspect }", caller_locations unless parts.is_a?(Array) && parts.size == 2
    cls = @@enums[parts[0]]
    raise ArgumentError, "Enum class not found: #{ parts[0] }", caller_locations unless cls
    val = cls[parts[1].to_sym]
    raise ArgumentError, "#{ cls.name } value not found: #{ parts[1] }", caller_locations unless val
    return val
  else
    val = self[source.to_sym]
    raise ArgumentError, "#{ self.name } value not found: #{ source }", caller_locations unless val
    return val
  end
end

.to_hHash<Symbol => IS::Enum>

Note:

Both canonical names and aliases are included. To distinguish, check aliases for alias keys.

Returns hash of all names and aliases.

Returns:

  • (Hash<Symbol => IS::Enum>)

    hash of all names and aliases



164
165
166
167
168
# File 'lib/is-enum.rb', line 164

def to_h
  result = {}
  result.merge! @values
  result.merge! @aliases
end

.to_rangeRange<IS::Enum>

Returns range from first to last value.

Returns:

  • (Range<IS::Enum>)

    range from first to last value



157
158
159
# File 'lib/is-enum.rb', line 157

def to_range
  (first .. last)
end

.valuesArray<IS::Enum>

Returns:



137
138
139
# File 'lib/is-enum.rb', line 137

def values
  @sorted ||= @values.values.sort_by { |v| v.order_no }
end

Instance Method Details

#<=>(other) ⇒ Integer?

Note:

Comparison semantics ‘==` and `<=>` compare by order_no, while eql? compares object identity. Multiple values may share the same order_no; they compare as equal but are distinct objects.

class Alpha < IS::Enum
  define :alpha, 10
  define :beta, 20
  define :bi, 20
  define :Gamma, 30
  define :g_letter, alias: :Gamma
end
Alpha.beta == Alpha.bi           # => true (same order_no: 20)
Alpha.beta.eql?(Alpha.bi)        # => false (different objects)
Alpha.Gamma.eql?(Alpha.g_letter) # => true (alias is same object)

Returns 1 if self > other; 0 if self == other; -1 if self < other. nil if other is not same type.

Returns:

  • (Integer, nil)

See Also:

  • Comparable


318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/is-enum.rb', line 318

def <=> other
  case other
  when self.class
    self.order_no <=> other.order_no
  when Symbol, String
    self.order_no <=> self.class[other.to_sym]&.order_no
  when Integer
    self.order_no <=> other
  else
    nil
  end
end

#inspectString

Returns detailed inspection string with class, name, order_no and attributes.

Returns:

  • (String)

    detailed inspection string with class, name, order_no and attributes



353
354
355
356
357
358
359
360
# File 'lib/is-enum.rb', line 353

def inspect
  data = [ "#{ self.class }.#{ self.name }", "order_no=#{ @order_no }" ]
  data << "description=#{ @description.inspect }" if @description
  @attrs.each do |key, value|
    data << "#{ key }=#{ value.inspect }"
  end
  "[enum #{ data.join(' ') }]"
end

#succIS::Enum?

Returns the next value by order_no, or nil if last.

Returns:



334
335
336
# File 'lib/is-enum.rb', line 334

def succ
  self.class.values.find { |v| v.order_no > self.order_no }
end

#to_sString

Returns name as string.

Returns:

  • (String)

    name as string



348
349
350
# File 'lib/is-enum.rb', line 348

def to_s
  name.to_s
end

#to_symSymbol

Returns name as symbol.

Returns:

  • (Symbol)

    name as symbol



343
344
345
# File 'lib/is-enum.rb', line 343

def to_sym
  name
end