class StateGate::Builder

Description

Responsible for generating the state gate engine, along with the Class and Instance helper methods for the submitted Klass. Everything is generated from #initialize when a new instance is created.

Both Class and Instance methods are generated for:

Public Class Methods

new(klass = nil, attribute_name = nil, &config) click to toggle source

Initialize the Builder, creating the state gate and generating all the Class and Instace helper methods on the sumbitted klass.

:klass

The class containing the attribute to be cast as a state gate (Class | required)

:attribute_name

The name of the klass attribute to use for the state gate (Symbol | required)

:config

The configuration block for the state gate. (Block | required)

StateGate::Builder.new(Klass, :attribute_name) do
     ... configuration ...
end
# File lib/state_gate/builder.rb, line 57
def initialize(klass = nil, attribute_name = nil, &config)
  @klass     = klass
  @attribute = attribute_name
  @alias     = nil

  # assert the input is valid
  _assert_klass_is_valid_for_state_gate
  _parse_atttribute_name_for_alias
  _assert_no_existing_state_gate_for_attribute
  _assert_attribute_name_is_a_database_string_column

  # build the engine and cast the attribute
  _build_state_gate_engine(&config)
  _cast_attribute_as_state_gate

  # generate the helper methods
  generate_helper_methods
end

Private Instance Methods

_assert_attribute_name_is_a_database_string_column() click to toggle source

Validate the attribute is a database String attribute, raising an error if not.

meta

  • ensure it’s mapped to a database column

  • ensure it’s a :string databse type

# File lib/state_gate/builder.rb, line 148
def _assert_attribute_name_is_a_database_string_column
  if @klass.column_names.exclude?(@attribute.to_s)
    err :non_db_attr_err, kattr: true

  elsif @klass.columns_hash[@attribute.to_s].type != :string
    err :non_string_column_err, kattr:     true,
                                attr_type: @klass.columns_hash[@attribute.to_s].type
  end
end
_assert_klass_is_valid_for_state_gate() click to toggle source

Validate the klass to ensure it is a ‘Class’ and derived from ActiveRecord, raising an error if not.

# File lib/state_gate/builder.rb, line 96
def _assert_klass_is_valid_for_state_gate
  err(:non_class_err, klass: true) unless @klass.is_a?(Class)
  err(:non_ar_err, klass: true) unless @klass.ancestors.include?(::ActiveRecord::Base)
end
_assert_no_existing_state_gate_for_attribute() click to toggle source

Validate we don’t already have a state gate defined for the attribute, raising an error if not.

# File lib/state_gate/builder.rb, line 131
def _assert_no_existing_state_gate_for_attribute
  return unless @klass.methods(false).include?(:stateables)
  return unless @klass.stateables.keys.include?(@attribute)

  err :existing_state_gate_err, kattr: true
end
_build_state_gate_engine(&config) click to toggle source

Builds a StateGate::Engine for the given attribute and add it to the :stateables repository.

config - the user generated configuration for the engine, including states,

transitions and optional settings
# File lib/state_gate/builder.rb, line 166
def _build_state_gate_engine(&config)
  _initialize_state_gate_repository
  @engine = StateGate::Engine.new(@klass.name, @attribute, &config)
  @klass.stateables[@attribute] = @engine # rubocop:disable Layout/ExtraSpacing
end
_cast_attribute_as_state_gate() click to toggle source

Builds a StateGate::Type with the custom states for the attribute, then casts the attribute.

This ensures that regardless of the setter method used to set a new state, even if transition validations are disabled, an invalid state should never reach the database.

meta

  • retrieve the root attribute name if the supplied attribute is an alias.

  • create a StateGate::Type with attributes states.

  • overwrite the attribute, casting the type as the new StateGate::Type

# File lib/state_gate/builder.rb, line 215
def _cast_attribute_as_state_gate
  states    = @engine.states
  attr_type = StateGate::Type.new(@klass.name, @attribute, states)
  @klass.attribute(@attribute, attr_type, default: @engine.default_state)
end
_initialize_state_gate_repository() click to toggle source

Adds a :stateables class_attribute if it doesn’t already exist and initializes it to an empty Hash.

:stateables contains is a repository for the state gate engines created when generating state gates. The state gate attribute name is used as the key.

Example

Klass.stateables # => {
                                    status:   <StateGate::Engine>,
                                    account:  <StateGate::Engine>
                                  }

Note

The default empty Hash is set after the attribute is created to accommodate ActiveRecord 5.0, even though ActiveRecord 6.0 allows it to be set within the .class_attribute method.

# File lib/state_gate/builder.rb, line 193
def _initialize_state_gate_repository
  return if @klass.methods(false).include?(:stateables)

  @klass.class_attribute(:stateables, instance_writer: false)
  @klass.stateables = {}
end
_parse_atttribute_name_for_alias() click to toggle source

Parse the attribute name to ensure it is a valid input value and detect if it’s an attribute_alias.

meta

  • ensure it exists

  • ensure it’s a Symbol, to avoid string whitespace issues

  • check is it’s a registere attribute

    • update @attribute & @alias

# File lib/state_gate/builder.rb, line 113
def _parse_atttribute_name_for_alias
  if @attribute.nil?
    err :missing_attribute_err, klass: true

  elsif !@attribute.is_a?(Symbol)
    err :attribute_type_err

  elsif @klass.attribute_aliases[@attribute.to_s]
    @alias     = @attribute
    @attribute = @klass.attribute_aliases[@attribute.to_s].to_sym
  end
end
err(err, **args) click to toggle source

Raise an ArgumentError for the given error, using I18n for the message.

err - Symbol key for the I18n message

args - Hash of attributes to pass to the message string.

[:klass] When true, args[:klass] will be updated with the 'KlassName'.
[:kattr] When true, args[:kattr] will be updated with 'KlassName#attribute'.

Example

err(:invalid_attribute_type_err, kattr: true)
# File lib/state_gate/builder.rb, line 236
def err(err, **args)
  args[:klass] = @klass                    if args.dig(:klass) == true
  args[:kattr] = "#{@klass}##{@attribute}" if args.dig(:kattr) == true

  fail ArgumentError, I18n.t("state_gate.builder.#{err}", **args)
end
generate_helper_methods() click to toggle source

Generate the helper methods

# File lib/state_gate/builder.rb, line 80
def generate_helper_methods
  # add the helper methods
  generate_scope_methods
  generate_state_methods
  generate_transition_methods
  generate_transition_validation_methods

  # warn if any state gate attribute methods are redefined
  _generate_method_redefine_detection
end