active_interaction-extras 1.1.0 → 2.0.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/.github/workflows/ci.yml +3 -2
- data/CHANGELOG.md +24 -0
- data/README.md +325 -0
- data/active_interaction-extras.gemspec +6 -5
- data/lib/active_interaction/extras/active_job.rb +36 -5
- data/lib/active_interaction/extras/add_to_batch.rb +13 -0
- data/lib/active_interaction/extras/after_initialize.rb +20 -0
- data/lib/active_interaction/extras/all.rb +16 -1
- data/lib/active_interaction/extras/all_runner.rb +91 -0
- data/lib/active_interaction/extras/append_inputs.rb +60 -0
- data/lib/active_interaction/extras/current.rb +23 -0
- data/lib/active_interaction/extras/file_blobs.rb +80 -0
- data/lib/active_interaction/extras/filter_alias.rb +28 -2
- data/lib/active_interaction/extras/filter_extensions.rb +8 -0
- data/lib/active_interaction/extras/filters/ar_relation_filter.rb +22 -0
- data/lib/active_interaction/extras/filters/uuid_filter.rb +1 -1
- data/lib/active_interaction/extras/form_for.rb +33 -0
- data/lib/active_interaction/extras/good_job.rb +45 -0
- data/lib/active_interaction/extras/include_errors.rb +10 -0
- data/lib/active_interaction/extras/initialize_with.rb +16 -0
- data/lib/active_interaction/extras/instance_runnable.rb +35 -0
- data/lib/active_interaction/extras/model_fields.rb +68 -33
- data/lib/active_interaction/extras/model_names.rb +20 -0
- data/lib/active_interaction/extras/named_callbacks.rb +54 -0
- data/lib/active_interaction/extras/nested_attributes.rb +126 -0
- data/lib/active_interaction/extras/strong_params.rb +48 -10
- data/lib/active_interaction/extras/transaction.rb +27 -5
- data/lib/active_interaction/extras/version.rb +1 -1
- data/lib/active_interaction/extras.rb +10 -23
- metadata +41 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0475c2d5f3660b0b545a444835c4cbcac60c5379fb352f43a427e9e3f12a5496
|
|
4
|
+
data.tar.gz: 4a70441a2ee4f40a01ab49bbcf2473007affbf5049db891c8d69a51462321338
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be224738bce179d169ef765122691eecf957a333ce5798094f3d0d31b431fafef253759bc64a1b23f09224e59f576cb0abdb4512b619de5ed326d67281b1d740
|
|
7
|
+
data.tar.gz: f947b78e59b9f047b6ac8fe913cfa9ed90280d4136cf03b9a6731689609e03b03badd94d6d2b30c191d77068a30d75b19756191ace663b665107f9fa415e368d
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -19,10 +19,10 @@ jobs:
|
|
|
19
19
|
runs-on: ubuntu-latest
|
|
20
20
|
strategy:
|
|
21
21
|
matrix:
|
|
22
|
-
ruby-version: ['2
|
|
22
|
+
ruby-version: ['3.2', '3.3', '3.4', '4.0']
|
|
23
23
|
|
|
24
24
|
steps:
|
|
25
|
-
- uses: actions/checkout@
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
26
|
- name: Set up Ruby
|
|
27
27
|
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
|
28
28
|
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
|
@@ -31,5 +31,6 @@ jobs:
|
|
|
31
31
|
with:
|
|
32
32
|
ruby-version: ${{ matrix.ruby-version }}
|
|
33
33
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
34
|
+
cache-version: 3
|
|
34
35
|
- name: Run tests
|
|
35
36
|
run: bundle exec rake
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.0.0] - 2026-10-13
|
|
8
|
+
|
|
9
|
+
- Add the `ar_relation` filter for `ActiveRecord::Relation` inputs without forcing relation loading when checking defaults
|
|
10
|
+
- Extend the `uuid` filter to accept UUID versions 5 and 7.
|
|
11
|
+
- Add Rails-style `NestedAttributes` for single and collection inputs, including `allow_destroy` and `reject_if`. Nested attributes now require explicit `permit:` rules.
|
|
12
|
+
- Extend `StrongParams` to support object filters and custom permit lists for scalar, hash, object, array, and nested-attribute inputs.
|
|
13
|
+
- Add `FormFor` to delegate form identity and routing to a model input, and `ModelNames` to derive route keys for namespaced forms.
|
|
14
|
+
- Add an `AfterInitialize` callback hook plus `InitializeWith` and `AppendInputs` for post-initialization defaults that do not overwrite explicitly supplied inputs; allow `initialize_with` blocks to return `nil` and ensure defaults run after model-field initialization.
|
|
15
|
+
- Add `InstanceRunnable` with one-shot instance execution and `save`/`save!` methods for form-style usage.
|
|
16
|
+
- Add `IncludeErrors` to merge model validation errors into interaction errors and halt when the model is invalid.
|
|
17
|
+
- Extend `ModelFields` with `assign_model_fields`, `assign_model_changes`, `save_model!`, and `save_model_changes!`; improve change detection for new records and plain objects, and preserve custom filter blocks.
|
|
18
|
+
- Add `NamedCallbacks` for before, after, and around filter, validation, and execution callbacks.
|
|
19
|
+
- Extend transaction helpers to allow disabling transactions and add `force_explicit_transactions!` and `list_missing_transaction_definition` for enforcing and auditing transaction choices.
|
|
20
|
+
- Add ActiveJob inline execution and the `Current` context for the running job; avoid redundant job dispatch during GoodJob batch completion.
|
|
21
|
+
- Add GoodJob batch and concurrency support, including current batch/event context, and `AddToBatch` for joining the current batch.
|
|
22
|
+
- Add `AllRunner` to run an interaction for every record in its object scope, synchronously or through GoodJob bulk enqueueing.
|
|
23
|
+
- Add `FileBlobs` helpers for working with ActiveStorage blobs, attachments, and signed IDs in form objects.
|
|
24
|
+
- Refactor extensions for Zeitwerk autoloading; require Ruby 3.0+, ActiveModel and ActiveSupport 7.1+, and Zeitwerk 2+. Update CI to cover Ruby 3.2 through 4.0.
|
|
25
|
+
|
|
26
|
+
## [1.1.0] - 2023-03-08
|
|
27
|
+
|
|
28
|
+
- Add support for active_interaction v5
|
|
29
|
+
- Fix `model_fields` helper
|
|
30
|
+
|
|
7
31
|
## [1.0.4] - 2022-06-08
|
|
8
32
|
|
|
9
33
|
- Improve halt's robustness (https://github.com/antulik/active_interaction-extras/pull/13)
|
data/README.md
CHANGED
|
@@ -9,9 +9,11 @@ This gem contains the collection of useful extensions to [active_interaction](ht
|
|
|
9
9
|
- [Filters](#filters)
|
|
10
10
|
- [Anything](#anything)
|
|
11
11
|
- [UUID](#uuid)
|
|
12
|
+
- [ActiveRecord Relation](#activerecord-relation)
|
|
12
13
|
- [Filter Extensions](#filter-extensions)
|
|
13
14
|
- [Hash: auto strip](#hash-auto-strip)
|
|
14
15
|
- [Object: multiple classes](#object-multiple-classes)
|
|
16
|
+
- [Timezone support](#timezone-support)
|
|
15
17
|
- [Extensions](#extensions)
|
|
16
18
|
- [Filter alias](#filter-alias)
|
|
17
19
|
- [Halt](#halt)
|
|
@@ -19,9 +21,22 @@ This gem contains the collection of useful extensions to [active_interaction](ht
|
|
|
19
21
|
- [RunCallback](#runcallback)
|
|
20
22
|
- [StrongParams](#strongparams)
|
|
21
23
|
- [Transaction](#transaction)
|
|
24
|
+
- [NamedCallbacks](#namedcallbacks)
|
|
25
|
+
- [ModelNames](#modelnames)
|
|
26
|
+
- [FormFor](#formfor)
|
|
27
|
+
- [NestedAttributes](#nestedattributes)
|
|
28
|
+
- [InitializeWith](#initializewith)
|
|
29
|
+
- [AppendInputs](#appendinputs)
|
|
30
|
+
- [IncludeErrors](#includeerrors)
|
|
31
|
+
- [InstanceRunnable](#instancerunnable)
|
|
32
|
+
- [Current](#current)
|
|
33
|
+
- [AddToBatch](#addtobatch)
|
|
34
|
+
- [AllRunner](#allrunner)
|
|
35
|
+
- [FileBlobs](#fileblobs)
|
|
22
36
|
- [Jobs](#jobs)
|
|
23
37
|
- [ActiveJob](#activejob)
|
|
24
38
|
- [Sidekiq](#sidekiq)
|
|
39
|
+
- [GoodJob](#goodjob)
|
|
25
40
|
- [RSpec](#rspec)
|
|
26
41
|
|
|
27
42
|
## Installation
|
|
@@ -61,6 +76,20 @@ class Service < ActiveInteraction::Base
|
|
|
61
76
|
end
|
|
62
77
|
```
|
|
63
78
|
|
|
79
|
+
### ActiveRecord Relation
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
class Service < ActiveInteraction::Base
|
|
83
|
+
ar_relation :users
|
|
84
|
+
|
|
85
|
+
def execute
|
|
86
|
+
users.where(active: true)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
Service.run!(users: User.all) # accepts any ActiveRecord::Relation
|
|
91
|
+
```
|
|
92
|
+
|
|
64
93
|
## Filter Extensions
|
|
65
94
|
|
|
66
95
|
You can load all filter extensions with:
|
|
@@ -96,6 +125,27 @@ class Service < ActiveInteraction::Base
|
|
|
96
125
|
end
|
|
97
126
|
```
|
|
98
127
|
|
|
128
|
+
### Timezone support
|
|
129
|
+
|
|
130
|
+
Parses time strings in the configured timezone. Requires a separate require:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
require 'active_interaction/extras/filter_extensions/timezone_support'
|
|
134
|
+
|
|
135
|
+
class Service < ActiveInteraction::Base
|
|
136
|
+
time :epoch
|
|
137
|
+
|
|
138
|
+
def execute
|
|
139
|
+
epoch.iso8601
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
Time.use_zone('Bangkok') do
|
|
144
|
+
Service.run!(epoch: '2000-12-11 10:30')
|
|
145
|
+
# => '2000-12-11T10:30:00+07:00'
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
99
149
|
|
|
100
150
|
## Extensions
|
|
101
151
|
|
|
@@ -237,6 +287,248 @@ UpdateUserForm.run
|
|
|
237
287
|
Comment.count # => 0
|
|
238
288
|
```
|
|
239
289
|
|
|
290
|
+
### NamedCallbacks
|
|
291
|
+
|
|
292
|
+
Provides friendly method names for ActiveInteraction's internal callbacks.
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
class Service < ActiveInteraction::Base
|
|
296
|
+
include ActiveInteraction::Extras::NamedCallbacks
|
|
297
|
+
|
|
298
|
+
before_filter :normalize_inputs
|
|
299
|
+
after_validate :log_validation
|
|
300
|
+
before_execute :setup_context
|
|
301
|
+
after_execute :send_notification
|
|
302
|
+
|
|
303
|
+
def execute
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### ModelNames
|
|
309
|
+
|
|
310
|
+
Note: This module is currently NOT included in `ActiveInteraction::Extras::All`.
|
|
311
|
+
Sets up automatic ActiveModel naming for forms based on their class name for URL helpers.
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
class ApplicationForm < ActiveInteraction::Base
|
|
315
|
+
include ActiveInteraction::Extras::ModelNames
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
class User::SignUpForm < ApplicationForm
|
|
319
|
+
# route_key is auto-set to :user_sign_up_forms
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
class User::UpdateForm < ApplicationForm
|
|
323
|
+
singular_resource_route_key! # route_key becomes :user_update_form
|
|
324
|
+
end
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### FormFor
|
|
328
|
+
|
|
329
|
+
Makes form objects work with Rails URL helpers by delegating routing to a model field.
|
|
330
|
+
|
|
331
|
+
```ruby
|
|
332
|
+
class User::Form < ActiveInteraction::Base
|
|
333
|
+
include ActiveInteraction::Extras::FormFor
|
|
334
|
+
|
|
335
|
+
object :user
|
|
336
|
+
form_for :user # delegates routing to user
|
|
337
|
+
|
|
338
|
+
# url_for(form) => "/users/1"
|
|
339
|
+
end
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### NestedAttributes
|
|
343
|
+
|
|
344
|
+
Rails-style `accepts_nested_attributes_for` for interactions.
|
|
345
|
+
|
|
346
|
+
```ruby
|
|
347
|
+
class Form < ActiveInteraction::Base
|
|
348
|
+
include ActiveInteraction::Extras::NestedAttributes
|
|
349
|
+
|
|
350
|
+
array :items
|
|
351
|
+
|
|
352
|
+
accepts_nested_attributes_for :items,
|
|
353
|
+
permit: [:name, :quantity]
|
|
354
|
+
|
|
355
|
+
def execute
|
|
356
|
+
# items_attributes automatically mapped to items
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### InitializeWith
|
|
362
|
+
|
|
363
|
+
Lazily sets input values after initialization, only for inputs not explicitly given.
|
|
364
|
+
|
|
365
|
+
```ruby
|
|
366
|
+
class Service < ActiveInteraction::Base
|
|
367
|
+
include ActiveInteraction::Extras::InitializeWith
|
|
368
|
+
|
|
369
|
+
anything :current_user
|
|
370
|
+
|
|
371
|
+
initialize_with do
|
|
372
|
+
{ current_user: User.current }
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def execute
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
Service.run! # current_user => User.current
|
|
380
|
+
Service.run!(current_user: other_user) # current_user => other_user
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### AppendInputs
|
|
384
|
+
|
|
385
|
+
Set input defaults after initialization without overwriting explicitly given inputs.
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
class Service < ActiveInteraction::Base
|
|
389
|
+
include ActiveInteraction::Extras::AppendInputs
|
|
390
|
+
|
|
391
|
+
string :tenant_name
|
|
392
|
+
|
|
393
|
+
default_inputs do
|
|
394
|
+
{ tenant_name: Current.tenant.name }
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Or single default:
|
|
398
|
+
default_input :tenant_name do
|
|
399
|
+
Current.tenant.name
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### IncludeErrors
|
|
405
|
+
|
|
406
|
+
Merges a model's errors into the interaction's errors and halts if invalid.
|
|
407
|
+
|
|
408
|
+
```ruby
|
|
409
|
+
class Service < ActiveInteraction::Base
|
|
410
|
+
include ActiveInteraction::Extras::IncludeErrors
|
|
411
|
+
|
|
412
|
+
def execute
|
|
413
|
+
model = User.new(params)
|
|
414
|
+
model.save
|
|
415
|
+
include_errors!(model) # merges model errors and halts if invalid
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### InstanceRunnable
|
|
421
|
+
|
|
422
|
+
Allows running interactions from an already-instantiated object. Useful for controller-style usage with `save`/`save!` aliases.
|
|
423
|
+
|
|
424
|
+
```ruby
|
|
425
|
+
class Form < ActiveInteraction::Base
|
|
426
|
+
include ActiveInteraction::Extras::InstanceRunnable
|
|
427
|
+
|
|
428
|
+
string :name
|
|
429
|
+
|
|
430
|
+
def execute
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Controller-style:
|
|
435
|
+
def create
|
|
436
|
+
if form.save
|
|
437
|
+
redirect_to record_path
|
|
438
|
+
else
|
|
439
|
+
render :new
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def form
|
|
444
|
+
@form ||= Form.new(params:)
|
|
445
|
+
end
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Current
|
|
449
|
+
|
|
450
|
+
Provides `ActiveSupport::CurrentAttributes` context accessible via `current`.
|
|
451
|
+
|
|
452
|
+
```ruby
|
|
453
|
+
class Service < ActiveInteraction::Base
|
|
454
|
+
include ActiveInteraction::Extras::Current
|
|
455
|
+
|
|
456
|
+
def execute
|
|
457
|
+
current.job # the current job (if running in background)
|
|
458
|
+
current.batch # the current GoodJob batch (if applicable)
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### AddToBatch
|
|
464
|
+
|
|
465
|
+
Conditionally runs a block within a GoodJob batch if currently in a batch context.
|
|
466
|
+
|
|
467
|
+
```ruby
|
|
468
|
+
class Service < ActiveInteraction::Base
|
|
469
|
+
include ActiveInteraction::Extras::AddToBatch
|
|
470
|
+
|
|
471
|
+
def execute
|
|
472
|
+
add_to_batch do
|
|
473
|
+
SomeOtherService.delay.run(some_param: value)
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### AllRunner
|
|
480
|
+
|
|
481
|
+
Runs the service for every record in the default scope of the object filter.
|
|
482
|
+
|
|
483
|
+
```ruby
|
|
484
|
+
class NotifyUsers < ActiveInteraction::Base
|
|
485
|
+
include ActiveInteraction::Extras::AllRunner
|
|
486
|
+
|
|
487
|
+
object :user
|
|
488
|
+
|
|
489
|
+
def execute
|
|
490
|
+
# process each user
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Run synchronously for all users:
|
|
495
|
+
NotifyUsers.all.run!
|
|
496
|
+
|
|
497
|
+
# Run async via GoodJob batches:
|
|
498
|
+
NotifyUsers.all.delay.run!
|
|
499
|
+
|
|
500
|
+
# With custom scope:
|
|
501
|
+
NotifyUsers.all(user: User.active).delay(wait: 5.minutes).run!
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### FileBlobs
|
|
505
|
+
|
|
506
|
+
Helper for working with ActiveStorage attachments in form objects.
|
|
507
|
+
|
|
508
|
+
```ruby
|
|
509
|
+
class Doc::Form < ActiveInteraction::Base
|
|
510
|
+
include ActiveInteraction::Extras::FileBlobs
|
|
511
|
+
|
|
512
|
+
object :doc
|
|
513
|
+
|
|
514
|
+
model_fields(:doc) do
|
|
515
|
+
array :files
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def execute
|
|
519
|
+
save_model!(:doc)
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
```slim
|
|
525
|
+
/ _form.html.slim
|
|
526
|
+
= form.blobs_for(:files).each do |blob|
|
|
527
|
+
= f.hidden_field :files, value: blob.signed_id
|
|
528
|
+
|
|
529
|
+
= f.file_field :files, multiple: true, direct_upload: true
|
|
530
|
+
```
|
|
531
|
+
|
|
240
532
|
## Jobs
|
|
241
533
|
|
|
242
534
|
You no longer need to create a separate Job class for the each interaction. This Job extension automatically converts interactions to background jobs. By convention each interaction will have a nested `Job` class which will be inherited from the parent interaction `Job` class (e.g. `ApplicationInteraction::Job`).
|
|
@@ -339,6 +631,39 @@ DoubleService.run # => RuntimeError
|
|
|
339
631
|
DoubleService.delay.perform_now(x: 2) # => returns 2
|
|
340
632
|
```
|
|
341
633
|
|
|
634
|
+
### GoodJob
|
|
635
|
+
|
|
636
|
+
Extends the ActiveJob integration with GoodJob batch support and concurrency keys.
|
|
637
|
+
|
|
638
|
+
```ruby
|
|
639
|
+
class ApplicationInteraction < ActiveInteraction::Base
|
|
640
|
+
include ActiveInteraction::Extras::GoodJob
|
|
641
|
+
|
|
642
|
+
class Job < ActiveJob::Base
|
|
643
|
+
include GoodJob::ActiveJobExtensions::Concurrency
|
|
644
|
+
include ActiveInteraction::Extras::GoodJob::Perform
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
class ProcessUser < ApplicationInteraction
|
|
649
|
+
object :user
|
|
650
|
+
def execute
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
class ProcessUserFinished < ApplicationInteraction
|
|
655
|
+
def execute
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# Create and enqueue a GoodJob batch:
|
|
660
|
+
batch = ProcessUserFinished.batch_job
|
|
661
|
+
batch.add do
|
|
662
|
+
ProcessUser.delay.run!(user: User.last)
|
|
663
|
+
end
|
|
664
|
+
batch.enqueue
|
|
665
|
+
```
|
|
666
|
+
|
|
342
667
|
## Rspec
|
|
343
668
|
|
|
344
669
|
```ruby
|
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
"changelog_uri" => "https://github.com/antulik/active_interaction-extras/blob/master/CHANGELOG.md",
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
spec.required_ruby_version = '>=
|
|
20
|
+
spec.required_ruby_version = '>= 3.0'
|
|
21
21
|
|
|
22
22
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
23
23
|
f.match(%r{^(test|spec|features)/})
|
|
@@ -27,14 +27,15 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
spec.require_paths = ["lib"]
|
|
28
28
|
|
|
29
29
|
spec.add_dependency "active_interaction", ">= 5.0", "< 6.0"
|
|
30
|
-
spec.add_dependency "activemodel", ">=
|
|
31
|
-
spec.add_dependency "activesupport", ">=
|
|
30
|
+
spec.add_dependency "activemodel", ">= 7.1"
|
|
31
|
+
spec.add_dependency "activesupport", ">= 7.1"
|
|
32
|
+
spec.add_dependency "zeitwerk", ">= 2"
|
|
32
33
|
|
|
33
|
-
spec.add_development_dependency "bundler", "
|
|
34
|
+
spec.add_development_dependency "bundler", ">= 2.2"
|
|
34
35
|
spec.add_development_dependency "rake", ">= 12.3.3"
|
|
35
36
|
spec.add_development_dependency "rspec", "~> 3.7"
|
|
36
37
|
spec.add_development_dependency "pry"
|
|
37
|
-
spec.add_development_dependency "sqlite3"
|
|
38
|
+
spec.add_development_dependency "sqlite3", ">= 2"
|
|
38
39
|
spec.add_development_dependency "activerecord"
|
|
39
40
|
spec.add_development_dependency "activejob"
|
|
40
41
|
spec.add_development_dependency "actionpack"
|
|
@@ -2,34 +2,65 @@ require 'active_job'
|
|
|
2
2
|
|
|
3
3
|
module ActiveInteraction::Extras::ActiveJob
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
|
-
|
|
6
5
|
include ActiveInteraction::Extras::Jobs::Core
|
|
7
6
|
|
|
8
7
|
class_methods do
|
|
9
8
|
def configured_job_class
|
|
10
9
|
ConfiguredJob
|
|
11
10
|
end
|
|
11
|
+
|
|
12
|
+
def perform_now(&block)
|
|
13
|
+
job_class.perform_now(&block)
|
|
14
|
+
end
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
module Perform
|
|
15
18
|
extend ActiveSupport::Concern
|
|
16
19
|
|
|
17
20
|
def perform(*args)
|
|
21
|
+
ActiveInteraction::Extras::Current::CurrentContext.set(job: self) do
|
|
22
|
+
parent_class.run!(*args)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parent_class
|
|
18
27
|
if self.class.respond_to?(:module_parent)
|
|
19
|
-
self.class.module_parent
|
|
28
|
+
self.class.module_parent
|
|
20
29
|
else
|
|
21
|
-
self.class.parent
|
|
30
|
+
self.class.parent
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class_methods do
|
|
35
|
+
def perform_now
|
|
36
|
+
Thread.current[name] ||= []
|
|
37
|
+
Thread.current[name].push true
|
|
38
|
+
yield
|
|
39
|
+
ensure
|
|
40
|
+
if Thread.current[name].size == 1
|
|
41
|
+
Thread.current[name] = nil
|
|
42
|
+
else
|
|
43
|
+
Thread.current[name].pop
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def perform_now?
|
|
48
|
+
Thread.current[name]
|
|
22
49
|
end
|
|
23
50
|
end
|
|
24
51
|
end
|
|
25
52
|
|
|
26
53
|
class ConfiguredJob < ::ActiveJob::ConfiguredJob
|
|
27
54
|
def run(*args)
|
|
28
|
-
|
|
55
|
+
run!(*args)
|
|
29
56
|
end
|
|
30
57
|
|
|
31
58
|
def run!(*args)
|
|
32
|
-
|
|
59
|
+
if @job_class.perform_now?
|
|
60
|
+
perform_now(*args)
|
|
61
|
+
else
|
|
62
|
+
perform_later(*args)
|
|
63
|
+
end
|
|
33
64
|
end
|
|
34
65
|
end
|
|
35
66
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module ActiveInteraction::Extras::AfterInitialize
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
include ActiveSupport::Callbacks
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
define_callbacks :initialize
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class_methods do
|
|
11
|
+
def after_initialize(...)
|
|
12
|
+
set_callback(:initialize, :after, ...)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(...)
|
|
17
|
+
super
|
|
18
|
+
run_callbacks :initialize, :after
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -2,11 +2,26 @@ module ActiveInteraction::Extras::All
|
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
4
|
# order dependant, include first so around callback includes other modules
|
|
5
|
+
include ActiveInteraction::Extras::NamedCallbacks
|
|
6
|
+
include ActiveInteraction::Extras::AfterInitialize
|
|
5
7
|
include ActiveInteraction::Extras::Transaction
|
|
6
8
|
|
|
7
9
|
include ActiveInteraction::Extras::FilterAlias
|
|
8
10
|
include ActiveInteraction::Extras::Halt
|
|
9
11
|
include ActiveInteraction::Extras::ModelFields
|
|
10
|
-
include ActiveInteraction::Extras::
|
|
12
|
+
include ActiveInteraction::Extras::NestedAttributes
|
|
13
|
+
|
|
11
14
|
include ActiveInteraction::Extras::StrongParams
|
|
15
|
+
include ActiveInteraction::Extras::Current
|
|
16
|
+
|
|
17
|
+
include ActiveInteraction::Extras::AddToBatch
|
|
18
|
+
include ActiveInteraction::Extras::FormFor
|
|
19
|
+
include ActiveInteraction::Extras::InstanceRunnable
|
|
20
|
+
include ActiveInteraction::Extras::InitializeWith
|
|
21
|
+
include ActiveInteraction::Extras::IncludeErrors
|
|
22
|
+
include ActiveInteraction::Extras::AllRunner
|
|
23
|
+
include ActiveInteraction::Extras::AppendInputs
|
|
24
|
+
include ActiveInteraction::Extras::FileBlobs
|
|
25
|
+
|
|
26
|
+
include ActiveInteraction::Extras::RunCallback
|
|
12
27
|
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module ActiveInteraction::Extras::AllRunner
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
class AllExecution
|
|
5
|
+
attr_reader :klass, :params
|
|
6
|
+
|
|
7
|
+
def initialize(klass:, params:)
|
|
8
|
+
@klass = klass
|
|
9
|
+
@params = params
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run(opts = {})
|
|
13
|
+
execute(opts) { klass.run(_1) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run!(opts = {})
|
|
17
|
+
execute(opts) { klass.run!(_1) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def delay(**delay_opts)
|
|
21
|
+
DelayedExecution.new(klass:, params:, delay_opts:)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def noop
|
|
27
|
+
yield
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def default_params
|
|
31
|
+
object_filters = klass.filters
|
|
32
|
+
.reject { _2.default? }
|
|
33
|
+
.select { _2.is_a?(ActiveInteraction::ObjectFilter) }
|
|
34
|
+
raise "only one object input allowed" if object_filters.size > 1
|
|
35
|
+
name, _ = object_filters.first
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
name => name.to_s.classify.constantize.all,
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def execute(opts, around: method(:noop))
|
|
43
|
+
params = self.params.dup.reverse_merge(default_params)
|
|
44
|
+
|
|
45
|
+
name, scope = params.find { _2.is_a?(ActiveRecord::Relation) }
|
|
46
|
+
params.delete(name)
|
|
47
|
+
|
|
48
|
+
scope.find_in_batches do |batch|
|
|
49
|
+
around.call do
|
|
50
|
+
batch.each do |record|
|
|
51
|
+
yield(opts.merge(name => record, **params))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class DelayedExecution < AllExecution
|
|
59
|
+
attr_reader :delay_opts
|
|
60
|
+
def initialize(delay_opts: {}, **kw)
|
|
61
|
+
super(**kw)
|
|
62
|
+
@delay_opts = delay_opts
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def run(opts = {})
|
|
66
|
+
require 'good_job'
|
|
67
|
+
around = ::GoodJob::Bulk.method(:enqueue)
|
|
68
|
+
execute(opts, around:) { klass.delay(delay_opts).run(_1) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def run!(opts = {})
|
|
72
|
+
run(opts)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class_methods do
|
|
77
|
+
# class Service < ActiveInteraction::Base
|
|
78
|
+
# object :user
|
|
79
|
+
# def execute; end
|
|
80
|
+
# end
|
|
81
|
+
#
|
|
82
|
+
# @example
|
|
83
|
+
# Service.all.run!
|
|
84
|
+
# Service.all.run
|
|
85
|
+
# Service.all.delay.run!
|
|
86
|
+
# Service.all(user: User.all).delay(wait: 5.minutes).run!
|
|
87
|
+
def all(params = {})
|
|
88
|
+
AllExecution.new(klass: self, params:)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|