Module: PersistentEnum

Extended by:
ActiveSupport::Concern
Defined in:
lib/persistent_enum.rb,
lib/persistent_enum/railtie.rb,
lib/persistent_enum/version.rb,
lib/persistent_enum/acts_as_enum.rb

Overview

Provide a database-backed enumeration between indices and symbolic values. This allows us to have a valid foreign key which behaves like a enumeration. Values are cached at startup, and cannot be changed.

Defined Under Namespace

Modules: ActsAsEnum, ClassMethods Classes: AbstractDummyModel, EnumSpec, EnumTableInvalid, MissingEnumTypeError, Railtie

Constant Summary collapse

VERSION =
'1.2.4'

Class Method Summary collapse

Class Method Details

.cache_constants(model, required_members, name_attr: 'name', required_attributes: nil, sql_enum_type: nil) ⇒ Object

Given an ‘enum-like’ table with (id, name, …) structure and a set of enum members specified as either [name, …] or {attr: val, …, …}, ensure that there is a row in the table corresponding to each name, and cache the models as constants on the model class.

When using a database such as postgresql that supports native enumerated types, can additionally specify a native enum type to use as the primary key. In this case, the required members will be added to the native type by name using ‘ALTER TYPE` before insertion. This ensures that enum table ids will have predictable values and can therefore be used in database level constraints.



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
# File 'lib/persistent_enum.rb', line 109

def cache_constants(model, required_members, name_attr: 'name', required_attributes: nil, sql_enum_type: nil)
  # normalize member specification
  unless required_members.is_a?(Hash)
    required_members = required_members.each_with_object({}) { |c, h| h[c] = {} }
  end

  # Normalize symbols
  name_attr = name_attr.to_s
  required_members = required_members.each_with_object({}) do |(name, attrs), h|
    h[name.to_s] = attrs.transform_keys(&:to_s)
  end
  required_attributes = required_attributes.map(&:to_s) if required_attributes

  # We need to cope with (a) loading this class and (b) ensuring that all the
  # constants are defined (if not functional) in the case that the database
  # isn't present yet. If no database is present, create dummy values to
  # populate the constants.
  values =
    begin
      cache_constants_in_table(model, name_attr, required_members, required_attributes, sql_enum_type)
    rescue EnumTableInvalid => ex
      # If we're running the application in any way, under no circumstances
      # do we want to introduce the dummy models: crash out now. Our
      # conservative heuristic to detect a 'safe' loading outside the
      # application is whether there is a current Rake task.
      unless Object.const_defined?(:Rake) && Rake.try(:application)&.top_level_tasks.present?
        raise
      end

      # Otherwise, we want to try as hard as possible to allow the
      # application to be initialized enough to run the Rake task (e.g.
      # db:migrate).
      log_warning("Database table initialization error for model #{model.name}, "\
                  'initializing constants with dummy records instead: ' +
                  ex.message)
      cache_constants_in_dummy_class(model, name_attr, required_members, required_attributes, sql_enum_type)
    end

  return cache_values(model, values, name_attr)
end

.cache_records(model, name_attr: :name) ⇒ Object

Given an ‘enum-like’ table with (id, name, …) structure, load existing records from the database and cache them in constants on this class



152
153
154
155
156
157
158
159
160
161
# File 'lib/persistent_enum.rb', line 152

def cache_records(model, name_attr: :name)
  if model.table_exists?
    values = model.scoped
    cache_values(model, values, name_attr)
  else
    puts "Database table for model #{model.name} doesn't exist, no constants cached."
  end
rescue ActiveRecord::NoDatabaseError
  puts "Database for model #{model.name} doesn't exist, no constants cached."
end

.dummy_class(model, name_attr) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/persistent_enum.rb', line 163

def dummy_class(model, name_attr)
  if model.const_defined?(:DummyModel, false)
    dummy_class = model::DummyModel
    unless dummy_class.superclass == AbstractDummyModel && dummy_class.name_attr == name_attr
      raise NameError.new("PersistentEnum dummy class type mismatch: '#{dummy_class.inspect}' does not match '#{model.name}'")
    end

    dummy_class
  else
    nil
  end
end