class ActiveInteraction::Base

@abstract Subclass and override {#execute} to implement a custom

ActiveInteraction::Base class.

Provides interaction functionality. Subclass this to create an interaction.

@example

class ExampleInteraction < ActiveInteraction::Base
  # Required
  boolean :a

  # Optional
  boolean :b, default: false

  def execute
    a && b
  end
end

outcome = ExampleInteraction.run(a: true)
if outcome.valid?
  outcome.result
else
  outcome.errors
end

Public Class Methods

desc(desc = nil) click to toggle source

Get or set the description.

@example

core.desc
# => nil
core.desc('Description!')
core.desc
# => "Description!"

@param desc [String, nil] What to set the description to.

@return [String, nil] The description.

# File lib/active_interaction/base.rb, line 72
def desc(desc = nil)
  if desc.nil?
    @_interaction_desc = nil unless instance_variable_defined?(:@_interaction_desc)
  else
    @_interaction_desc = desc
  end

  @_interaction_desc
end
filters() click to toggle source

Get all the filters defined on this interaction.

@return [Hash{Symbol => Filter}]

# File lib/active_interaction/base.rb, line 85
def filters
  # rubocop:disable Naming/MemoizedInstanceVariableName
  @_interaction_filters ||= {}
  # rubocop:enable Naming/MemoizedInstanceVariableName
end
method_missing(*args, &block) click to toggle source

@private rubocop:disable Style/MissingRespondToMissing

# File lib/active_interaction/base.rb, line 93
def method_missing(*args, &block)
  super do |klass, names, options|
    raise InvalidFilterError, 'missing attribute name' if names.empty?

    names.each { |name| add_filter(klass, name, options, &block) }
  end
end
new(inputs = {}) click to toggle source

@private

# File lib/active_interaction/base.rb, line 162
def initialize(inputs = {})
  @_interaction_raw_inputs = inputs

  populate_filters_and_inputs(Inputs.process(inputs))
end

Private Class Methods

add_filter(klass, name, options, &block) click to toggle source

@param klass [Class] @param name [Symbol] @param options [Hash]

# File lib/active_interaction/base.rb, line 107
def add_filter(klass, name, options, &block)
  raise InvalidFilterError, %("#{name}" is a reserved name) if Inputs.reserved?(name)

  initialize_filter(klass.new(name, options, &block))
end
eagerly_evaluate_default(filter) click to toggle source

@param filter [Filter]

# File lib/active_interaction/base.rb, line 155
def eagerly_evaluate_default(filter)
  default = filter.options[:default]
  filter.default if default && !default.is_a?(Proc)
end
import_filters(klass, options = {}) click to toggle source

Import filters from another interaction.

@param klass [Class] The other interaction. @param options [Hash]

@option options [Array<Symbol>, nil] :only Import only these filters. @option options [Array<Symbol>, nil] :except Import all filters except

for these.

@return (see .filters)

@!visibility public

# File lib/active_interaction/base.rb, line 125
def import_filters(klass, options = {})
  only = options[:only]
  except = options[:except]

  other_filters = klass.filters.dup
  other_filters.select! { |k, _| [*only].include?(k) } if only
  other_filters.reject! { |k, _| [*except].include?(k) } if except

  other_filters.each_value { |filter| initialize_filter(filter) }
end
inherited(klass) click to toggle source

@param klass [Class]

Calls superclass method
# File lib/active_interaction/base.rb, line 137
def inherited(klass)
  klass.instance_variable_set(:@_interaction_filters, filters.dup)

  super
end
initialize_filter(filter) click to toggle source

@param filter [Filter]

# File lib/active_interaction/base.rb, line 144
def initialize_filter(filter)
  attribute = filter.name
  warn "WARNING: Redefining #{name}##{attribute} filter" if filters.key?(attribute)
  filters[attribute] = filter

  attr_accessor attribute

  eagerly_evaluate_default(filter)
end

Public Instance Methods

given?(input, *rest) click to toggle source

Returns `true` if the given key was in the hash passed to {.run}. Otherwise returns `false`. Use this to figure out if an input was given, even if it was `nil`. Keys within nested hash filter can also be checked by passing them in series. Arrays can be checked in the same manor as hashes by passing an index.

@example

class Example < ActiveInteraction::Base
  integer :x, default: nil
  def execute; given?(:x) end
end
Example.run!()        # => false
Example.run!(x: nil)  # => true
Example.run!(x: rand) # => true

@example Nested checks

class Example < ActiveInteraction::Base
  hash :x, default: {} do
    integer :y, default: nil
  end
  array :a, default: [] do
    integer
  end
  def execute; given?(:x, :y) || given?(:a, 2) end
end
Example.run!()               # => false
Example.run!(x: nil)         # => false
Example.run!(x: {})          # => false
Example.run!(x: { y: nil })  # => true
Example.run!(x: { y: rand }) # => true
Example.run!(a: [1, 2])      # => false
Example.run!(a: [1, 2, 3])   # => true

@param input [#to_sym]

@return [Boolean]

@since 2.1.0 rubocop:disable all

# File lib/active_interaction/base.rb, line 233
def given?(input, *rest)
  filter_level = self.class
  input_level = @_interaction_raw_inputs

  [input, *rest].each do |key_or_index|
    if key_or_index.is_a?(Symbol) || key_or_index.is_a?(String)
      key = key_or_index.to_sym
      key_to_s = key_or_index.to_s
      filter_level = filter_level.filters[key]

      break false if filter_level.nil? || input_level.nil?
      if filter_level.accepts_grouped_inputs?
        break false unless input_level.key?(key) || input_level.key?(key_to_s) || Inputs.keys_for_group?(input_level.keys, key)
      else
        break false unless input_level.key?(key) || input_level.key?(key_to_s)
      end

      input_level = input_level[key] || input_level[key_to_s]
    else
      index = key_or_index
      filter_level = filter_level.filters.first.last

      break false if filter_level.nil? || input_level.nil?
      break false unless index.between?(-input_level.size, input_level.size - 1)

      input_level = input_level[index]
    end
  end && true
end
inputs() click to toggle source

Returns the inputs provided to {.run} or {.run!} after being cast based

on the filters in the class.

@return [Hash{Symbol => Object}] All inputs passed to {.run} or {.run!}.

# File lib/active_interaction/base.rb, line 190
def inputs
  @_interaction_inputs
end

Protected Instance Methods

run_validations!() click to toggle source

rubocop:enable all

Calls superclass method
# File lib/active_interaction/base.rb, line 266
def run_validations!
  type_check

  super if errors.empty?
end

Private Instance Methods

populate_filters_and_inputs(inputs) click to toggle source
# File lib/active_interaction/base.rb, line 274
def populate_filters_and_inputs(inputs)
  @_interaction_inputs = Inputs.new

  self.class.filters.each do |name, filter|
    value =
      begin
        filter.clean(inputs[name], self)
      rescue InvalidValueError, MissingValueError, NoDefaultError
        # #type_check will add errors if appropriate.
        # We'll get the original value for the error.
        inputs[name]
      end

    @_interaction_inputs[name] = value
    public_send("#{name}=", value)
  end

  @_interaction_inputs.freeze
end
type_check() click to toggle source
# File lib/active_interaction/base.rb, line 294
def type_check
  run_callbacks(:type_check) do
    Validation.validate(self, self.class.filters, inputs).each do |attr, type, kwargs = {}|
      errors.add(attr, type, **kwargs)
    end
  end
end