axn 0.1.0.pre.alpha.2.1 → 0.1.0.pre.alpha.2.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20198518e83064e97c25335dbcb2be0a88affe103dcd1aaf8ca3965a964cbeb4
4
- data.tar.gz: 70707f0966255c4c0015953fea14a3b42d1bcc2c1e54b896a8ea20c4e5effb3b
3
+ metadata.gz: ac3f240b093cdf14fef16b5eb6993dbb8f6d49ba58c837245128a0efb3558f11
4
+ data.tar.gz: 40b631f49349b51811ebb0bbc60cbc7962a3f7d633bab1134b7b234b4f681123
5
5
  SHA512:
6
- metadata.gz: 781768348b4b08267f34956a94b4a4ab0ef470123d9d084cfb6fd24fb1ab02b6c03f979206a57d828f675ec21b7754672bfbaed10acd8bf222f6efc374c90b39
7
- data.tar.gz: d81788b26c3b313fa3d21fb2d7d763109839659eff4d0bc8af0251a7ee93ef77ee121df1f023dad4f71ed63eb94b355d7469cfcac5e21dab4fcbec126f40ea88
6
+ metadata.gz: c8ac8be69e70d72d04b0c5be89e4ec198223c4a1771a15d254b82b0a204543c627a80321b3de95d69fa97a244fc56c9c39ca59262600c6b991c363603e0a7beb
7
+ data.tar.gz: 4eba5152626d0417ff3be725da9512034276d4b034898f8ad331890499196c1851a1d3dcd1cde880955c42f425090ab6c5fe30cac1496835013c7bd6cda52cdb
data/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  ## UNRELEASED
4
4
  * N/A
5
5
 
