axn 0.1.0.pre.alpha.1.1 → 0.1.0.pre.alpha.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -2
  3. data/.tool-versions +1 -0
  4. data/CHANGELOG.md +16 -2
  5. data/CONTRIBUTING.md +1 -1
  6. data/README.md +1 -1
  7. data/docs/.vitepress/config.mjs +18 -10
  8. data/docs/advanced/rough.md +2 -0
  9. data/docs/index.md +11 -3
  10. data/docs/{guide/index.md → intro/overview.md} +11 -32
  11. data/docs/recipes/memoization.md +46 -0
  12. data/docs/{usage → recipes}/testing.md +4 -2
  13. data/docs/reference/action-result.md +32 -9
  14. data/docs/reference/class.md +70 -12
  15. data/docs/reference/configuration.md +28 -15
  16. data/docs/reference/instance.md +96 -13
  17. data/docs/usage/setup.md +0 -2
  18. data/docs/usage/using.md +7 -15
  19. data/docs/usage/writing.md +50 -8
  20. data/lib/action/attachable/base.rb +43 -0
  21. data/lib/action/attachable/steps.rb +47 -0
  22. data/lib/action/attachable/subactions.rb +70 -0
  23. data/lib/action/attachable.rb +17 -0
  24. data/lib/action/{configuration.rb → core/configuration.rb} +1 -1
  25. data/lib/action/{context_facade.rb → core/context_facade.rb} +18 -28
  26. data/lib/action/{contract.rb → core/contract.rb} +21 -10
  27. data/lib/action/{contract_validator.rb → core/contract_validator.rb} +9 -11
  28. data/lib/action/{exceptions.rb → core/exceptions.rb} +11 -0
  29. data/lib/action/{hoist_errors.rb → core/hoist_errors.rb} +3 -2
  30. data/lib/action/{swallow_exceptions.rb → core/swallow_exceptions.rb} +52 -7
  31. data/lib/action/{top_level_around_hook.rb → core/top_level_around_hook.rb} +12 -1
  32. data/lib/axn/factory.rb +116 -0
  33. data/lib/axn/version.rb +1 -1
  34. data/lib/axn.rb +20 -10
  35. metadata +28 -22
  36. data/lib/action/organizer.rb +0 -41
  37. /data/docs/{usage → advanced}/conventions.md +0 -0
  38. /data/docs/{about/index.md → intro/about.md} +0 -0
  39. /data/docs/{advanced → recipes}/validating-user-input.md +0 -0
  40. /data/lib/action/{enqueueable.rb → core/enqueueable.rb} +0 -0
  41. /data/lib/action/{logging.rb → core/logging.rb} +0 -0
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Axn
4
+ class Factory
5
+ class << self
6
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/ParameterLists
7
+ def build(
8
+ # Builder-specific options
9
+ name: nil,
10
+ superclass: nil,
11
+ expose_return_as: :nil,
12
+
13
+ # Expose standard class-level options
14
+ exposes: [],
15
+ expects: [],
16
+ messages: {},
17
+ before: nil,
18
+ after: nil,
19
+ around: nil,
20
+
21
+ # Allow dynamically assigning rollback method
22
+ rollback: nil,
23
+ &block
24
+ )
25
+ args = block.parameters.each_with_object(_hash_with_default_array) { |(type, field), hash| hash[type] << field }
26
+
27
+ if args[:opt].present? || args[:req].present? || args[:rest].present?
28
+ raise ArgumentError,
29
+ "[Axn::Factory] Cannot convert block to action: block expects positional arguments"
30
+ end
31
+ raise ArgumentError, "[Axn::Factory] Cannot convert block to action: block expects a splat of keyword arguments" if args[:keyrest].present?
32
+
33
+ # TODO: is there any way to support default arguments? (if so, set allow_blank: true for those)
34
+ if args[:key].present?
35
+ raise ArgumentError,
36
+ "[Axn::Factory] Cannot convert block to action: block expects keyword arguments with defaults (ruby does not allow introspecting)"
37
+ end
38
+
39
+ expects = _hydrate_hash(expects)
40
+ exposes = _hydrate_hash(exposes)
41
+
42
+ Array(args[:keyreq]).each do |field|
43
+ expects[field] ||= {}
44
+ end
45
+
46
+ # NOTE: inheriting from wrapping class, so we can set default values (e.g. for HTTP headers)
47
+ Class.new(superclass || Object) do
48
+ include Action unless self < Action
49
+
50
+ define_singleton_method(:name) do
51
+ [
52
+ superclass&.name.presence || "AnonymousAction",
53
+ name,
54
+ ].compact.join("#")
55
+ end
56
+
57
+ define_method(:call) do
58
+ unwrapped_kwargs = Array(args[:keyreq]).each_with_object({}) do |field, hash|
59
+ hash[field] = public_send(field)
60
+ end
61
+
62
+ retval = instance_exec(**unwrapped_kwargs, &block)
63
+ expose(expose_return_as => retval) if expose_return_as.present?
64
+ end
65
+ end.tap do |axn| # rubocop: disable Style/MultilineBlockChain
66
+ expects.each do |field, opts|
67
+ axn.expects(field, **opts)
68
+ end
69
+
70
+ exposes.each do |field, opts|
71
+ axn.exposes(field, **opts)
72
+ end
73
+
74
+ axn.messages(**messages) if messages.present?
75
+
76
+ # Hooks
77
+ axn.before(before) if before.present?
78
+ axn.after(after) if after.present?
79
+ axn.around(around) if around.present?
80
+
81
+ # Rollback
82
+ if rollback.present?
83
+ raise ArgumentError, "[Axn::Factory] Rollback must be a callable" unless rollback.respond_to?(:call) && rollback.respond_to?(:arity)
84
+ raise ArgumentError, "[Axn::Factory] Rollback must be a callable with no arguments" unless rollback.arity.zero?
85
+
86
+ axn.define_method(:rollback) do
87
+ instance_exec(&rollback)
88
+ end
89
+ end
90
+
91
+ # Default exposure
92
+ axn.exposes(expose_return_as, allow_blank: true) if expose_return_as.present?
93
+ end
94
+ end
95
+ # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/ParameterLists
96
+
97
+ private
98
+
99
+ def _hash_with_default_array = Hash.new { |h, k| h[k] = [] }
100
+
101
+ def _hydrate_hash(given)
102
+ return given if given.is_a?(Hash)
103
+
104
+ Array(given).each_with_object({}) do |key, acc|
105
+ if key.is_a?(Hash)
106
+ key.each_key do |k|
107
+ acc[k] = key[k]
108
+ end
109
+ else
110
+ acc[key] = {}
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
data/lib/axn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Axn
4
- VERSION = "0.1.0-alpha.1.1"
4
+ VERSION = "0.1.0-alpha.2.1"
5
5
  end
