class SmartEnum

A class used to build in-memory graphs of “lookup” objects that are long-lived and can associate among themselves or ActiveRecord instances.

Example:

class Foo < SmartEnum
  attribute :id, Integer
  has_many :accounts, :class_name => "Customer"
end

class Bar < SmartEnum
  attribute :foo_id, Integer
  belongs_to :foo
end

Foo.register_values([{id: 1},{id: 2}])
Bar.register_values([{id:9, foo_id: 1},{id: 10, foo_id: 2}])
Bar.find(1)
# ActiveRecord::RecordNotFound: Couldn't find Bar with 'id'=1
bar = Bar.find(9)
# => #<Bar:0x007fcb6440a1f0 @attributes={:foo_id=>1, :id=>9}>
bar.foo
# => #<Foo:0x007fcb643633c8 @attributes={:id=>1}>
bar.foo.accounts
#  Customer Load (1.3ms)  SELECT "customers".* FROM "customers" WHERE "customers"."foo_id" = 1
# => [#<Customer id: 13, foo_id: 1>, ...]

Methods to make SmartEnum models work in contexts like views where rails expects ActiveRecord instances.

Macros for registring associations with other SmartEnum models

A simple replacement for Virtus.

Example:

class Foo
  include SmartEnum::Attributes
  attribute :id, Integer
  attribute :enabled, Boolean
  attribute :created_at, Time, coercer: ->(arg) { Time.parse(arg) }
end

Foo.new(id: 1, created_at: '2016-1-1')
# => #<Foo:0x007f970a090760 @attributes={:id=>1, :created_at=>"2016-01-01T00:00:00.000-06:00", :enabled=>false}}>
Foo.new(id: 1, created_at: 123)
# TypeError: no implicit conversion of 123 into String
Foo.new(id: 1, enabled: true).enabled?
# => true

Simple emulation of the monetize macro.

Methods for registering values from YAML files

Constants

DEFAULT_TYPE_ATTR_STR

TODO: allow a SmartEnum to define its own type discriminator attr?

DEFAULT_TYPE_ATTR_SYM
INTEGER
VERSION

Attributes

abstract_class[RW]

Public Class Methods

[](id) click to toggle source
# File lib/smart_enum.rb, line 38
def self.[](id)
  ensure_ready_for_reads!
  _enum_storage[id]
end
enum_locked?() click to toggle source
# File lib/smart_enum.rb, line 48
def self.enum_locked?
  @enum_locked
end
lock_enum!() click to toggle source
# File lib/smart_enum.rb, line 108
def self.lock_enum!
  return if @enum_locked
  @enum_locked = true
  @_constantize_cache = nil
  @_descends_from_cache = nil

  _enum_storage.freeze
  class_descendants(self).each do |klass|
    klass.lock_enum!
  end
end
register_value(enum_type: self, detect_sti_types: false, **attrs) click to toggle source
# File lib/smart_enum.rb, line 131
def self.register_value(enum_type: self, detect_sti_types: false, **attrs)
  fail EnumLocked.new(enum_type) if enum_locked?
  type_attr_val = attrs[DEFAULT_TYPE_ATTR_STR] || attrs[DEFAULT_TYPE_ATTR_SYM]
  klass = if type_attr_val && detect_sti_types
            _constantize_cache[type_attr_val] ||= SmartEnum::Utilities.constantize(type_attr_val)
          else
            enum_type
          end
  unless (_descends_from_cache[klass] ||= (klass <= self))
    raise RegistrationError.new("Specified class must derive from #{self}", type: klass, attributes: attrs)
  end
  if klass.abstract_class
    raise RegistrationError, "#{klass} is marked as abstract and may not be registered"
  end

  instance = klass.new(attrs)
  id = instance.id
  unless id
    raise MissingIDError.new(type: klass, attributes: attrs)
  end
  if _enum_storage.has_key?(id)
    raise DuplicateIDError.new(type: klass, attributes: attrs, id: id, existing: _enum_storage[id])
  end
  instance.freeze_attributes
  _enum_storage[id] = instance
  if klass != self
    klass._enum_storage[id] = instance
  end
end
register_values(values, enum_type=self, detect_sti_types: false) click to toggle source
# File lib/smart_enum.rb, line 120
def self.register_values(values, enum_type=self, detect_sti_types: false)
  values.each do |raw_attrs|
    _deferred_attr_hashes << SmartEnum::Utilities.symbolize_hash_keys(raw_attrs).merge(enum_type: enum_type, detect_sti_types: detect_sti_types)
  end
  @_deferred_values_present = true
end
values() click to toggle source
# File lib/smart_enum.rb, line 43
def self.values
  ensure_ready_for_reads!
  _enum_storage.values
end

Protected Class Methods

_enum_storage() click to toggle source
# File lib/smart_enum.rb, line 55
          def _enum_storage
  @_enum_storage ||= {}
end
ensure_ready_for_reads!() click to toggle source
# File lib/smart_enum.rb, line 59
          def ensure_ready_for_reads!
  return true if enum_locked?
  # This method must be called on a base class if in an STI heirarachy,
  # because that is the only place deferred hashes are stored.
  if superclass != SmartEnum
    return superclass.ensure_ready_for_reads!
  end
  if @_deferred_values_present
    # if we have deferred hashes, instantiate them and lock the enum
    process_deferred_attr_hashes
    lock_enum!
  else
    # No instance registration has been attempted, need to call
    # register_values or register_value and lock_enum! first.
    raise EnumUnlocked, self
  end
end

Private Class Methods

_constantize_cache() click to toggle source
# File lib/smart_enum.rb, line 77
        def _constantize_cache
  @_constantize_cache ||= {}
end
_deferred_attr_hashes() click to toggle source
# File lib/smart_enum.rb, line 95
        def _deferred_attr_hashes
  @_deferred_attr_hashes ||= []
end
_descends_from_cache() click to toggle source
# File lib/smart_enum.rb, line 81
        def _descends_from_cache
  @_descends_from_cache ||= {}
end
class_descendants(klass) click to toggle source

The descendants of a class. From activesupport's Class#descendants

# File lib/smart_enum.rb, line 86
        def class_descendants(klass)
  descendants = []
  ObjectSpace.each_object(klass.singleton_class) do |k|
    next if k.singleton_class?
    descendants.unshift k unless k == self
  end
  descendants
end
process_deferred_attr_hashes() click to toggle source
# File lib/smart_enum.rb, line 99
        def process_deferred_attr_hashes
  _deferred_attr_hashes.each do |args|
    register_value(**args)
  end
end