rage_arch 0.1.4 → 0.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.
- checksums.yaml +4 -4
- data/README.md +125 -69
- data/lib/generators/rage_arch/controller_generator.rb +53 -0
- data/lib/generators/rage_arch/dep_generator.rb +2 -0
- data/lib/generators/rage_arch/job_generator.rb +28 -0
- data/lib/generators/rage_arch/mailer_generator.rb +42 -0
- data/lib/generators/rage_arch/resource_generator.rb +110 -0
- data/lib/generators/rage_arch/scaffold_generator.rb +0 -12
- data/lib/generators/rage_arch/templates/controller/action_use_case.rb.tt +12 -0
- data/lib/generators/rage_arch/templates/controller/controller.rb.tt +12 -0
- data/lib/generators/rage_arch/templates/dep.rb.tt +1 -1
- data/lib/generators/rage_arch/templates/job/job.rb.tt +15 -0
- data/lib/generators/rage_arch/templates/mailer/mailer_dep.rb.tt +17 -0
- data/lib/generators/rage_arch/templates/rage_arch.rb.tt +5 -8
- data/lib/generators/rage_arch/templates/scaffold/controller.rb.tt +8 -3
- data/lib/generators/rage_arch/templates/scaffold/create.rb.tt +0 -1
- data/lib/generators/rage_arch/templates/scaffold/destroy.rb.tt +0 -1
- data/lib/generators/rage_arch/templates/scaffold/list.rb.tt +0 -1
- data/lib/generators/rage_arch/templates/scaffold/new.rb.tt +0 -1
- data/lib/generators/rage_arch/templates/scaffold/post_repo.rb.tt +1 -1
- data/lib/generators/rage_arch/templates/scaffold/show.rb.tt +0 -1
- data/lib/generators/rage_arch/templates/scaffold/update.rb.tt +0 -1
- data/lib/generators/rage_arch/templates/use_case.rb.tt +2 -4
- data/lib/rage_arch/auto_registry.rb +95 -0
- data/lib/rage_arch/container.rb +36 -3
- data/lib/rage_arch/railtie.rb +6 -10
- data/lib/rage_arch/rspec_helpers.rb +19 -0
- data/lib/rage_arch/subscriber_job.rb +13 -0
- data/lib/rage_arch/use_case.rb +102 -35
- data/lib/rage_arch/version.rb +1 -1
- data/lib/rage_arch.rb +37 -3
- metadata +18 -8
- data/lib/generators/rage_arch/ar_dep_generator.rb +0 -74
- data/lib/generators/rage_arch/templates/ar_dep.rb.tt +0 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 67361dabc2170be9ff033ec5cacbc0c271d01351bccde53e04804417043fc1ac
|
|
4
|
+
data.tar.gz: fede1669810366cc822f31ce1e14152c564365ed5934cab2b34eb0707e5245c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 725210b1791995e3dd9f7964676b2ca7a87016113eb05454dcb7e327efdde2f2715e3691b8689c47ac56292cc800244a32faa9f8d1374a1d0d3e434b58c69664
|
|
7
|
+
data.tar.gz: d846584108e497e4b2ab036a3965f7ef6d6466a6ad57c1fa33e63aab005223b618583153f3710380663ad0e3ae3b3837a2b069b7d1d90fc462b2500d7997a0ef
|
data/README.md
CHANGED
|
@@ -35,50 +35,6 @@ result.errors # => ["Validation error"]
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
### `RageArch::UseCase::Base` — use cases
|
|
39
|
-
|
|
40
|
-
```ruby
|
|
41
|
-
class CreateOrder < RageArch::UseCase::Base
|
|
42
|
-
use_case_symbol :create_order
|
|
43
|
-
deps :order_store, :notifications # injected by symbol
|
|
44
|
-
|
|
45
|
-
def call(params = {})
|
|
46
|
-
order = order_store.build(params)
|
|
47
|
-
return failure(order.errors) unless order_store.save(order)
|
|
48
|
-
notifications.notify(:order_created, order)
|
|
49
|
-
success(order)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Build and run manually:
|
|
55
|
-
|
|
56
|
-
```ruby
|
|
57
|
-
use_case = RageArch::UseCase::Base.build(:create_order)
|
|
58
|
-
result = use_case.call(reference: "REF-1", total_cents: 1000)
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
#### `ar_dep` — inline ActiveRecord dep
|
|
62
|
-
|
|
63
|
-
When a dep is a simple wrapper over an ActiveRecord model, declare it directly in the use case instead of creating a separate class:
|
|
64
|
-
|
|
65
|
-
```ruby
|
|
66
|
-
class Posts::Create < RageArch::UseCase::Base
|
|
67
|
-
use_case_symbol :posts_create
|
|
68
|
-
ar_dep :post_store, Post # auto-creates an AR adapter if :post_store is not registered
|
|
69
|
-
|
|
70
|
-
def call(params = {})
|
|
71
|
-
post = post_store.build(params)
|
|
72
|
-
return failure(post.errors.full_messages) unless post_store.save(post)
|
|
73
|
-
success(post: post)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
If `:post_store` is registered in the container, that implementation is used. Otherwise, `RageArch::Deps::ActiveRecord.for(Post)` is used as fallback.
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
38
|
### `RageArch::Container` — dependency registration
|
|
83
39
|
|
|
84
40
|
```ruby
|
|
@@ -98,6 +54,15 @@ RageArch.resolve(:order_store) # => the registered implementation
|
|
|
98
54
|
RageArch.registered?(:order_store) # => true
|
|
99
55
|
```
|
|
100
56
|
|
|
57
|
+
**Convention-based auto-registration:** Use cases from `app/use_cases/` and deps from `app/deps/` are auto-registered at boot. The initializer is only needed to override conventions or register external adapters.
|
|
58
|
+
|
|
59
|
+
**AR model auto-resolution:** If a dep symbol ends in `_store` or `_repo` and no file exists in `app/deps/`, rage_arch looks for an ActiveRecord model automatically:
|
|
60
|
+
|
|
61
|
+
- `:post_store` or `:post_repo` resolves to `Post`
|
|
62
|
+
- `:appointment_store` or `:appointment_repo` resolves to `Appointment`
|
|
63
|
+
|
|
64
|
+
Explicit `RageArch.register(...)` always takes priority.
|
|
65
|
+
|
|
101
66
|
---
|
|
102
67
|
|
|
103
68
|
### Dependencies (Deps)
|
|
@@ -129,21 +94,7 @@ module Posts
|
|
|
129
94
|
end
|
|
130
95
|
```
|
|
131
96
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
```ruby
|
|
135
|
-
RageArch.register(:post_store, Posts::PostStore.new)
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
#### ActiveRecord dep (generated)
|
|
139
|
-
|
|
140
|
-
For deps that simply wrap an AR model with standard CRUD, use the generator:
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
rails g rage_arch:ar_dep post_store Post
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
This creates `app/deps/posts/post_store.rb` with `build`, `find`, `save`, `update`, `destroy`, and `list` methods backed by `RageArch::Deps::ActiveRecord.for(Post)`.
|
|
97
|
+
Auto-registered by convention from `app/deps/` — no manual registration needed.
|
|
147
98
|
|
|
148
99
|
#### Generating a dep from use case analysis
|
|
149
100
|
|
|
@@ -172,6 +123,79 @@ The generator scans `app/deps/` for files matching the symbol, updates `config/i
|
|
|
172
123
|
|
|
173
124
|
---
|
|
174
125
|
|
|
126
|
+
### `RageArch::UseCase::Base` — use cases
|
|
127
|
+
|
|
128
|
+
Use cases declare their dependencies by symbol, receive them via injection, and return a `Result`. The symbol is inferred from the class name by convention:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
class Orders::Create < RageArch::UseCase::Base
|
|
132
|
+
# symbol :orders_create is inferred automatically
|
|
133
|
+
deps :order_store, :notifications
|
|
134
|
+
|
|
135
|
+
def call(params = {})
|
|
136
|
+
order = order_store.build(params)
|
|
137
|
+
return failure(order.errors) unless order_store.save(order)
|
|
138
|
+
notifications.notify(:order_created, order)
|
|
139
|
+
success(order: order)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Symbol inference: `Orders::Create` becomes `:orders_create`. Explicit `use_case_symbol :my_symbol` still works as override.
|
|
145
|
+
|
|
146
|
+
Build and run manually:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
use_case = RageArch::UseCase::Base.build(:orders_create)
|
|
150
|
+
result = use_case.call(reference: "REF-1", total_cents: 1000)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### undo — automatic rollback on failure
|
|
156
|
+
|
|
157
|
+
Define `def undo(value)` on any use case. If `call` returns `failure(...)`, `undo` is called automatically:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
class Payments::Charge < RageArch::UseCase::Base
|
|
161
|
+
deps :payment_gateway
|
|
162
|
+
|
|
163
|
+
def call(params = {})
|
|
164
|
+
charge = payment_gateway.charge(params[:amount])
|
|
165
|
+
return failure(["Payment failed"]) unless charge.success?
|
|
166
|
+
success(charge_id: charge.id)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def undo(value)
|
|
170
|
+
payment_gateway.refund(value[:charge_id]) if value
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Cascade undo with `use_cases`:** When a parent use case orchestrates children via `use_cases` and returns failure, each successfully-completed child has its `undo` called in reverse order:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
class Bookings::Create < RageArch::UseCase::Base
|
|
179
|
+
use_cases :payments_charge, :slots_reserve, :notifications_send
|
|
180
|
+
|
|
181
|
+
def call(params = {})
|
|
182
|
+
charge = payments_charge.call(amount: params[:amount])
|
|
183
|
+
return charge unless charge.success?
|
|
184
|
+
|
|
185
|
+
reserve = slots_reserve.call(slot_id: params[:slot_id])
|
|
186
|
+
return failure(reserve.errors) unless reserve.success?
|
|
187
|
+
# If this fails, slots_reserve.undo and payments_charge.undo run automatically
|
|
188
|
+
|
|
189
|
+
notifications_send.call(booking: params)
|
|
190
|
+
success(booking: params)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
No DSL, no configuration. Just define `undo` where you need rollback.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
175
199
|
### `RageArch::Controller` — thin controller mixin
|
|
176
200
|
|
|
177
201
|
```ruby
|
|
@@ -202,13 +226,12 @@ end
|
|
|
202
226
|
|
|
203
227
|
### `RageArch::EventPublisher` — domain events
|
|
204
228
|
|
|
205
|
-
Every use case automatically publishes an event when it finishes. Other use cases subscribe to react
|
|
229
|
+
Every use case automatically publishes an event when it finishes. Other use cases subscribe to react. **Subscribers run asynchronously via ActiveJob by default:**
|
|
206
230
|
|
|
207
231
|
```ruby
|
|
208
232
|
class Notifications::SendPostCreatedEmail < RageArch::UseCase::Base
|
|
209
|
-
use_case_symbol :send_post_created_email
|
|
210
233
|
deps :mailer
|
|
211
|
-
subscribe :posts_create #
|
|
234
|
+
subscribe :posts_create # async by default
|
|
212
235
|
|
|
213
236
|
def call(payload = {})
|
|
214
237
|
return success unless payload[:success]
|
|
@@ -218,6 +241,12 @@ class Notifications::SendPostCreatedEmail < RageArch::UseCase::Base
|
|
|
218
241
|
end
|
|
219
242
|
```
|
|
220
243
|
|
|
244
|
+
**Synchronous subscribers** (opt-in):
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
subscribe :posts_create, async: false
|
|
248
|
+
```
|
|
249
|
+
|
|
221
250
|
Subscribe to multiple events or everything:
|
|
222
251
|
|
|
223
252
|
```ruby
|
|
@@ -237,7 +266,6 @@ skip_auto_publish
|
|
|
237
266
|
|
|
238
267
|
```ruby
|
|
239
268
|
class CreateOrderWithNotification < RageArch::UseCase::Base
|
|
240
|
-
use_case_symbol :create_order_with_notification
|
|
241
269
|
deps :order_store
|
|
242
270
|
use_cases :orders_create, :notifications_send
|
|
243
271
|
|
|
@@ -260,12 +288,15 @@ end
|
|
|
260
288
|
| `rails g rage_arch:scaffold Post title:string` | Full CRUD: model, use cases, dep, controller, views, routes |
|
|
261
289
|
| `rails g rage_arch:scaffold Post title:string --api` | Same but API-only (JSON responses) |
|
|
262
290
|
| `rails g rage_arch:scaffold Post title:string --skip-model` | Skip model/migration if it already exists |
|
|
291
|
+
| `rails g rage_arch:resource Post title:string` | Like scaffold but without views (API-style controller) |
|
|
292
|
+
| `rails g rage_arch:controller Pages home about` | Thin controller + use case per action + routes |
|
|
263
293
|
| `rails g rage_arch:use_case CreateOrder` | Generates a base use case file |
|
|
264
294
|
| `rails g rage_arch:use_case orders/create` | Generates a namespaced use case (`Orders::Create`) |
|
|
265
295
|
| `rails g rage_arch:dep post_store` | Generates a dep class by scanning method calls in use cases |
|
|
266
|
-
| `rails g rage_arch:ar_dep post_store Post` | Generates a dep that wraps an ActiveRecord model |
|
|
267
296
|
| `rails g rage_arch:dep_switch post_store` | Lists implementations and switches which one is registered |
|
|
268
297
|
| `rails g rage_arch:dep_switch post_store PostgresPostStore` | Directly activates a specific implementation |
|
|
298
|
+
| `rails g rage_arch:mailer PostMailer post_created` | Rails mailer + dep wrapper (auto-registered) |
|
|
299
|
+
| `rails g rage_arch:job ProcessOrder orders_create` | ActiveJob that runs a use case by symbol |
|
|
269
300
|
|
|
270
301
|
---
|
|
271
302
|
|
|
@@ -274,7 +305,23 @@ end
|
|
|
274
305
|
```ruby
|
|
275
306
|
# spec/rails_helper.rb
|
|
276
307
|
require "rage_arch/rspec_matchers"
|
|
308
|
+
require "rage_arch/rspec_helpers"
|
|
277
309
|
require "rage_arch/fake_event_publisher"
|
|
310
|
+
|
|
311
|
+
RSpec.configure do |config|
|
|
312
|
+
config.include RageArch::RSpecHelpers # auto-isolates each test
|
|
313
|
+
end
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Test isolation:** `RageArch::RSpecHelpers` wraps each example in `RageArch.isolate`, so dep registrations never bleed between tests.
|
|
317
|
+
|
|
318
|
+
**Manual isolation** (without the helper):
|
|
319
|
+
|
|
320
|
+
```ruby
|
|
321
|
+
RageArch.isolate do
|
|
322
|
+
RageArch.register(:payment_gateway, FakeGateway.new)
|
|
323
|
+
# registrations inside are scoped; originals restored on exit
|
|
324
|
+
end
|
|
278
325
|
```
|
|
279
326
|
|
|
280
327
|
**Result matchers:**
|
|
@@ -306,6 +353,9 @@ config.rage_arch.auto_publish_events = false
|
|
|
306
353
|
|
|
307
354
|
# Disable boot verification (default: true)
|
|
308
355
|
config.rage_arch.verify_deps = false
|
|
356
|
+
|
|
357
|
+
# Run all subscribers synchronously — useful for test environments (default: true)
|
|
358
|
+
config.rage_arch.async_subscribers = false
|
|
309
359
|
```
|
|
310
360
|
|
|
311
361
|
---
|
|
@@ -314,16 +364,17 @@ config.rage_arch.verify_deps = false
|
|
|
314
364
|
|
|
315
365
|
At boot, `RageArch.verify_deps!` runs automatically and raises if it finds wiring problems. It checks:
|
|
316
366
|
|
|
317
|
-
- Every dep declared with `deps :symbol` is registered in the container
|
|
367
|
+
- Every dep declared with `deps :symbol` is registered in the container (or auto-resolved for `_store`/`_repo` deps)
|
|
318
368
|
- Every method called on a dep is implemented by the registered object (via static analysis)
|
|
319
369
|
- Every use case declared with `use_cases :symbol` exists in the registry
|
|
370
|
+
- Warns if `use_case_symbol` doesn't match the convention-inferred symbol
|
|
320
371
|
|
|
321
372
|
Example error output:
|
|
322
373
|
|
|
323
374
|
```
|
|
324
375
|
RageArch boot verification failed:
|
|
325
|
-
UseCase :posts_create (Posts::Create) declares dep :post_store — not registered in container
|
|
326
|
-
UseCase :posts_create (Posts::Create) calls :post_store#save — Posts::PostStore does not implement #save
|
|
376
|
+
UseCase :posts_create (Posts::Create) declares dep :post_store — not registered in container and no AR model found for _store/_repo
|
|
377
|
+
UseCase :posts_create (Posts::Create) calls dep :post_store#save — Posts::PostStore does not implement #save
|
|
327
378
|
UseCase :posts_notify (Posts::Notify) declares use_cases :email_send — not registered in use case registry
|
|
328
379
|
```
|
|
329
380
|
|
|
@@ -346,9 +397,14 @@ end
|
|
|
346
397
|
|
|
347
398
|
## Documentation
|
|
348
399
|
|
|
349
|
-
- [`doc/REFERENCE.md`](doc/REFERENCE.md) — Full API reference with all options and examples
|
|
350
|
-
- [`doc/DOCUMENTATION.md`](doc/DOCUMENTATION.md) — Detailed behaviour (use cases, deps, events, config)
|
|
351
400
|
- [`doc/GETTING_STARTED.md`](doc/GETTING_STARTED.md) — Getting started guide with common tasks
|
|
401
|
+
- [`doc/TUTORIAL.md`](doc/TUTORIAL.md) — Side-by-side Rails vs Rails+RageArch comparison
|
|
402
|
+
- [`doc/DOCUMENTATION.md`](doc/DOCUMENTATION.md) — Detailed behaviour (use cases, deps, events, config)
|
|
403
|
+
- [`doc/REFERENCE.md`](doc/REFERENCE.md) — Quick-lookup API reference (classes, methods, options)
|
|
404
|
+
|
|
405
|
+
## AI context
|
|
406
|
+
|
|
407
|
+
If you use an AI coding agent, point it to [`IA.md`](IA.md) for a compact reference of the full gem API, conventions, and architecture.
|
|
352
408
|
|
|
353
409
|
## License
|
|
354
410
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module RageArch
|
|
6
|
+
module Generators
|
|
7
|
+
class ControllerGenerator < ::Rails::Generators::NamedBase
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
argument :actions, type: :array, default: [], banner: "action action"
|
|
11
|
+
|
|
12
|
+
desc "Generate a thin RageArch controller with use cases for each action."
|
|
13
|
+
def create_controller
|
|
14
|
+
template "controller/controller.rb.tt", File.join("app/controllers", "#{plural_file_name}_controller.rb")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_use_cases
|
|
18
|
+
actions.each do |action|
|
|
19
|
+
@current_action = action
|
|
20
|
+
template "controller/action_use_case.rb.tt", File.join("app/use_cases", plural_file_name, "#{action}.rb")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add_routes
|
|
25
|
+
return if actions.empty?
|
|
26
|
+
lines = actions.map { |a| " get \"#{plural_file_name}/#{a}\", to: \"#{plural_file_name}##{a}\"" }
|
|
27
|
+
route lines.join("\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def plural_file_name
|
|
33
|
+
file_name.pluralize
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def controller_class_name
|
|
37
|
+
plural_file_name.camelize
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def module_name
|
|
41
|
+
plural_file_name.camelize
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def current_action
|
|
45
|
+
@current_action
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def use_case_symbol(action)
|
|
49
|
+
"#{plural_file_name}_#{action}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -27,6 +27,8 @@ module RageArch
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
private
|
|
31
|
+
|
|
30
32
|
# When the dep file already exists, parse it for existing method names and insert only stubs for missing ones.
|
|
31
33
|
def add_missing_methods_only(relative_path)
|
|
32
34
|
full_path = File.join(destination_root, relative_path)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module RageArch
|
|
6
|
+
module Generators
|
|
7
|
+
class JobGenerator < ::Rails::Generators::NamedBase
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
argument :use_case_symbol, type: :string, required: false, default: nil, banner: "use_case_symbol"
|
|
11
|
+
|
|
12
|
+
desc "Generate an ActiveJob that runs a RageArch use case by symbol."
|
|
13
|
+
def create_job
|
|
14
|
+
template "job/job.rb.tt", File.join("app/jobs", "#{file_name}_job.rb")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def job_class_name
|
|
20
|
+
file_name.camelize
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def inferred_symbol
|
|
24
|
+
use_case_symbol || file_name
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module RageArch
|
|
6
|
+
module Generators
|
|
7
|
+
class MailerGenerator < ::Rails::Generators::NamedBase
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
argument :actions, type: :array, default: [], banner: "action action"
|
|
11
|
+
|
|
12
|
+
desc "Generate a Rails mailer and a RageArch dep wrapper (auto-registered from app/deps/)."
|
|
13
|
+
def create_rails_mailer
|
|
14
|
+
args = [name] + actions
|
|
15
|
+
invoke "mailer", args
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_dep
|
|
19
|
+
dep_dir = File.join("app/deps")
|
|
20
|
+
template "mailer/mailer_dep.rb.tt", File.join(dep_dir, "#{dep_file_name}.rb")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def mailer_class_name
|
|
26
|
+
name.camelize
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def dep_class_name
|
|
30
|
+
"#{mailer_class_name}Dep"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def dep_file_name
|
|
34
|
+
"#{file_name}_dep"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def dep_symbol
|
|
38
|
+
file_name.underscore
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module RageArch
|
|
7
|
+
module Generators
|
|
8
|
+
class ResourceGenerator < ::Rails::Generators::NamedBase
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
|
|
12
|
+
class_option :skip_model, type: :boolean, default: false, desc: "Skip model and migration (use when model already exists)"
|
|
13
|
+
|
|
14
|
+
desc "Generate a RageArch resource: model, migration, CRUD use cases, dep, API-style controller, and routes (no views)."
|
|
15
|
+
def create_all
|
|
16
|
+
create_model_and_migration
|
|
17
|
+
create_use_cases
|
|
18
|
+
create_dep
|
|
19
|
+
create_controller
|
|
20
|
+
add_route
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def create_model_and_migration
|
|
26
|
+
return if options[:skip_model]
|
|
27
|
+
args = [name] + attributes.map(&:to_s)
|
|
28
|
+
invoke "active_record:model", args
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_use_cases
|
|
32
|
+
dir = File.join("app/use_cases", plural_name)
|
|
33
|
+
empty_directory dir
|
|
34
|
+
template "scaffold/list.rb.tt", File.join(dir, "list.rb")
|
|
35
|
+
template "scaffold/show.rb.tt", File.join(dir, "show.rb")
|
|
36
|
+
template "scaffold/new.rb.tt", File.join(dir, "new.rb")
|
|
37
|
+
template "scaffold/create.rb.tt", File.join(dir, "create.rb")
|
|
38
|
+
template "scaffold/update.rb.tt", File.join(dir, "update.rb")
|
|
39
|
+
template "scaffold/destroy.rb.tt", File.join(dir, "destroy.rb")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def create_dep
|
|
43
|
+
dep_dir = File.join("app/deps", plural_name)
|
|
44
|
+
empty_directory dep_dir
|
|
45
|
+
template "scaffold/post_repo.rb.tt", File.join(dep_dir, "#{singular_name}_repo.rb")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def create_controller
|
|
49
|
+
template "scaffold/api_controller.rb.tt", File.join("app/controllers", "#{plural_name}_controller.rb")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def add_route
|
|
53
|
+
route "resources :#{plural_name}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def plural_name
|
|
57
|
+
name.underscore.pluralize
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def singular_name
|
|
61
|
+
name.underscore
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def model_class_name
|
|
65
|
+
name.camelize
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def module_name
|
|
69
|
+
plural_name.camelize
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def repo_symbol
|
|
73
|
+
"#{singular_name}_repo"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def repo_class_name
|
|
77
|
+
"#{module_name}::#{singular_name.camelize}Repo"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def list_symbol
|
|
81
|
+
"#{plural_name}_list"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def show_symbol
|
|
85
|
+
"#{plural_name}_show"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def create_symbol
|
|
89
|
+
"#{plural_name}_create"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def update_symbol
|
|
93
|
+
"#{plural_name}_update"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def destroy_symbol
|
|
97
|
+
"#{plural_name}_destroy"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def new_symbol
|
|
101
|
+
"#{plural_name}_new"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def attribute_names_for_permit
|
|
105
|
+
return [] if attributes.blank?
|
|
106
|
+
attributes.map { |a| a.to_s.split(":").first.to_sym }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -20,7 +20,6 @@ module RageArch
|
|
|
20
20
|
invoke_rails_scaffold_views
|
|
21
21
|
create_controller
|
|
22
22
|
add_route
|
|
23
|
-
inject_register_ar
|
|
24
23
|
end
|
|
25
24
|
|
|
26
25
|
private
|
|
@@ -65,17 +64,6 @@ module RageArch
|
|
|
65
64
|
route "resources :#{plural_name}"
|
|
66
65
|
end
|
|
67
66
|
|
|
68
|
-
def inject_register_ar
|
|
69
|
-
initializer_path = File.join(destination_root, "config/initializers/rage_arch.rb")
|
|
70
|
-
return unless File.exist?(initializer_path)
|
|
71
|
-
content = File.read(initializer_path)
|
|
72
|
-
return if content.include?("register_ar(:#{repo_symbol})")
|
|
73
|
-
inject_line = " RageArch.register_ar(:#{repo_symbol}, #{model_class_name})\n"
|
|
74
|
-
content.sub!(/(Rails\.application\.config\.after_initialize do\s*\n)/m, "\\1#{inject_line}")
|
|
75
|
-
File.write(initializer_path, content)
|
|
76
|
-
say_status :inject, "config/initializers/rage_arch.rb (register_ar :#{repo_symbol})", :green
|
|
77
|
-
end
|
|
78
|
-
|
|
79
67
|
def plural_name
|
|
80
68
|
name.underscore.pluralize
|
|
81
69
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class <%= controller_class_name %>Controller < ApplicationController
|
|
4
|
+
<%- actions.each do |action| -%>
|
|
5
|
+
def <%= action %>
|
|
6
|
+
run :<%= use_case_symbol(action) %>,
|
|
7
|
+
success: ->(r) { render :<%= action %> },
|
|
8
|
+
failure: ->(r) { flash_errors(r); redirect_to root_path }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
<%- end -%>
|
|
12
|
+
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Dep for :<%= symbol_name %>.
|
|
4
4
|
# Methods detected from use cases: <%= @methods.join(', ') %>.
|
|
5
|
-
#
|
|
5
|
+
# Auto-registered by convention from app/deps/
|
|
6
6
|
module <%= module_name %>
|
|
7
7
|
class <%= class_name %>
|
|
8
8
|
<% @methods.each do |method_name| -%>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class <%= job_class_name %>Job < ApplicationJob
|
|
4
|
+
queue_as :default
|
|
5
|
+
|
|
6
|
+
def perform(**params)
|
|
7
|
+
result = RageArch::UseCase::Base.build(:<%= inferred_symbol %>).call(params)
|
|
8
|
+
|
|
9
|
+
unless result.success?
|
|
10
|
+
Rails.logger.error "[<%= job_class_name %>Job] Use case :<%= inferred_symbol %> failed: #{result.errors.inspect}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
result
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Dep for :<%= dep_symbol %>. Wraps <%= mailer_class_name %>.
|
|
4
|
+
# Auto-registered by convention from app/deps/
|
|
5
|
+
class <%= dep_class_name %>
|
|
6
|
+
<%- actions.each do |action| -%>
|
|
7
|
+
def <%= action %>(*args)
|
|
8
|
+
<%= mailer_class_name %>.<%=action %>(*args).deliver_later
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
<%- end -%>
|
|
12
|
+
<%- if actions.empty? -%>
|
|
13
|
+
# def send_email(*args)
|
|
14
|
+
# <%= mailer_class_name %>.send_email(*args).deliver_later
|
|
15
|
+
# end
|
|
16
|
+
<%- end -%>
|
|
17
|
+
end
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# rage_arch auto-registers use cases from app/use_cases/ and deps from app/deps/
|
|
4
|
+
# Add registrations here only to override conventions or register external adapters.
|
|
5
|
+
#
|
|
6
|
+
# Example:
|
|
7
|
+
# RageArch.register(:payment_gateway, Payments::StripeGateway.new)
|
|
5
8
|
|
|
6
9
|
Rails.application.config.after_initialize do
|
|
7
|
-
# Deps
|
|
8
|
-
# RageArch.register(:post_repo, Posts::PostRepo.new)
|
|
9
|
-
# RageArch.register_ar(:user_repo, User)
|
|
10
|
-
|
|
11
10
|
# Event publisher: use cases that declare subscribe :event_name are wired here.
|
|
12
11
|
publisher = RageArch::EventPublisher.new
|
|
13
12
|
RageArch::UseCase::Base.wire_subscriptions_to(publisher)
|
|
14
13
|
RageArch.register(:event_publisher, publisher)
|
|
15
14
|
|
|
16
|
-
# Called here (after all deps are registered) instead of relying on the Railtie,
|
|
17
|
-
# which runs before this block. Skipped during asset precompilation.
|
|
18
15
|
RageArch.verify_deps! unless ENV["SECRET_KEY_BASE_DUMMY"].present?
|
|
19
16
|
end
|