dexkit 0.5.0 → 0.6.0
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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +6 -2
- data/guides/llm/EVENT.md +64 -0
- data/guides/llm/OPERATION.md +1 -1
- data/lib/dex/event/handler.rb +20 -1
- data/lib/dex/event.rb +0 -2
- data/lib/dex/executable.rb +44 -0
- data/lib/dex/operation/transaction_wrapper.rb +2 -1
- data/lib/dex/operation.rb +1 -35
- data/lib/dex/pipeline.rb +58 -0
- data/lib/dex/version.rb +1 -1
- data/lib/dexkit.rb +3 -0
- metadata +8 -6
- data/lib/dex/operation/pipeline.rb +0 -60
- /data/lib/dex/{operation/settings.rb → settings.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8d7274727e727937b55704faa81e4da8a54bb3af7758b11e9144e59fb2dba14
|
|
4
|
+
data.tar.gz: cb899ee88b5e83ec57a913f0173036ac228f672c7850d88e95209f4804218f96
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7758fe2c0d5b9cdd2bcf62aa510c4146ef29898335ddffb118abb35409367d5f7cbd1aeba6365f865bb36d4011b1030f136cfb9b6eee27fb3aa3d06d11eeb21e
|
|
7
|
+
data.tar.gz: e437a4af9b4aa0c121245de966775cd3be0bad5ceb5918f139cfaf059545159631797b630a9b21f3add8855308c832f5846efb78f97f36141042f40d28b32341
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-03-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Handler callbacks** — `Dex::Event::Handler` now supports `before`, `after`, and `around` callbacks, same DSL as operations
|
|
8
|
+
- **Handler transactions** — `Dex::Event::Handler` supports `transaction` and `after_commit` DSL. Transactions are disabled by default on handlers (opt in with `transaction`)
|
|
9
|
+
- **Handler pipeline** — `Dex::Event::Handler` supports `use` for adding custom wrapper modules, same as operations
|
|
10
|
+
- **`Dex::Executable`** — shared execution skeleton (Settings, Pipeline, `use` DSL) extracted from Operation and used by both Operation and Handler
|
|
11
|
+
|
|
12
|
+
### Breaking
|
|
13
|
+
|
|
14
|
+
- **`transaction false` fully opts out** — operations with `transaction false` no longer route `after_commit` through the database adapter. Previously, `after_commit` on a non-transactional operation would still detect and defer to ambient database transactions (e.g., `ActiveRecord::Base.transaction { op.call }`); now it fires callbacks directly after the pipeline completes. To restore ambient transaction awareness, remove `transaction false` or use `transaction` (enabled)
|
|
15
|
+
|
|
3
16
|
## 0.5.0 - 2026-03-05
|
|
4
17
|
|
|
5
18
|
### Added
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dexkit
|
|
2
2
|
|
|
3
3
|
Rails patterns toolbelt. Equip to gain +4 DEX.
|
|
4
4
|
|
|
@@ -127,6 +127,10 @@ order_placed.trace do
|
|
|
127
127
|
end
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
**Callbacks** — `before`, `after`, `around` hooks on handlers, same DSL as operations.
|
|
131
|
+
|
|
132
|
+
**Transactions** — opt-in `transaction` and `after_commit` for handlers that write to the database.
|
|
133
|
+
|
|
130
134
|
**Suppression**, optional **persistence**, **context capture**, and **retries** with exponential backoff.
|
|
131
135
|
|
|
132
136
|
### Testing
|
|
@@ -259,7 +263,7 @@ Full documentation at **[dex.razorjack.net](https://dex.razorjack.net)**.
|
|
|
259
263
|
|
|
260
264
|
## AI Coding Assistant Setup
|
|
261
265
|
|
|
262
|
-
|
|
266
|
+
dexkit ships LLM-optimized guides. Copy them into your project so AI agents automatically know the API:
|
|
263
267
|
|
|
264
268
|
```bash
|
|
265
269
|
cp $(bundle show dexkit)/guides/llm/OPERATION.md app/operations/CLAUDE.md
|
data/guides/llm/EVENT.md
CHANGED
|
@@ -111,6 +111,70 @@ end
|
|
|
111
111
|
|
|
112
112
|
When retries exhausted, exception propagates normally.
|
|
113
113
|
|
|
114
|
+
### Callbacks
|
|
115
|
+
|
|
116
|
+
Same `before`/`after`/`around` DSL as operations:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
class ProcessPayment < Dex::Event::Handler
|
|
120
|
+
on PaymentReceived
|
|
121
|
+
|
|
122
|
+
before :log_start
|
|
123
|
+
after :log_end
|
|
124
|
+
|
|
125
|
+
around ->(cont) {
|
|
126
|
+
Instrumentation.measure("payment") { cont.call }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
def perform
|
|
130
|
+
PaymentGateway.charge(event.amount)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def log_start = Rails.logger.info("Processing payment...")
|
|
136
|
+
def log_end = Rails.logger.info("Payment processed")
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Callbacks are inherited. Child handlers run parent callbacks first.
|
|
141
|
+
|
|
142
|
+
### Transactions
|
|
143
|
+
|
|
144
|
+
Handlers can opt into database transactions and deferred `after_commit`:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
class FulfillOrder < Dex::Event::Handler
|
|
148
|
+
on OrderPlaced
|
|
149
|
+
transaction
|
|
150
|
+
|
|
151
|
+
def perform
|
|
152
|
+
order = Order.find(event.order_id)
|
|
153
|
+
order.update!(status: "fulfilled")
|
|
154
|
+
|
|
155
|
+
after_commit { Shipment::Ship.new(order_id: order.id).async.call }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Transactions are **disabled by default** on handlers (unlike operations). Opt in with `transaction`. The `after_commit` block defers until the transaction commits; on exception, deferred blocks are discarded.
|
|
161
|
+
|
|
162
|
+
### Custom Pipeline
|
|
163
|
+
|
|
164
|
+
Handlers support the same `use` DSL as operations for adding custom wrappers:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
class Monitored < Dex::Event::Handler
|
|
168
|
+
use MetricsWrapper, as: :metrics
|
|
169
|
+
|
|
170
|
+
def perform
|
|
171
|
+
# ...
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Default handler pipeline: `[:transaction, :callback]`.
|
|
177
|
+
|
|
114
178
|
---
|
|
115
179
|
|
|
116
180
|
## Tracing (Causality)
|
data/guides/llm/OPERATION.md
CHANGED
|
@@ -347,7 +347,7 @@ record params: false # response only
|
|
|
347
347
|
|
|
348
348
|
Recording happens inside the transaction — rolled back on `error!`/`assert!`. Missing columns silently skipped.
|
|
349
349
|
|
|
350
|
-
When both async and recording are enabled,
|
|
350
|
+
When both async and recording are enabled, dexkit automatically stores only the record ID in the job payload instead of full params. The record tracks `status` (pending → running → done/failed) and `error` (code or exception class name).
|
|
351
351
|
|
|
352
352
|
---
|
|
353
353
|
|
data/lib/dex/event/handler.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Dex
|
|
4
4
|
class Event
|
|
5
5
|
class Handler
|
|
6
|
+
include Dex::Executable
|
|
7
|
+
|
|
6
8
|
attr_reader :event
|
|
7
9
|
|
|
8
10
|
def self.on(*event_classes)
|
|
@@ -36,7 +38,7 @@ module Dex
|
|
|
36
38
|
def self._event_handle(event)
|
|
37
39
|
instance = new
|
|
38
40
|
instance.instance_variable_set(:@event, event)
|
|
39
|
-
instance.
|
|
41
|
+
instance.send(:call)
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
def self._event_handle_from_payload(event_class_name, payload, metadata_hash)
|
|
@@ -69,6 +71,23 @@ module Dex
|
|
|
69
71
|
end
|
|
70
72
|
end
|
|
71
73
|
|
|
74
|
+
use TransactionWrapper
|
|
75
|
+
use CallbackWrapper
|
|
76
|
+
|
|
77
|
+
transaction false
|
|
78
|
+
private :call
|
|
79
|
+
|
|
80
|
+
# Guard must be defined after `include Executable` (which defines #call).
|
|
81
|
+
def self.method_added(method_name)
|
|
82
|
+
super
|
|
83
|
+
|
|
84
|
+
if method_name == :call
|
|
85
|
+
raise ArgumentError, "#{name || "Handler"} must not define #call — define #perform instead"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private :perform if method_name == :perform
|
|
89
|
+
end
|
|
90
|
+
|
|
72
91
|
def perform
|
|
73
92
|
raise NotImplementedError, "#{self.class.name} must implement #perform"
|
|
74
93
|
end
|
data/lib/dex/event.rb
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
module Executable
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.include(Dex::Settings)
|
|
7
|
+
base.extend(ClassMethods)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def inherited(subclass)
|
|
12
|
+
subclass.instance_variable_set(:@_pipeline, pipeline.dup)
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def pipeline
|
|
17
|
+
@_pipeline ||= Pipeline.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def use(mod, as: nil, wrap: nil, before: nil, after: nil, at: nil)
|
|
21
|
+
step_name = as || _derive_step_name(mod)
|
|
22
|
+
wrap_method = wrap || :"_#{step_name}_wrap"
|
|
23
|
+
pipeline.add(step_name, method: wrap_method, before: before, after: after, at: at)
|
|
24
|
+
include mod
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def _derive_step_name(mod)
|
|
30
|
+
base = mod.name&.split("::")&.last
|
|
31
|
+
raise ArgumentError, "anonymous modules require explicit as: parameter" unless base
|
|
32
|
+
|
|
33
|
+
base.sub(/Wrapper\z/, "")
|
|
34
|
+
.gsub(/([a-z])([A-Z])/, '\1_\2')
|
|
35
|
+
.downcase
|
|
36
|
+
.to_sym
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def call
|
|
41
|
+
self.class.pipeline.execute(self) { perform }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -121,7 +121,8 @@ module Dex
|
|
|
121
121
|
return if callbacks.empty?
|
|
122
122
|
|
|
123
123
|
flush = -> { callbacks.each(&:call) }
|
|
124
|
-
|
|
124
|
+
enabled = self.class.settings_for(:transaction).fetch(:enabled, true)
|
|
125
|
+
adapter = enabled && _transaction_adapter
|
|
125
126
|
if adapter
|
|
126
127
|
adapter.after_commit(&flush)
|
|
127
128
|
else
|
data/lib/dex/operation.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Wrapper modules (loaded before class body so `include`/`use` can find them)
|
|
4
|
-
require_relative "operation/settings"
|
|
5
4
|
require_relative "operation/result_wrapper"
|
|
6
5
|
require_relative "operation/record_wrapper"
|
|
7
6
|
require_relative "operation/transaction_wrapper"
|
|
@@ -11,9 +10,6 @@ require_relative "operation/safe_wrapper"
|
|
|
11
10
|
require_relative "operation/rescue_wrapper"
|
|
12
11
|
require_relative "operation/callback_wrapper"
|
|
13
12
|
|
|
14
|
-
# Pipeline (referenced inside class body)
|
|
15
|
-
require_relative "operation/pipeline"
|
|
16
|
-
|
|
17
13
|
module Dex
|
|
18
14
|
class Operation
|
|
19
15
|
Halt = Struct.new(:type, :value, :error_code, :error_message, :error_details, keyword_init: true) do
|
|
@@ -46,6 +42,7 @@ module Dex
|
|
|
46
42
|
|
|
47
43
|
RESERVED_PROP_NAMES = %i[call perform async safe initialize].to_set.freeze
|
|
48
44
|
|
|
45
|
+
include Executable
|
|
49
46
|
include PropsSetup
|
|
50
47
|
include TypeCoercion
|
|
51
48
|
|
|
@@ -60,22 +57,6 @@ module Dex
|
|
|
60
57
|
)
|
|
61
58
|
end
|
|
62
59
|
|
|
63
|
-
def inherited(subclass)
|
|
64
|
-
subclass.instance_variable_set(:@_pipeline, pipeline.dup)
|
|
65
|
-
super
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def pipeline
|
|
69
|
-
@_pipeline ||= Pipeline.new
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def use(mod, as: nil, wrap: nil, before: nil, after: nil, at: nil)
|
|
73
|
-
step_name = as || _derive_step_name(mod)
|
|
74
|
-
wrap_method = wrap || :"_#{step_name}_wrap"
|
|
75
|
-
pipeline.add(step_name, method: wrap_method, before: before, after: after, at: at)
|
|
76
|
-
include mod
|
|
77
|
-
end
|
|
78
|
-
|
|
79
60
|
private
|
|
80
61
|
|
|
81
62
|
def _contract_params
|
|
@@ -85,25 +66,11 @@ module Dex
|
|
|
85
66
|
hash[prop.name] = prop.type
|
|
86
67
|
end
|
|
87
68
|
end
|
|
88
|
-
|
|
89
|
-
def _derive_step_name(mod)
|
|
90
|
-
base = mod.name&.split("::")&.last
|
|
91
|
-
raise ArgumentError, "anonymous modules require explicit as: parameter" unless base
|
|
92
|
-
|
|
93
|
-
base.sub(/Wrapper\z/, "")
|
|
94
|
-
.gsub(/([a-z])([A-Z])/, '\1_\2')
|
|
95
|
-
.downcase
|
|
96
|
-
.to_sym
|
|
97
|
-
end
|
|
98
69
|
end
|
|
99
70
|
|
|
100
71
|
def perform(*, **)
|
|
101
72
|
end
|
|
102
73
|
|
|
103
|
-
def call
|
|
104
|
-
self.class.pipeline.execute(self) { perform }
|
|
105
|
-
end
|
|
106
|
-
|
|
107
74
|
def self.method_added(method_name)
|
|
108
75
|
super
|
|
109
76
|
return unless method_name == :perform
|
|
@@ -117,7 +84,6 @@ module Dex
|
|
|
117
84
|
new(**kwargs).call
|
|
118
85
|
end
|
|
119
86
|
|
|
120
|
-
include Settings
|
|
121
87
|
include AsyncWrapper
|
|
122
88
|
include SafeWrapper
|
|
123
89
|
|
data/lib/dex/pipeline.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Pipeline
|
|
5
|
+
Step = Data.define(:name, :method)
|
|
6
|
+
|
|
7
|
+
def initialize(steps = [])
|
|
8
|
+
@steps = steps.dup
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def dup
|
|
12
|
+
self.class.new(@steps)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def steps
|
|
16
|
+
@steps.dup.freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add(name, method: :"_#{name}_wrap", before: nil, after: nil, at: nil)
|
|
20
|
+
validate_positioning!(before, after, at)
|
|
21
|
+
step = Step.new(name: name, method: method)
|
|
22
|
+
|
|
23
|
+
if at == :outer then @steps.unshift(step)
|
|
24
|
+
elsif at == :inner then @steps.push(step)
|
|
25
|
+
elsif before then @steps.insert(find_index!(before), step)
|
|
26
|
+
elsif after then @steps.insert(find_index!(after) + 1, step)
|
|
27
|
+
else @steps.push(step)
|
|
28
|
+
end
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def remove(name)
|
|
33
|
+
@steps.reject! { |s| s.name == name }
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def execute(target)
|
|
38
|
+
chain = @steps.reverse_each.reduce(-> { yield }) do |next_step, step|
|
|
39
|
+
-> { target.send(step.method, &next_step) }
|
|
40
|
+
end
|
|
41
|
+
chain.call
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def validate_positioning!(before, after, at)
|
|
47
|
+
count = [before, after, at].count { |v| !v.nil? }
|
|
48
|
+
raise ArgumentError, "specify only one of before:, after:, at:" if count > 1
|
|
49
|
+
raise ArgumentError, "at: must be :outer or :inner" if at && !%i[outer inner].include?(at)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def find_index!(name)
|
|
53
|
+
idx = @steps.index { |s| s.name == name }
|
|
54
|
+
raise ArgumentError, "pipeline step :#{name} not found" unless idx
|
|
55
|
+
idx
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/dex/version.rb
CHANGED
data/lib/dexkit.rb
CHANGED
|
@@ -15,6 +15,9 @@ require_relative "dex/ref_type"
|
|
|
15
15
|
require_relative "dex/type_coercion"
|
|
16
16
|
require_relative "dex/props_setup"
|
|
17
17
|
require_relative "dex/error"
|
|
18
|
+
require_relative "dex/settings"
|
|
19
|
+
require_relative "dex/pipeline"
|
|
20
|
+
require_relative "dex/executable"
|
|
18
21
|
require_relative "dex/operation"
|
|
19
22
|
require_relative "dex/event"
|
|
20
23
|
require_relative "dex/form"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dexkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jacek Galanciak
|
|
@@ -177,6 +177,7 @@ files:
|
|
|
177
177
|
- lib/dex/event/trace.rb
|
|
178
178
|
- lib/dex/event_test_helpers.rb
|
|
179
179
|
- lib/dex/event_test_helpers/assertions.rb
|
|
180
|
+
- lib/dex/executable.rb
|
|
180
181
|
- lib/dex/form.rb
|
|
181
182
|
- lib/dex/form/nesting.rb
|
|
182
183
|
- lib/dex/form/uniqueness_validator.rb
|
|
@@ -188,21 +189,21 @@ files:
|
|
|
188
189
|
- lib/dex/operation/jobs.rb
|
|
189
190
|
- lib/dex/operation/lock_wrapper.rb
|
|
190
191
|
- lib/dex/operation/outcome.rb
|
|
191
|
-
- lib/dex/operation/pipeline.rb
|
|
192
192
|
- lib/dex/operation/record_backend.rb
|
|
193
193
|
- lib/dex/operation/record_wrapper.rb
|
|
194
194
|
- lib/dex/operation/rescue_wrapper.rb
|
|
195
195
|
- lib/dex/operation/result_wrapper.rb
|
|
196
196
|
- lib/dex/operation/safe_wrapper.rb
|
|
197
|
-
- lib/dex/operation/settings.rb
|
|
198
197
|
- lib/dex/operation/transaction_adapter.rb
|
|
199
198
|
- lib/dex/operation/transaction_wrapper.rb
|
|
199
|
+
- lib/dex/pipeline.rb
|
|
200
200
|
- lib/dex/props_setup.rb
|
|
201
201
|
- lib/dex/query.rb
|
|
202
202
|
- lib/dex/query/backend.rb
|
|
203
203
|
- lib/dex/query/filtering.rb
|
|
204
204
|
- lib/dex/query/sorting.rb
|
|
205
205
|
- lib/dex/ref_type.rb
|
|
206
|
+
- lib/dex/settings.rb
|
|
206
207
|
- lib/dex/test_helpers.rb
|
|
207
208
|
- lib/dex/test_helpers/assertions.rb
|
|
208
209
|
- lib/dex/test_helpers/execution.rb
|
|
@@ -211,14 +212,15 @@ files:
|
|
|
211
212
|
- lib/dex/type_coercion.rb
|
|
212
213
|
- lib/dex/version.rb
|
|
213
214
|
- lib/dexkit.rb
|
|
214
|
-
homepage: https://
|
|
215
|
+
homepage: https://dex.razorjack.net/
|
|
215
216
|
licenses:
|
|
216
217
|
- MIT
|
|
217
218
|
metadata:
|
|
218
219
|
allowed_push_host: https://rubygems.org
|
|
219
|
-
homepage_uri: https://
|
|
220
|
+
homepage_uri: https://dex.razorjack.net/
|
|
220
221
|
source_code_uri: https://github.com/razorjack/dexkit
|
|
221
222
|
changelog_uri: https://github.com/razorjack/dexkit/blob/master/CHANGELOG.md
|
|
223
|
+
documentation_uri: https://dex.razorjack.net/
|
|
222
224
|
rdoc_options: []
|
|
223
225
|
require_paths:
|
|
224
226
|
- lib
|
|
@@ -235,5 +237,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
235
237
|
requirements: []
|
|
236
238
|
rubygems_version: 4.0.3
|
|
237
239
|
specification_version: 4
|
|
238
|
-
summary: '
|
|
240
|
+
summary: 'dexkit: Rails Patterns Toolbelt. Equip to gain +4 DEX'
|
|
239
241
|
test_files: []
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Dex
|
|
4
|
-
class Operation
|
|
5
|
-
class Pipeline
|
|
6
|
-
Step = Data.define(:name, :method)
|
|
7
|
-
|
|
8
|
-
def initialize(steps = [])
|
|
9
|
-
@steps = steps.dup
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def dup
|
|
13
|
-
self.class.new(@steps)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def steps
|
|
17
|
-
@steps.dup.freeze
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def add(name, method: :"_#{name}_wrap", before: nil, after: nil, at: nil)
|
|
21
|
-
validate_positioning!(before, after, at)
|
|
22
|
-
step = Step.new(name: name, method: method)
|
|
23
|
-
|
|
24
|
-
if at == :outer then @steps.unshift(step)
|
|
25
|
-
elsif at == :inner then @steps.push(step)
|
|
26
|
-
elsif before then @steps.insert(find_index!(before), step)
|
|
27
|
-
elsif after then @steps.insert(find_index!(after) + 1, step)
|
|
28
|
-
else @steps.push(step)
|
|
29
|
-
end
|
|
30
|
-
self
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def remove(name)
|
|
34
|
-
@steps.reject! { |s| s.name == name }
|
|
35
|
-
self
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def execute(operation)
|
|
39
|
-
chain = @steps.reverse_each.reduce(-> { yield }) do |next_step, step|
|
|
40
|
-
-> { operation.send(step.method, &next_step) }
|
|
41
|
-
end
|
|
42
|
-
chain.call
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def validate_positioning!(before, after, at)
|
|
48
|
-
count = [before, after, at].count { |v| !v.nil? }
|
|
49
|
-
raise ArgumentError, "specify only one of before:, after:, at:" if count > 1
|
|
50
|
-
raise ArgumentError, "at: must be :outer or :inner" if at && !%i[outer inner].include?(at)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def find_index!(name)
|
|
54
|
-
idx = @steps.index { |s| s.name == name }
|
|
55
|
-
raise ArgumentError, "pipeline step :#{name} not found" unless idx
|
|
56
|
-
idx
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
File without changes
|