Module: Axn::Mountable

Extended by:
ActiveSupport::Concern
Defined in:
lib/axn/mountable.rb,
lib/axn/exceptions.rb,
lib/axn/mountable/descriptor.rb,
lib/axn/mountable/helpers/mounter.rb,
lib/axn/mountable/inherit_profiles.rb,
lib/axn/mountable/helpers/validator.rb,
lib/axn/mountable/mounting_strategies.rb,
lib/axn/mountable/helpers/class_builder.rb,
lib/axn/mountable/mounting_strategies/axn.rb,
lib/axn/mountable/mounting_strategies/step.rb,
lib/axn/mountable/helpers/namespace_manager.rb,
lib/axn/mountable/mounting_strategies/_base.rb,
lib/axn/mountable/mounting_strategies/method.rb

Overview

Mountable provides functionality for mounting actions to classes

## Inheritance Behavior

Mounted actions inherit features from their target class in different ways depending on the mounting strategy. Each strategy has sensible defaults, but you can customize inheritance behavior using the ‘inherit` parameter.

### Default Inheritance Modes

  • ‘mount_axn` and `mount_axn_method`: `:lifecycle` - Inherits hooks, callbacks, messages, and async config (but not fields)

  • ‘step`: `:none` - Completely independent to avoid conflicts

### Inheritance Profiles

  • ‘:lifecycle` - Inherits everything except fields (hooks, callbacks, messages, async config)

  • ‘:async_only` - Only inherits async configuration

  • ‘:none` - Completely standalone with no inheritance

You can also use a hash for granular control:

`inherit: { fields: false, hooks: true, callbacks: false, messages: true, async: true }`

Available hash keys: ‘:fields`, `:hooks`, `:callbacks`, `:messages`, `:async`

Examples:

Default inheritance behavior

class MyClass
  include Axn

  before :log_start
  on_success :track_success
  async :sidekiq
end

# mount_axn uses :lifecycle (inherits hooks, callbacks, messages, async)
MyClass.mount_axn :my_action do
  # Will run log_start before and track_success after
end

# step uses :none (completely independent)
MyClass.step :my_step do
  # Will NOT run log_start or track_success
end

Custom inheritance control

# Override step default to inherit lifecycle
MyClass.step :my_step, inherit: :lifecycle do
  # Will now run hooks and callbacks
end

# Use granular control
MyClass.mount_axn :my_action, inherit: { hooks: true, callbacks: false } do
  # Will run hooks but not callbacks
end

Defined Under Namespace

Modules: Helpers, InheritProfiles Classes: Descriptor, DuplicateMountingTypeError, MountingError, MountingStrategies, MountingTypeNotFound

Class Method Summary collapse

Class Method Details

.included(base) ⇒ Object



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
# File 'lib/axn/mountable.rb', line 68

def self.included(base)
  unless base.is_a?(Class)
    # Module host: plain accessor — no class_attribute (Class-only) or inherited hook needed.
    base.instance_variable_set(:@_mounted_axn_descriptors, [])
    base.define_singleton_method(:_mounted_axn_descriptors) { @_mounted_axn_descriptors ||= [] }
    base.define_singleton_method(:_mounted_axn_descriptors=) { |val| @_mounted_axn_descriptors = val }
  end

  if base.is_a?(Class)
    base.class_eval do
      class_attribute :_mounted_axn_descriptors, default: []

      # Eagerly create action class constants for inherited descriptors
      # (e.g. allow TeamsharesAPI::Company::Axns::Get.call to work *without* having to
      # call TeamsharesAPI::Company.get! first)
      def self.inherited(subclass)
        super

        # Only process if we have inherited descriptors from parent
        return unless _mounted_axn_descriptors.any?

        # Skip if subclass doesn't respond to _mounted_axn_descriptors
        # This prevents recursion when creating action classes that inherit from target
        return unless subclass.respond_to?(:_mounted_axn_descriptors)

        # Skip if we're currently creating an action class (prevent infinite recursion)
        # This is necessary because Axn::Factory.build creates classes that inherit from
        # Axn (which includes Axn::Mountable), triggering inherited callbacks during
        # action class creation.
        superclass = subclass.superclass
        creating_for = superclass&.instance_variable_get(:@_axn_creating_action_class_for)
        return if creating_for

        # Skip if this is an action class being created (they're in the Axns namespace)
        # Action classes have names like "ParentClass::Axns::ActionName"
        subclass_name = subclass.name
        return if subclass_name&.include?("::Axns::")

        # Eagerly create constants for all inherited descriptors
        # mounted_axn_for will ensure namespace exists and create the constant
        # If a child overrides, the new descriptor will replace the constant
        _mounted_axn_descriptors.each do |descriptor|
          # This will create the constant if it doesn't exist
          descriptor.mounted_axn_for(target: subclass)
          # Also define namespace methods on the child's namespace
          # This ensures TeamsharesAPI::Company::Axns.get works even though
          # the descriptor was originally mounted on TeamsharesAPI::Base
          # We use define_namespace_methods instead of mount_to_namespace to
          # avoid re-registering the constant (which is already created above)
          descriptor.mount_strategy.define_namespace_methods(descriptor:, target: subclass)
        end
      end
    end
  end

  MountingStrategies.all.each do |(_name, klass)|
    base.extend klass::DSL if klass.const_defined?(:DSL)
  end
end