data/lib/axn.rb CHANGED
@@ -4,19 +4,26 @@ module Axn; end
4
4
  require_relative "axn/version"
5
5
 
6
6
  require "interactor"
7
-
8
7
  require "active_support"
9
8
 
10
- require_relative "action/exceptions"
11
- require_relative "action/logging"
12
- require_relative "action/configuration"
13
- require_relative "action/top_level_around_hook"
14
- require_relative "action/contract"
15
- require_relative "action/swallow_exceptions"
16
- require_relative "action/hoist_errors"
9
+ require_relative "action/core/exceptions"
10
+ require_relative "action/core/logging"
11
+ require_relative "action/core/configuration"
12
+ require_relative "action/core/top_level_around_hook"
13
+ require_relative "action/core/contract"
14
+ require_relative "action/core/swallow_exceptions"
15
+ require_relative "action/core/hoist_errors"
16
+ require_relative "action/core/enqueueable"
17
+
18
+ require_relative "axn/factory"
19
+
20
+ require_relative "action/attachable"
17
21
 
18
- require_relative "action/organizer"
19
- require_relative "action/enqueueable"
22
+ def Axn(callable, **) # rubocop:disable Naming/MethodName
23
+ return callable if callable.is_a?(Class) && callable < Action
24
+
25
+ Axn::Factory.build(**, &callable)
26
+ end
20
27
 
21
28
  module Action
22
29
  def self.included(base)
@@ -37,6 +44,9 @@ module Action
37
44
 
38
45
  include Enqueueable
39
46
 
47
+ # --- Extensions ---
48
+ include Attachable
49
+
40
50
  # Allow additional automatic includes to be configured
41
51
  Array(Action.config.additional_includes).each { |mod| include mod }
42
52
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: axn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre.alpha.1.1
4
+ version: 0.1.0.pre.alpha.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kali Donovan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-27 00:00:00.000000000 Z
11
+ date: 2025-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -62,38 +62,44 @@ extra_rdoc_files: []
62
62
  files:
63
63
  - ".rspec"
64
64
  - ".rubocop.yml"