6
+ ## 0.1.0-alpha.2.3
7
+ * `expects` / `exposes`: Add `type: :uuid` special case validation
8
+ * [BUGFIX] Allow `hoist_errors` to pass the result through on success (allow access to subactions' exposures)
9
+ * [`Axn::Factory`] Support error_from + rescues
10
+ * `Action::Result.error` spec helper -- creation should NOT trigger global exception handler
11
+ * [CHANGE] `expects` / `exposes`: The `default` key, if a callable, should be evaluated in the _instance_'s context
12
+
13
+ ## 0.1.0-alpha.2.2
14
+ * Expands `Action::Result.ok` and `Action::Result.error` to better support mocking in specs
15
+
6
16
  ## 0.1.0-alpha.2.1
7
17
  * Expects/Exposes: Add `allow_nil` option
8
18
  * Expects/Exposes: Replace `boolean: true` with `type: :boolean`
@@ -4,7 +4,36 @@
4
4
  * TODO: document testing patterns
5
5
  :::
6
6
 
7
- Configuring rspec to treat files in spec/actions as service specs:
7
+ ## Mocking Axn calls
8
+
9
+ Say you're writing unit specs for PrimaryAction that calls Subaction, and you want to mock out the Subaction call.
10
+
11
+ To generate a successful Action::Result:
12
+
13
+ * Base case: `Action::Result.ok`
14
+ * [Optional] Custom message: `Action::Result.ok("It went awesome")`
15
+ * [Optional] Custom exposures: `Action::Result.ok("It went awesome", some_var: 123)`
16
+
17
+ To generate a failed Action::Result:
18
+
19
+ * Base case: `Action::Result.error`
20
+ * [Optional] Custom message: `Action::Result.error("It went poorly")`
21
+ * [Optional] Custom exposures: `Action::Result.error("It went poorly", some_var: 123)`
22
+ * [Optional] Custom exception: `Action::Result.error(some_var: 123) { raise FooBarException.new("bad thing") }`
23
+
24
+ Either way, using those to mock an actual call would look something like this in your rspec:
25
+
26
+ ```ruby
27
+ let(:subaction_response) { Action::Result.ok("custom message", foo: 1) }
28
+
29
+ before do
30
+ expect(Subaction).to receive(:call).and_return(subaction_response)
31
+ end
32
+ ```
33
+
34
+ ## RSpec configuration
35
+
36
+ Configuring rspec to treat files in spec/actions as service specs (very optional):
8
37
 
9
38
  ```ruby
10
39
  RSpec.configure do |config|
@@ -25,6 +25,7 @@ While we _support_ complex interface validations, in practice you usually just w
25
25
  In addition to the [standard ActiveModel validations](https://guides.rubyonrails.org/active_record_validations.html), we also support two additional custom validators:
26
26
  * `type: Foo` - fails unless the provided value `.is_a?(Foo)`
27
27
  * Edge case: use `type: :boolean` to handle a boolean field (since ruby doesn't have a Boolean class to pass in directly)
28
+ * Edge case: use `type: :uuid` to handle a confirming given string is a UUID (with or without `-` chars)
28
29
  * `validate: [callable]` - Support custom validations (fails if any string is returned OR if it raises an exception)
29
30
  * Example:
30
31
  ```ruby
@@ -94,11 +94,21 @@ module Action
94
94
  class Result < ContextFacade
95
95
  # For ease of mocking return results in tests
96
96
  class << self
97
- def ok = Class.new { include(Action) }.call
97
+ def ok(msg = nil, **exposures)
98
+ Axn::Factory.build(exposes: exposures.keys, messages: { success: msg }) do
99
+ exposures.each do |key, value|
100
+ expose(key, value)
101
+ end
102
+ end.call
103
+ end
98
104
 
99
- def error(msg = "Something went wrong")
100
- Class.new { include(Action) }.tap do |klass|
101
- klass.define_method(:call) { fail!(msg) }
105
+ def error(msg = nil, **exposures, &block)
106
+ Axn::Factory.build(exposes: exposures.keys, rescues: [-> { true }, msg]) do
107
+ exposures.each do |key, value|
108
+ expose(key, value)
109
+ end
110
+ block.call if block_given?
111
+ fail!
102
112
  end.call
103
113
  end
104
114
  end
@@ -163,11 +163,12 @@ module Action
163
163
  hash[config.field] = config.default
164
164
  end.compact
165
165
 
166
- defaults_mapping.each do |field, default_value|
167
- unless @context.public_send(field)
168
- @context.public_send("#{field}=",
169
- default_value.respond_to?(:call) ? default_value.call : default_value)
170
- end
166
+ defaults_mapping.each do |field, default_value_getter|
167
+ next if @context.public_send(field).present?
168
+
169
+ default_value = default_value_getter.respond_to?(:call) ? instance_exec(&default_value_getter) : default_value_getter
170
+
171
+ @context.public_send("#{field}=", default_value)
171
172
  end
172
173
  end
173
174
 
@@ -54,6 +54,8 @@ module Action
54
54
  record.errors.add attribute, (options[:message] || msg) unless types.any? do |type|
55
55
  if type == :boolean
56
56
  [true, false].include?(value)
57
+ elsif type == :uuid
58
+ value.is_a?(String) && value.match?(/\A[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}\z/i)
57
59
  else
58
60
  value.is_a?(type)
59
61
  end
@@ -36,7 +36,9 @@ module Action
36
36
  "#hoist_errors is expected to wrap an Action call, but it returned a #{result.class.name} instead"
37
37
  end
38
38
 
39
- _handle_hoisted_errors(result, prefix:) unless result.ok?
39
+ return result if result.ok?
40
+
41
+ _handle_hoisted_errors(result, prefix:)
40
42
  end
41
43
 
42
44
  # Separate method to allow overriding in subclasses
data/lib/axn/factory.rb CHANGED
@@ -14,6 +14,10 @@ module Axn
14
14
  exposes: [],
15
15
  expects: [],
16
16
  messages: {},
17
+ error_from: {},
18
+ rescues: {},
19
+
20
+ # Hooks
17
21
  before: nil,
18
22
  after: nil,
19
23
  around: nil,
@@ -71,7 +75,10 @@ module Axn
71
75
  axn.exposes(field, **opts)
72
76
  end
73
77
 
74
- axn.messages(**messages) if messages.present?
78
+ axn.messages(**messages) if messages.present? && messages.values.any?(&:present?)
79
+
80
+ axn.error_from(**_array_to_hash(error_from)) if error_from.present?
81
+ axn.rescues(**_array_to_hash(rescues)) if rescues.present?
75
82
 
76
83
  # Hooks
77
84
  axn.before(before) if before.present?
@@ -98,6 +105,12 @@ module Axn
98
105
 
99
106
  def _hash_with_default_array = Hash.new { |h, k| h[k] = [] }
100
107
 
108
+ def _array_to_hash(given)
109
+ return given if given.is_a?(Hash)
110
+
111
+ [given].to_h
112
+ end
113
+
101
114
  def _hydrate_hash(given)
102
115
  return given if given.is_a?(Hash)
103
116
 
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.2.1"
4
+ VERSION = "0.1.0-alpha.2.3"
5
5
  end
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.2.1
4
+ version: 0.1.0.pre.alpha.2.3
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-05-12 00:00:00.000000000 Z
11
+ date: 2025-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel