Class: Axn::Factory

Inherits:
Object
  • Object
show all
Defined in:
lib/axn/factory.rb

Constant Summary collapse

NOT_PROVIDED =
:__not_provided__

Class Method Summary collapse

Class Method Details

.build(callable = nil, superclass: nil, expose_return_as: nil, include: [], extend: [], prepend: [], exposes: [], expects: [], success: nil, error: nil, before: nil, after: nil, around: nil, on_success: nil, on_failure: nil, on_error: nil, on_exception: nil, use: [], async: nil, log_calls: NOT_PROVIDED, log_errors: NOT_PROVIDED, _creating_action_class_for: nil, &block) ⇒ Object

rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/ParameterLists

Raises:

  • (ArgumentError)


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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
# File 'lib/axn/factory.rb', line 9

def build(
  callable = nil,
  # Builder-specific options
  superclass: nil,
  expose_return_as: nil,

  # Module inclusion options
  include: [],
  extend: [],
  prepend: [],

  # Expose standard class-level options
  exposes: [],
  expects: [],
  success: nil,
  error: nil,

  # Hooks
  before: nil,
  after: nil,
  around: nil,

  # Callbacks
  on_success: nil,
  on_failure: nil,
  on_error: nil,
  on_exception: nil,

  # Strategies
  use: [],

  # Async configuration
  async: nil,

  # Logging configuration
  log_calls: NOT_PROVIDED,
  log_errors: NOT_PROVIDED,

  # Internal flag to prevent recursion during action class creation
  # Tracks which target class is having an action class created for it
  _creating_action_class_for: nil,

  &block
)
  raise ArgumentError, "[Axn::Factory] Cannot receive both a callable and a block" if callable.present? && block_given?

  executable = callable || block
  raise ArgumentError, "[Axn::Factory] Must provide either a callable or a block" unless executable

  args = executable.parameters.each_with_object(_hash_with_default_array) { |(type, field), hash| hash[type] << field }

  if args[:opt].present? || args[:req].present? || args[:rest].present?
    raise ArgumentError,
          "[Axn::Factory] Cannot convert callable to action: callable expects positional arguments"
  end
  raise ArgumentError, "[Axn::Factory] Cannot convert callable to action: callable expects a splat of keyword arguments" if args[:keyrest].present?

  if args[:key].present?
    raise ArgumentError,
          "[Axn::Factory] Cannot convert callable to action: callable expects keyword arguments with defaults (ruby does not allow introspecting)"
  end

  expects = _hydrate_hash(expects)
  exposes = _hydrate_hash(exposes)

  Array(args[:keyreq]).each do |field|
    expects[field] ||= {}
  end

  # NOTE: inheriting from wrapping class, so we can set default values (e.g. for HTTP headers)
  _build_axn_class(superclass:, args:, executable:, expose_return_as:, include:, extend:, prepend:, _creating_action_class_for:).tap do |axn|
    expects.each do |field, opts|
      axn.expects(field, **opts)
    end

    exposes.each do |field, opts|
      axn.exposes(field, **opts)
    end

    # Apply logging configuration (always apply if provided to override defaults)
    axn.log_calls(log_calls) unless log_calls == NOT_PROVIDED
    axn.log_errors(log_errors) unless log_errors == NOT_PROVIDED

    # Apply success and error handlers
    _apply_handlers(axn, :success, success, Axn::Core::Flow::Handlers::Descriptors::MessageDescriptor)
    _apply_handlers(axn, :error, error, Axn::Core::Flow::Handlers::Descriptors::MessageDescriptor)

    # Hooks
    axn.before(before) if before.present?
    axn.after(after) if after.present?
    axn.around(around) if around.present?

    # Callbacks
    _apply_handlers(axn, :on_success, on_success, Axn::Core::Flow::Handlers::Descriptors::CallbackDescriptor)
    _apply_handlers(axn, :on_failure, on_failure, Axn::Core::Flow::Handlers::Descriptors::CallbackDescriptor)
    _apply_handlers(axn, :on_error, on_error, Axn::Core::Flow::Handlers::Descriptors::CallbackDescriptor)
    _apply_handlers(axn, :on_exception, on_exception, Axn::Core::Flow::Handlers::Descriptors::CallbackDescriptor)

    # Strategies
    Array(use).each do |strategy|
      if strategy.is_a?(Array)
        strategy_name, *config_args = strategy
        if config_args.last.is_a?(Hash)
          *other_args, config = config_args
          axn.use(strategy_name, *other_args, **config)
        else
          axn.use(strategy_name, *config_args)
        end
      else
        axn.use(strategy)
      end
    end

    # Async configuration
    unless async.nil?
      async_array = Array(async)
      # Skip async configuration if adapter is nil (but not if array is empty)
      if !async_array.empty? && async_array[0].nil?
        # Do nothing - skip async configuration
      else
        _apply_async_config(axn, async_array)
      end
    end

    # Default exposure
    axn.exposes(expose_return_as, optional: true) if expose_return_as.present?
  end
end