65
+ - ".tool-versions"
65
66
  - CHANGELOG.md
66
67
  - CONTRIBUTING.md
67
68
  - LICENSE.txt
68
69
  - README.md
69
70
  - Rakefile
70
71
  - docs/.vitepress/config.mjs
71
- - docs/about/index.md
72
+ - docs/advanced/conventions.md
72
73
  - docs/advanced/rough.md
73
- - docs/advanced/validating-user-input.md
74
- - docs/guide/index.md
75
74
  - docs/index.md
75
+ - docs/intro/about.md
76
+ - docs/intro/overview.md
77
+ - docs/recipes/memoization.md
78
+ - docs/recipes/testing.md
79
+ - docs/recipes/validating-user-input.md
76
80
  - docs/reference/action-result.md
77
81
  - docs/reference/class.md
78
82
  - docs/reference/configuration.md
79
83
  - docs/reference/instance.md
80
- - docs/usage/conventions.md
81
84
  - docs/usage/setup.md
82
- - docs/usage/testing.md
83
85
  - docs/usage/using.md
84
86
  - docs/usage/writing.md
85
- - lib/action/configuration.rb
86
- - lib/action/context_facade.rb
87
- - lib/action/contract.rb
88
- - lib/action/contract_validator.rb
89
- - lib/action/enqueueable.rb
90
- - lib/action/exceptions.rb
91
- - lib/action/hoist_errors.rb
92
- - lib/action/logging.rb
93
- - lib/action/organizer.rb
94
- - lib/action/swallow_exceptions.rb
95
- - lib/action/top_level_around_hook.rb
87
+ - lib/action/attachable.rb
88
+ - lib/action/attachable/base.rb
89
+ - lib/action/attachable/steps.rb
90
+ - lib/action/attachable/subactions.rb
91
+ - lib/action/core/configuration.rb
92
+ - lib/action/core/context_facade.rb
93
+ - lib/action/core/contract.rb
94
+ - lib/action/core/contract_validator.rb
95
+ - lib/action/core/enqueueable.rb
96
+ - lib/action/core/exceptions.rb
97
+ - lib/action/core/hoist_errors.rb
98
+ - lib/action/core/logging.rb
99
+ - lib/action/core/swallow_exceptions.rb
100
+ - lib/action/core/top_level_around_hook.rb
96
101
  - lib/axn.rb
102
+ - lib/axn/factory.rb
97
103
  - lib/axn/version.rb
98
104
  - package.json
99
105
  - yarn.lock
@@ -113,14 +119,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
113
119
  requirements:
114
120
  - - ">="
115
121
  - !ruby/object:Gem::Version
116
- version: 3.1.0
122
+ version: 3.2.0
117
123
  required_rubygems_version: !ruby/object:Gem::Requirement
118
124
  requirements:
119
- - - ">"
125
+ - - ">="
120
126
  - !ruby/object:Gem::Version
121
- version: 1.3.1
127
+ version: '0'
122
128
  requirements: []
123
- rubygems_version: 3.4.10
129
+ rubygems_version: 3.5.22
124
130
  signing_key:
125
131
  specification_version: 4
126
132
  summary: A terse convention for business logic
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # CAUTION - ALPHA - TODO -- this code is extremely rough -- we have not yet gotten around to using it in production, so
5
- # consider this a work in progress / an indicator of things to come.
6
- #
7
-
8
- module Action
9
- # NOTE: replaces, rather than layers on, the upstream Interactor::Organizer module (only three methods, and
10
- # we want ability to implement a more complex interface where we pass options into the organized interactors)
11
- module Organizer
12
- def self.included(base)
13
- base.class_eval do
14
- include ::Action
15
-
16
- extend ClassMethods
17
- include InstanceMethods
18
- end
19
- end
20
-
21
- # NOTE: pulled unchanged from https://github.com/collectiveidea/interactor/blob/master/lib/interactor/organizer.rb
22
- module ClassMethods
23
- def organize(*interactors)
24
- @organized = interactors.flatten
25
- end
26
-
27
- def organized
28
- @organized ||= []
29
- end
30
- end
31
-
32
- module InstanceMethods
33
- # NOTE: override to use the `hoist_errors` method (internally, replaces call! with call + overrides to use @context directly)
34
- def call
35
- self.class.organized.each do |interactor|
36
- hoist_errors { interactor.call(@context) }
37
- end
38
- end
39
- end
40
- end
41
- end
File without changes
File without changes
File without changes
File without changes