dexkit 0.8.0 → 0.9.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 +14 -0
- data/README.md +6 -2
- data/gemfiles/mongoid_no_ar.gemfile +10 -0
- data/gemfiles/mongoid_no_ar.gemfile.lock +232 -0
- data/guides/llm/EVENT.md +17 -4
- data/guides/llm/FORM.md +2 -2
- data/guides/llm/OPERATION.md +22 -17
- data/guides/llm/QUERY.md +2 -2
- data/lib/dex/event/bus.rb +7 -0
- data/lib/dex/event/test_helpers.rb +88 -0
- data/lib/dex/event_test_helpers.rb +1 -86
- data/lib/dex/form/uniqueness_validator.rb +17 -1
- data/lib/dex/operation/async_proxy.rb +1 -0
- data/lib/dex/operation/explain.rb +11 -7
- data/lib/dex/operation/lock_wrapper.rb +15 -2
- data/lib/dex/operation/once_wrapper.rb +23 -15
- data/lib/dex/operation/record_backend.rb +13 -0
- data/lib/dex/operation/record_wrapper.rb +29 -4
- data/lib/dex/operation/test_helpers/assertions.rb +335 -0
- data/lib/dex/operation/test_helpers/execution.rb +30 -0
- data/lib/dex/operation/test_helpers/stubbing.rb +61 -0
- data/lib/dex/operation/test_helpers.rb +150 -0
- data/lib/dex/operation/transaction_adapter.rb +29 -68
- data/lib/dex/operation/transaction_wrapper.rb +10 -16
- data/lib/dex/query/backend.rb +13 -0
- data/lib/dex/query.rb +9 -5
- data/lib/dex/ref_type.rb +4 -0
- data/lib/dex/test_helpers.rb +4 -139
- data/lib/dex/type_coercion.rb +4 -1
- data/lib/dex/version.rb +1 -1
- data/lib/dexkit.rb +6 -5
- metadata +9 -5
- data/lib/dex/test_helpers/assertions.rb +0 -333
- data/lib/dex/test_helpers/execution.rb +0 -28
- data/lib/dex/test_helpers/stubbing.rb +0 -59
- /data/lib/dex/{event_test_helpers → event/test_helpers}/assertions.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: 41c8e4455fb4a4cca73b1d12da366b53bdbbada2cbae773917a12d344a150dd4
|
|
4
|
+
data.tar.gz: 86c4e76004df8b968c094b7b252a988eafe2f7aee035505ddb7bbeae1f25e04a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89a9f6075f3955300dbe3944a9adc9b80ab56c3b4dc60d9458ea370f68c5c83ab5b3f2daad26c6f852d6f423a498ccf068914853e9866c05442765539ac6ac91
|
|
7
|
+
data.tar.gz: cd541ee35700cf9aa877b3630d66f0b9580263d0fddc7f381200afe32c638f832d941112387f646fa59500c26bb552461ad5bf0f0ec79dcf7bb97d76ebcbfb86
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.9.0] - 2026-03-09
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- **Mongoid transaction support removed** — `transaction :mongoid` and `config.transaction_adapter = :mongoid` are no longer valid. Dex no longer ships a Mongoid transaction adapter. Before: Mongoid transactions could be enabled via configuration or per-operation DSL. After: both forms raise `ArgumentError` immediately at declaration/configuration time. Mongoid-only apps continue to work — transactions are automatically disabled (no adapter detected), and `after_commit` fires immediately after success. If you need Mongoid multi-document transactions, call `Mongoid.transaction` directly inside `perform`
|
|
8
|
+
- **Recording backends now validate required attributes before use** — Dex no longer silently drops missing `params`, `result`, `status`, or `once` attributes from `record_class`. Before: partial ActiveRecord/Mongoid recording models could appear to work while losing status transitions, replay data, or async params. After: Dex raises `ArgumentError` naming the missing attributes required by core recording, async record jobs, or `once`. Apps using minimal recording models must add the required columns/fields or explicitly disable the features that need them
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Mongoid-only Rails compatibility** — Dex boots and runs cleanly in Mongoid-only Rails apps without `activerecord` loaded, with prescriptive `LoadError`s for unsupported paths such as `advisory_lock` and async event dispatch without `ActiveJob`
|
|
13
|
+
- **ActiveRecord transaction auto-detection is stricter** — Dex now enables the ActiveRecord transaction adapter only when an ActiveRecord connection pool actually exists. Before: merely loading `activerecord` could make Mongoid-backed operations try to open an ActiveRecord transaction and fail with `ActiveRecord::ConnectionNotDefined`. After: unconfigured ActiveRecord no longer activates transactions implicitly
|
|
14
|
+
- **Mongoid async/recording serialization** — `_Ref(Model)` serializes IDs via `id.as_json`, so `BSON::ObjectId` values round-trip through async operations, async events, and recording without `ActiveJob::SerializationError`. Recording and `once` sanitize untyped Mongoid document results to JSON-safe payloads
|
|
15
|
+
- **Mongoid query and form parity** — query adapter detection and scope merging normalize Mongoid association scopes to `Mongoid::Criteria`, uniqueness validation excludes persisted Mongoid records correctly and uses a case-insensitive regex path for `case_sensitive: false`, and `_Ref(lock: true)` fails fast for model classes that do not support `.lock`
|
|
16
|
+
|
|
3
17
|
## [0.8.0] - 2026-03-09
|
|
4
18
|
|
|
5
19
|
### Added
|
data/README.md
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Rails patterns toolbelt. Equip to gain +4 DEX.
|
|
4
4
|
|
|
5
|
+
> **Active development.** dexkit is pre-1.0 and evolving rapidly. The public API may change between minor versions as the library matures.
|
|
6
|
+
|
|
5
7
|
**[Documentation](https://dex.razorjack.net)**
|
|
6
8
|
|
|
7
9
|
## Operations
|
|
8
10
|
|
|
9
11
|
Service objects with typed properties, transactions, error handling, and more.
|
|
10
12
|
|
|
13
|
+
Mongoid-only Rails apps work too – queries, recording, events, and forms all adapt automatically. Transactions are ActiveRecord-only (Mongoid users who need transactions can call `Mongoid.transaction` inside `perform`); `advisory_lock` is also ActiveRecord-only. Operation/event store models can be Mongoid documents; recording models must define the fields required by the enabled recording features.
|
|
14
|
+
|
|
11
15
|
```ruby
|
|
12
16
|
class Order::Place < Dex::Operation
|
|
13
17
|
prop :customer, _Ref(Customer)
|
|
@@ -204,7 +208,7 @@ Order::Placed.publish(order_id: 1, total: 99.99)
|
|
|
204
208
|
|
|
205
209
|
**Zero-config pub/sub** — define events and handlers, publish. No bus setup needed.
|
|
206
210
|
|
|
207
|
-
**Async by default** — handlers dispatched via ActiveJob. `sync: true` for inline.
|
|
211
|
+
**Async by default** — handlers dispatched via ActiveJob. `sync: true` for inline. If ActiveJob is not loaded, async publish raises `LoadError`.
|
|
208
212
|
|
|
209
213
|
**Causality tracing** — link events in chains with shared `trace_id`:
|
|
210
214
|
|
|
@@ -297,7 +301,7 @@ end
|
|
|
297
301
|
|
|
298
302
|
## Queries
|
|
299
303
|
|
|
300
|
-
Declarative query objects for filtering and sorting ActiveRecord
|
|
304
|
+
Declarative query objects for filtering and sorting ActiveRecord and Mongoid scopes.
|
|
301
305
|
|
|
302
306
|
```ruby
|
|
303
307
|
class Order::Query < Dex::Query
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: ..
|
|
3
|
+
specs:
|
|
4
|
+
dexkit (0.8.0)
|
|
5
|
+
activemodel (>= 6.1)
|
|
6
|
+
literal (~> 1.9)
|
|
7
|
+
zeitwerk (~> 2.6)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
actionpack (8.1.2)
|
|
13
|
+
actionview (= 8.1.2)
|
|
14
|
+
activesupport (= 8.1.2)
|
|
15
|
+
nokogiri (>= 1.8.5)
|
|
16
|
+
rack (>= 2.2.4)
|
|
17
|
+
rack-session (>= 1.0.1)
|
|
18
|
+
rack-test (>= 0.6.3)
|
|
19
|
+
rails-dom-testing (~> 2.2)
|
|
20
|
+
rails-html-sanitizer (~> 1.6)
|
|
21
|
+
useragent (~> 0.16)
|
|
22
|
+
actionview (8.1.2)
|
|
23
|
+
activesupport (= 8.1.2)
|
|
24
|
+
builder (~> 3.1)
|
|
25
|
+
erubi (~> 1.11)
|
|
26
|
+
rails-dom-testing (~> 2.2)
|
|
27
|
+
rails-html-sanitizer (~> 1.6)
|
|
28
|
+
activejob (8.1.2)
|
|
29
|
+
activesupport (= 8.1.2)
|
|
30
|
+
globalid (>= 0.3.6)
|
|
31
|
+
activemodel (8.1.2)
|
|
32
|
+
activesupport (= 8.1.2)
|
|
33
|
+
activesupport (8.1.2)
|
|
34
|
+
base64
|
|
35
|
+
bigdecimal
|
|
36
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
37
|
+
connection_pool (>= 2.2.5)
|
|
38
|
+
drb
|
|
39
|
+
i18n (>= 1.6, < 2)
|
|
40
|
+
json
|
|
41
|
+
logger (>= 1.4.2)
|
|
42
|
+
minitest (>= 5.1)
|
|
43
|
+
securerandom (>= 0.3)
|
|
44
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
45
|
+
uri (>= 0.13.1)
|
|
46
|
+
base64 (0.3.0)
|
|
47
|
+
bigdecimal (4.0.1)
|
|
48
|
+
bson (5.2.0)
|
|
49
|
+
builder (3.3.0)
|
|
50
|
+
concurrent-ruby (1.3.6)
|
|
51
|
+
connection_pool (3.0.2)
|
|
52
|
+
crass (1.0.6)
|
|
53
|
+
date (3.5.1)
|
|
54
|
+
drb (2.2.3)
|
|
55
|
+
erb (6.0.2)
|
|
56
|
+
erubi (1.13.1)
|
|
57
|
+
globalid (1.3.0)
|
|
58
|
+
activesupport (>= 6.1)
|
|
59
|
+
i18n (1.14.8)
|
|
60
|
+
concurrent-ruby (~> 1.0)
|
|
61
|
+
io-console (0.8.2)
|
|
62
|
+
irb (1.17.0)
|
|
63
|
+
pp (>= 0.6.0)
|
|
64
|
+
prism (>= 1.3.0)
|
|
65
|
+
rdoc (>= 4.0.0)
|
|
66
|
+
reline (>= 0.4.2)
|
|
67
|
+
json (2.19.1)
|
|
68
|
+
literal (1.9.0)
|
|
69
|
+
zeitwerk
|
|
70
|
+
logger (1.7.0)
|
|
71
|
+
loofah (2.25.0)
|
|
72
|
+
crass (~> 1.0.2)
|
|
73
|
+
nokogiri (>= 1.12.0)
|
|
74
|
+
minitest (6.0.2)
|
|
75
|
+
drb (~> 2.0)
|
|
76
|
+
prism (~> 1.5)
|
|
77
|
+
mongo (2.23.0)
|
|
78
|
+
base64
|
|
79
|
+
bson (>= 4.14.1, < 6.0.0)
|
|
80
|
+
mongoid (9.0.10)
|
|
81
|
+
activemodel (>= 5.1, < 8.2, != 7.0.0)
|
|
82
|
+
concurrent-ruby (>= 1.0.5, < 2.0)
|
|
83
|
+
mongo (>= 2.18.0, < 3.0.0)
|
|
84
|
+
nokogiri (1.19.1-aarch64-linux-gnu)
|
|
85
|
+
racc (~> 1.4)
|
|
86
|
+
nokogiri (1.19.1-aarch64-linux-musl)
|
|
87
|
+
racc (~> 1.4)
|
|
88
|
+
nokogiri (1.19.1-arm-linux-gnu)
|
|
89
|
+
racc (~> 1.4)
|
|
90
|
+
nokogiri (1.19.1-arm-linux-musl)
|
|
91
|
+
racc (~> 1.4)
|
|
92
|
+
nokogiri (1.19.1-arm64-darwin)
|
|
93
|
+
racc (~> 1.4)
|
|
94
|
+
nokogiri (1.19.1-x86_64-darwin)
|
|
95
|
+
racc (~> 1.4)
|
|
96
|
+
nokogiri (1.19.1-x86_64-linux-gnu)
|
|
97
|
+
racc (~> 1.4)
|
|
98
|
+
nokogiri (1.19.1-x86_64-linux-musl)
|
|
99
|
+
racc (~> 1.4)
|
|
100
|
+
ostruct (0.6.3)
|
|
101
|
+
pp (0.6.3)
|
|
102
|
+
prettyprint
|
|
103
|
+
prettyprint (0.2.0)
|
|
104
|
+
prism (1.9.0)
|
|
105
|
+
psych (5.3.1)
|
|
106
|
+
date
|
|
107
|
+
stringio
|
|
108
|
+
racc (1.8.1)
|
|
109
|
+
rack (3.2.5)
|
|
110
|
+
rack-session (2.1.1)
|
|
111
|
+
base64 (>= 0.1.0)
|
|
112
|
+
rack (>= 3.0.0)
|
|
113
|
+
rack-test (2.2.0)
|
|
114
|
+
rack (>= 1.3)
|
|
115
|
+
rackup (2.3.1)
|
|
116
|
+
rack (>= 3)
|
|
117
|
+
rails-dom-testing (2.3.0)
|
|
118
|
+
activesupport (>= 5.0.0)
|
|
119
|
+
minitest
|
|
120
|
+
nokogiri (>= 1.6)
|
|
121
|
+
rails-html-sanitizer (1.7.0)
|
|
122
|
+
loofah (~> 2.25)
|
|
123
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
124
|
+
railties (8.1.2)
|
|
125
|
+
actionpack (= 8.1.2)
|
|
126
|
+
activesupport (= 8.1.2)
|
|
127
|
+
irb (~> 1.13)
|
|
128
|
+
rackup (>= 1.0.0)
|
|
129
|
+
rake (>= 12.2)
|
|
130
|
+
thor (~> 1.0, >= 1.2.2)
|
|
131
|
+
tsort (>= 0.2)
|
|
132
|
+
zeitwerk (~> 2.6)
|
|
133
|
+
rake (13.3.1)
|
|
134
|
+
rdoc (7.2.0)
|
|
135
|
+
erb
|
|
136
|
+
psych (>= 4.0.0)
|
|
137
|
+
tsort
|
|
138
|
+
reline (0.6.3)
|
|
139
|
+
io-console (~> 0.5)
|
|
140
|
+
securerandom (0.4.1)
|
|
141
|
+
stringio (3.2.0)
|
|
142
|
+
thor (1.5.0)
|
|
143
|
+
tsort (0.2.0)
|
|
144
|
+
tzinfo (2.0.6)
|
|
145
|
+
concurrent-ruby (~> 1.0)
|
|
146
|
+
uri (1.1.1)
|
|
147
|
+
useragent (0.16.11)
|
|
148
|
+
zeitwerk (2.7.5)
|
|
149
|
+
|
|
150
|
+
PLATFORMS
|
|
151
|
+
aarch64-linux-gnu
|
|
152
|
+
aarch64-linux-musl
|
|
153
|
+
arm-linux-gnu
|
|
154
|
+
arm-linux-musl
|
|
155
|
+
arm64-darwin
|
|
156
|
+
x86_64-darwin
|
|
157
|
+
x86_64-linux-gnu
|
|
158
|
+
x86_64-linux-musl
|
|
159
|
+
|
|
160
|
+
DEPENDENCIES
|
|
161
|
+
actionpack (>= 6.1)
|
|
162
|
+
activejob (>= 6.1)
|
|
163
|
+
activesupport (>= 6.1)
|
|
164
|
+
dexkit!
|
|
165
|
+
mongoid (>= 8.0)
|
|
166
|
+
ostruct
|
|
167
|
+
railties (>= 6.1)
|
|
168
|
+
|
|
169
|
+
CHECKSUMS
|
|
170
|
+
actionpack (8.1.2) sha256=ced74147a1f0daafaa4bab7f677513fd4d3add574c7839958f7b4f1de44f8423
|
|
171
|
+
actionview (8.1.2) sha256=80455b2588911c9b72cec22d240edacb7c150e800ef2234821269b2b2c3e2e5b
|
|
172
|
+
activejob (8.1.2) sha256=908dab3713b101859536375819f4156b07bdf4c232cc645e7538adb9e302f825
|
|
173
|
+
activemodel (8.1.2) sha256=e21358c11ce68aed3f9838b7e464977bc007b4446c6e4059781e1d5c03bcf33e
|
|
174
|
+
activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae
|
|
175
|
+
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
176
|
+
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
|
|
177
|
+
bson (5.2.0) sha256=c468c1e8a3cfa1e80531cc519a890f85586986721d8e305f83465cc36bb82608
|
|
178
|
+
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
|
|
179
|
+
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
|
|
180
|
+
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
|
|
181
|
+
crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d
|
|
182
|
+
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
|
|
183
|
+
dexkit (0.8.0)
|
|
184
|
+
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
|
185
|
+
erb (6.0.2) sha256=9fe6264d44f79422c87490a1558479bd0e7dad4dd0e317656e67ea3077b5242b
|
|
186
|
+
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
|
|
187
|
+
globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11
|
|
188
|
+
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
|
|
189
|
+
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
|
|
190
|
+
irb (1.17.0) sha256=168c4ddb93d8a361a045c41d92b2952c7a118fa73f23fe14e55609eb7a863aae
|
|
191
|
+
json (2.19.1) sha256=dd94fdc59e48bff85913829a32350b3148156bc4fd2a95a2568a78b11344082d
|
|
192
|
+
literal (1.9.0) sha256=b1dfac91931e71e1c4ebfddd4b459306f2973e9f749e077a647fece6ea15414a
|
|
193
|
+
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
194
|
+
loofah (2.25.0) sha256=df5ed7ac3bac6a4ec802df3877ee5cc86d027299f8952e6243b3dac446b060e6
|
|
195
|
+
minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d
|
|
196
|
+
mongo (2.23.0) sha256=be2fe4cc6f7119fa6b79e82a1963b2406856b4dc92d0ccfb74db543897be3109
|
|
197
|
+
mongoid (9.0.10) sha256=351192e70027276748f3c946b8926fad9254356e36021dacb5cec08d0740f21d
|
|
198
|
+
nokogiri (1.19.1-aarch64-linux-gnu) sha256=cfdb0eafd9a554a88f12ebcc688d2b9005f9fce42b00b970e3dc199587b27f32
|
|
199
|
+
nokogiri (1.19.1-aarch64-linux-musl) sha256=1e2150ab43c3b373aba76cd1190af7b9e92103564063e48c474f7600923620b5
|
|
200
|
+
nokogiri (1.19.1-arm-linux-gnu) sha256=0a39ed59abe3bf279fab9dd4c6db6fe8af01af0608f6e1f08b8ffa4e5d407fa3
|
|
201
|
+
nokogiri (1.19.1-arm-linux-musl) sha256=3a18e559ee499b064aac6562d98daab3d39ba6cbb4074a1542781b2f556db47d
|
|
202
|
+
nokogiri (1.19.1-arm64-darwin) sha256=dfe2d337e6700eac47290407c289d56bcf85805d128c1b5a6434ddb79731cb9e
|
|
203
|
+
nokogiri (1.19.1-x86_64-darwin) sha256=7093896778cc03efb74b85f915a775862730e887f2e58d6921e3fa3d981e68bf
|
|
204
|
+
nokogiri (1.19.1-x86_64-linux-gnu) sha256=1a4902842a186b4f901078e692d12257678e6133858d0566152fe29cdb98456a
|
|
205
|
+
nokogiri (1.19.1-x86_64-linux-musl) sha256=4267f38ad4fc7e52a2e7ee28ed494e8f9d8eb4f4b3320901d55981c7b995fc23
|
|
206
|
+
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
|
|
207
|
+
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
|
|
208
|
+
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
|
|
209
|
+
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
210
|
+
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
|
|
211
|
+
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
212
|
+
rack (3.2.5) sha256=4cbd0974c0b79f7a139b4812004a62e4c60b145cba76422e288ee670601ed6d3
|
|
213
|
+
rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9
|
|
214
|
+
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
|
|
215
|
+
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
|
|
216
|
+
rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
|
|
217
|
+
rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89
|
|
218
|
+
railties (8.1.2) sha256=1289ece76b4f7668fc46d07e55cc992b5b8751f2ad85548b7da351b8c59f8055
|
|
219
|
+
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
|
|
220
|
+
rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
|
|
221
|
+
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
|
|
222
|
+
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
|
|
223
|
+
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
|
|
224
|
+
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
|
|
225
|
+
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
|
|
226
|
+
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
|
|
227
|
+
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
|
|
228
|
+
useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844
|
|
229
|
+
zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
|
|
230
|
+
|
|
231
|
+
BUNDLED WITH
|
|
232
|
+
4.0.4
|
data/guides/llm/EVENT.md
CHANGED
|
@@ -56,7 +56,7 @@ event.publish(sync: true) # sync
|
|
|
56
56
|
OrderPlaced.publish(order_id: 1, total: 99.99, caused_by: parent_event)
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
**Async** (default): handlers dispatched via ActiveJob. **Sync**: handlers called inline.
|
|
59
|
+
**Async** (default): handlers dispatched via ActiveJob. If ActiveJob is not loaded, `publish(sync: false)` raises `LoadError`. **Sync**: handlers called inline.
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
@@ -109,7 +109,7 @@ class ProcessPayment < Dex::Event::Handler
|
|
|
109
109
|
end
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
When retries exhausted, exception propagates normally.
|
|
112
|
+
When retries exhausted, exception propagates normally. Async handlers and retries require ActiveJob to be loaded.
|
|
113
113
|
|
|
114
114
|
### Callbacks
|
|
115
115
|
|
|
@@ -157,7 +157,7 @@ class FulfillOrder < Dex::Event::Handler
|
|
|
157
157
|
end
|
|
158
158
|
```
|
|
159
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.
|
|
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. Transactions are ActiveRecord-only – in Mongoid-only apps, `after_commit` fires immediately after handler success.
|
|
161
161
|
|
|
162
162
|
### Custom Pipeline
|
|
163
163
|
|
|
@@ -231,6 +231,19 @@ create_table :event_records do |t|
|
|
|
231
231
|
end
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
+
Mongoid stores work too:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
class EventRecord
|
|
238
|
+
include Mongoid::Document
|
|
239
|
+
include Mongoid::Timestamps
|
|
240
|
+
|
|
241
|
+
field :event_type, type: String
|
|
242
|
+
field :payload, type: Hash
|
|
243
|
+
field :metadata, type: Hash
|
|
244
|
+
end
|
|
245
|
+
```
|
|
246
|
+
|
|
234
247
|
Persistence failures are silently rescued — they never halt event publishing.
|
|
235
248
|
|
|
236
249
|
---
|
|
@@ -294,7 +307,7 @@ Everything works without configuration. All three settings are optional.
|
|
|
294
307
|
|
|
295
308
|
```ruby
|
|
296
309
|
# test/test_helper.rb
|
|
297
|
-
require "dex/
|
|
310
|
+
require "dex/event/test_helpers"
|
|
298
311
|
|
|
299
312
|
class Minitest::Test
|
|
300
313
|
include Dex::Event::TestHelpers
|
data/guides/llm/FORM.md
CHANGED
|
@@ -224,7 +224,7 @@ validates :email, uniqueness: true
|
|
|
224
224
|
| `model:` | Explicit model class | `uniqueness: { model: User }` |
|
|
225
225
|
| `attribute:` | Column name if different | `uniqueness: { attribute: :email }` |
|
|
226
226
|
| `scope:` | Scoped uniqueness | `uniqueness: { scope: :tenant_id }` |
|
|
227
|
-
| `case_sensitive:` | Case-insensitive check | `uniqueness: { case_sensitive: false }` |
|
|
227
|
+
| `case_sensitive:` | Case-insensitive check (`LOWER()` on ActiveRecord, case-insensitive regex on Mongoid) | `uniqueness: { case_sensitive: false }` |
|
|
228
228
|
| `conditions:` | Extra query conditions | `uniqueness: { conditions: -> { where(active: true) } }` |
|
|
229
229
|
| `message:` | Custom error message | `uniqueness: { message: "already registered" }` |
|
|
230
230
|
|
|
@@ -237,7 +237,7 @@ validates :email, uniqueness: true
|
|
|
237
237
|
|
|
238
238
|
### Record exclusion
|
|
239
239
|
|
|
240
|
-
When `form.record` is persisted, the current record is excluded from the uniqueness check (for updates).
|
|
240
|
+
When `form.record` is persisted, the current record is excluded from the uniqueness check (for updates) on both ActiveRecord and Mongoid-backed forms.
|
|
241
241
|
|
|
242
242
|
---
|
|
243
243
|
|
data/guides/llm/OPERATION.md
CHANGED
|
@@ -95,7 +95,7 @@ prop? :note, String # optional (nilable, default: nil)
|
|
|
95
95
|
|
|
96
96
|
### _Ref(Model)
|
|
97
97
|
|
|
98
|
-
Accepts model instances or IDs, coerces IDs via `Model.find(id)`. With `lock: true`, uses `Model.lock.find(id)` (SELECT FOR UPDATE). Instances pass through without re-locking. In serialization (recording, async), stores model ID only. IDs are treated as strings in JSON Schema – this supports integer PKs, UUIDs, and Mongoid BSON::ObjectId equally.
|
|
98
|
+
Accepts model instances or IDs, coerces IDs via `Model.find(id)`. With `lock: true`, uses `Model.lock.find(id)` (SELECT FOR UPDATE) – requires a model that responds to `.lock` (ActiveRecord). Mongoid documents do not support row locks and raise `ArgumentError` at declaration time. Instances pass through without re-locking. In serialization (recording, async), stores model ID only via `id.as_json`, so Mongoid BSON::ObjectId values are safe in ActiveJob payloads too. IDs are treated as strings in JSON Schema – this supports integer PKs, UUIDs, and Mongoid BSON::ObjectId equally.
|
|
99
99
|
|
|
100
100
|
Outside the class body (e.g., in tests), use `Dex::RefType.new(Model)` instead of `_Ref(Model)`.
|
|
101
101
|
|
|
@@ -408,11 +408,11 @@ end
|
|
|
408
408
|
|
|
409
409
|
## Transactions
|
|
410
410
|
|
|
411
|
-
Operations run inside database transactions
|
|
411
|
+
Operations run inside database transactions when Dex has an active transaction adapter. ActiveRecord is auto-detected. In Mongoid-only apps, no adapter is active, so transactions are automatically disabled – but `after_commit` still works (callbacks fire immediately after success). If you need Mongoid transactions, use `Mongoid.transaction` directly inside `perform`.
|
|
412
412
|
|
|
413
413
|
```ruby
|
|
414
414
|
transaction false # disable
|
|
415
|
-
transaction :
|
|
415
|
+
transaction :active_record # explicit adapter
|
|
416
416
|
```
|
|
417
417
|
|
|
418
418
|
Child classes can re-enable: `transaction true`.
|
|
@@ -433,7 +433,7 @@ end
|
|
|
433
433
|
Callbacks are always deferred — they run after the outermost operation boundary succeeds:
|
|
434
434
|
|
|
435
435
|
- **Transactional operations:** deferred until the DB transaction commits.
|
|
436
|
-
- **Non-transactional operations:** queued in memory, flushed after the operation pipeline completes successfully.
|
|
436
|
+
- **Non-transactional operations (including Mongoid-only):** queued in memory, flushed after the operation pipeline completes successfully.
|
|
437
437
|
- **Nested operations:** callbacks queue up and flush once at the outermost successful boundary.
|
|
438
438
|
- **On error (`error!` or exception):** queued callbacks are discarded.
|
|
439
439
|
|
|
@@ -441,13 +441,11 @@ Multiple blocks run in registration order.
|
|
|
441
441
|
|
|
442
442
|
**ActiveRecord:** requires Rails 7.2+ (`after_all_transactions_commit`).
|
|
443
443
|
|
|
444
|
-
**Mongoid:** deferred across nested Dex operations. Ambient `Mongoid.transaction` blocks opened outside Dex are not detected — callbacks will fire immediately in that case.
|
|
445
|
-
|
|
446
444
|
---
|
|
447
445
|
|
|
448
446
|
## Advisory Locking
|
|
449
447
|
|
|
450
|
-
Mutual exclusion via database advisory locks (requires `with_advisory_lock` gem). Wraps **outside** the transaction.
|
|
448
|
+
Mutual exclusion via database advisory locks (requires `with_advisory_lock` gem). Wraps **outside** the transaction. ActiveRecord-only; Mongoid-only apps get a clear `LoadError`.
|
|
451
449
|
|
|
452
450
|
```ruby
|
|
453
451
|
advisory_lock { "pay:#{charge_id}" } # dynamic key from props
|
|
@@ -510,7 +508,17 @@ record result: false # params only
|
|
|
510
508
|
record params: false # result only
|
|
511
509
|
```
|
|
512
510
|
|
|
513
|
-
All outcomes are recorded — success (`completed`), business errors (`error`), and exceptions (`failed`). Recording runs outside the operation's own transaction so error records survive its rollbacks. Records still participate in ambient transactions (e.g., an outer operation's transaction).
|
|
511
|
+
All outcomes are recorded — success (`completed`), business errors (`error`), and exceptions (`failed`). Recording runs outside the operation's own transaction so error records survive its rollbacks. Records still participate in ambient transactions (e.g., an outer operation's transaction). Dex validates the configured record model before use and raises if required attributes are missing.
|
|
512
|
+
|
|
513
|
+
Required attributes by feature:
|
|
514
|
+
|
|
515
|
+
- Core recording: `name`, `status`, `error_code`, `error_message`, `error_details`, `performed_at`
|
|
516
|
+
- Params capture: `params` unless `record params: false`
|
|
517
|
+
- Result capture: `result` unless `record result: false`
|
|
518
|
+
- Async record jobs: `params`
|
|
519
|
+
- `once`: `once_key`, plus `once_key_expires_at` when `expires_in:` is used
|
|
520
|
+
|
|
521
|
+
Untyped results are sanitized to JSON-safe values before persistence: Hash keys round-trip as strings, and objects fall back to `as_json`/`to_s` under `"_dex_value"`.
|
|
514
522
|
|
|
515
523
|
Status values: `pending` (async enqueued), `running` (async executing), `completed` (success), `error` (business error via `error!`), `failed` (unhandled exception).
|
|
516
524
|
|
|
@@ -574,7 +582,7 @@ Clearing is idempotent — clearing a non-existent key is a no-op. After clearin
|
|
|
574
582
|
**Requirements:**
|
|
575
583
|
|
|
576
584
|
- Record backend must be configured (`Dex.configure { |c| c.record_class = OperationRecord }`)
|
|
577
|
-
- The record
|
|
585
|
+
- The record backend must satisfy the Recording requirements above, and `once` additionally requires `once_key` plus `once_key_expires_at` when `expires_in:` is used
|
|
578
586
|
- `once` cannot be declared with `record false` — raises `ArgumentError`
|
|
579
587
|
- Only one `once` declaration per operation
|
|
580
588
|
|
|
@@ -586,11 +594,10 @@ Clearing is idempotent — clearing a non-existent key is a no-op. After clearin
|
|
|
586
594
|
# config/initializers/dexkit.rb
|
|
587
595
|
Dex.configure do |config|
|
|
588
596
|
config.record_class = OperationRecord # model for recording (default: nil)
|
|
589
|
-
config.transaction_adapter = nil # auto-detect (default); or :active_record / :mongoid
|
|
590
597
|
end
|
|
591
598
|
```
|
|
592
599
|
|
|
593
|
-
All DSL methods validate arguments at declaration time — typos and wrong types raise `ArgumentError` immediately (e.g., `error "string"`, `async priority: 5`, `transaction :redis`).
|
|
600
|
+
All DSL methods validate arguments at declaration time — typos and wrong types raise `ArgumentError` immediately (e.g., `error "string"`, `async priority: 5`, `transaction :redis`). Only `:active_record` is a valid transaction adapter.
|
|
594
601
|
|
|
595
602
|
---
|
|
596
603
|
|
|
@@ -598,22 +605,20 @@ All DSL methods validate arguments at declaration time — typos and wrong types
|
|
|
598
605
|
|
|
599
606
|
```ruby
|
|
600
607
|
# test/test_helper.rb
|
|
601
|
-
require "dex/test_helpers"
|
|
608
|
+
require "dex/operation/test_helpers"
|
|
602
609
|
|
|
603
610
|
class Minitest::Test
|
|
604
|
-
include Dex::TestHelpers
|
|
611
|
+
include Dex::Operation::TestHelpers
|
|
605
612
|
end
|
|
606
613
|
```
|
|
607
614
|
|
|
608
615
|
Not autoloaded — stays out of production. TestLog and stubs are auto-cleared in `setup`.
|
|
609
616
|
|
|
610
|
-
For Mongoid-backed operation tests, run against a MongoDB replica set (MongoDB transactions require it).
|
|
611
|
-
|
|
612
617
|
### Subject & Execution
|
|
613
618
|
|
|
614
619
|
```ruby
|
|
615
620
|
class CreateUserTest < Minitest::Test
|
|
616
|
-
include Dex::TestHelpers
|
|
621
|
+
include Dex::Operation::TestHelpers
|
|
617
622
|
|
|
618
623
|
testing CreateUser # default for all helpers
|
|
619
624
|
|
|
@@ -778,7 +783,7 @@ Each entry has: `name`, `operation_class`, `params`, `result` (Ok/Err), `duratio
|
|
|
778
783
|
|
|
779
784
|
```ruby
|
|
780
785
|
class CreateUserTest < Minitest::Test
|
|
781
|
-
include Dex::TestHelpers
|
|
786
|
+
include Dex::Operation::TestHelpers
|
|
782
787
|
|
|
783
788
|
testing CreateUser
|
|
784
789
|
|
data/guides/llm/QUERY.md
CHANGED
|
@@ -91,7 +91,7 @@ Reserved prop names: `scope`, `sort`, `resolve`, `call`, `from_params`, `to_para
|
|
|
91
91
|
| `:in` | `IN (values)` | `filter :roles, :in, column: :role` |
|
|
92
92
|
| `:not_in` | `NOT IN (values)` | `filter :roles, :not_in, column: :role` |
|
|
93
93
|
|
|
94
|
-
String strategies (`:contains`, `:starts_with`, `:ends_with`) use case-insensitive matching. With ActiveRecord, this uses Arel `matches` (LIKE); with Mongoid, case-insensitive regex. Wildcards in values are auto-sanitized. The adapter is auto-detected from the scope.
|
|
94
|
+
String strategies (`:contains`, `:starts_with`, `:ends_with`) use case-insensitive matching. With ActiveRecord, this uses Arel `matches` (LIKE); with Mongoid, case-insensitive regex. Wildcards in values are auto-sanitized. The adapter is auto-detected from the scope, and Mongoid association scopes/proxies are normalized to `Mongoid::Criteria` automatically.
|
|
95
95
|
|
|
96
96
|
### Column Mapping
|
|
97
97
|
|
|
@@ -159,7 +159,7 @@ Only one default per class. Applied when no sort is provided.
|
|
|
159
159
|
|
|
160
160
|
### `.call`
|
|
161
161
|
|
|
162
|
-
Returns a queryable scope (`ActiveRecord::Relation` or `Mongoid::Criteria`):
|
|
162
|
+
Returns a queryable scope (`ActiveRecord::Relation` or `Mongoid::Criteria`). Association scopes like `current_user.posts` work too:
|
|
163
163
|
|
|
164
164
|
```ruby
|
|
165
165
|
users = UserSearch.call(name: "ali", role: %w[admin], sort: "-name")
|
data/lib/dex/event/bus.rb
CHANGED
|
@@ -81,6 +81,7 @@ module Dex
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def enqueue(handler_class, event, trace_data)
|
|
84
|
+
ensure_active_job_loaded!
|
|
84
85
|
ctx = event.context
|
|
85
86
|
|
|
86
87
|
Dex::Event::Processor.perform_later(
|
|
@@ -92,6 +93,12 @@ module Dex
|
|
|
92
93
|
context: ctx
|
|
93
94
|
)
|
|
94
95
|
end
|
|
96
|
+
|
|
97
|
+
def ensure_active_job_loaded!
|
|
98
|
+
return if defined?(ActiveJob::Base)
|
|
99
|
+
|
|
100
|
+
raise LoadError, "ActiveJob is required for async event handlers. Add 'activejob' to your Gemfile."
|
|
101
|
+
end
|
|
95
102
|
end
|
|
96
103
|
end
|
|
97
104
|
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Event
|
|
5
|
+
module EventTestWrapper
|
|
6
|
+
CAPTURING_KEY = :_dex_event_capturing
|
|
7
|
+
PUBLISHED_KEY = :_dex_event_published
|
|
8
|
+
|
|
9
|
+
@_installed = false
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
include ExecutionState
|
|
13
|
+
|
|
14
|
+
def install!
|
|
15
|
+
return if @_installed
|
|
16
|
+
|
|
17
|
+
Dex::Event::Bus.singleton_class.prepend(BusInterceptor)
|
|
18
|
+
@_installed = true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def installed?
|
|
22
|
+
@_installed
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def capturing?
|
|
26
|
+
(_execution_state[CAPTURING_KEY] || 0) > 0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def begin_capture!
|
|
30
|
+
_execution_state[CAPTURING_KEY] = (_execution_state[CAPTURING_KEY] || 0) + 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def end_capture!
|
|
34
|
+
depth = (_execution_state[CAPTURING_KEY] || 0) - 1
|
|
35
|
+
_execution_state[CAPTURING_KEY] = [depth, 0].max
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def published_events
|
|
39
|
+
_execution_state[PUBLISHED_KEY] ||= []
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def clear_published!
|
|
43
|
+
_execution_state[PUBLISHED_KEY] = []
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module BusInterceptor
|
|
48
|
+
def publish(event, sync:)
|
|
49
|
+
if Dex::Event::EventTestWrapper.capturing?
|
|
50
|
+
return if Dex::Event::Suppression.suppressed?(event.class)
|
|
51
|
+
|
|
52
|
+
Dex::Event::EventTestWrapper.published_events << event
|
|
53
|
+
else
|
|
54
|
+
super(event, sync: true)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module TestHelpers
|
|
61
|
+
def self.included(base)
|
|
62
|
+
EventTestWrapper.install!
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def setup
|
|
66
|
+
super
|
|
67
|
+
EventTestWrapper.clear_published!
|
|
68
|
+
Dex::Event::Trace.clear!
|
|
69
|
+
Dex::Event::Suppression.clear!
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def capture_events
|
|
73
|
+
EventTestWrapper.begin_capture!
|
|
74
|
+
yield
|
|
75
|
+
ensure
|
|
76
|
+
EventTestWrapper.end_capture!
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def _dex_published_events
|
|
82
|
+
EventTestWrapper.published_events
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
require_relative "test_helpers/assertions"
|