standard_procedure_operations 0.4.3 → 0.5.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/README.md +18 -5
- data/app/models/concerns/operations/participant.rb +17 -0
- data/app/models/operations/task.rb +18 -0
- data/app/models/operations/task_participant.rb +12 -0
- data/db/migrate/20250309_create_operations_task_participants.rb +15 -0
- data/lib/operations/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a14742a79230259d8cb55462cac7682b6adfe8993cacc0ec4f35ed3832910858
|
4
|
+
data.tar.gz: 939c54f7cb755794ecd71ee920bfc1ff65f8e925187f6ccd9c1e98515d5a1b3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c3ec1e16dada2b58d48aa72a11e64aaa7701b7a481836cc9b35ac268f752257b9dabf619eb43ecc6e4541fbc6ed50ae1b51d6ee4b4ed60fe4ad2ec302b1f8b8
|
7
|
+
data.tar.gz: 67d54e8449c264dc2fb1dd4883b64a810cabd47a2b976e931f1060d1573d0579c4964336159f6f8acf1d6d599ae3edf4de64b818f08c4c46c963035534264500
|
data/README.md
CHANGED
@@ -290,7 +290,24 @@ Instead of using the standard [JSON coder](https://api.rubyonrails.org/v4.2/clas
|
|
290
290
|
|
291
291
|
If the original database record was deleted between the time the hash was serialised and when it was retrieved, the `GlobalID::Locator` will fail. With ActiveJob, this means that the job cannot run and is discarded. For Operations, we attempt to deserialise a second time, returning the GlobalID string instead of the model. So be aware that when you access `data` or `results` you may receive a string (similar to `"gid://test-app/User/1"`) instead of the models you were expecting. And the error handling deserialiser is very simple so you may get format changes in some of the data as well. If serialisation fails you can access the original JSON string as `data.raw_data` or `results[:raw_data]`.
|
292
292
|
|
293
|
-
|
293
|
+
#### Indexing data and results
|
294
|
+
|
295
|
+
If you need to search through existing tasks by a model that is stored in the `data` or `results` fields - for example, you might want to list all operations that were started by a particular `User` - the models can be indexed alongside the task.
|
296
|
+
|
297
|
+
If your ActiveRecord model (in this example, `User`) includes the `Operations::Participant` module, it will be linked with any task that references that model. A polymorphic join table, `operations_task_participants` is used for this. Whenever a task is saved, any `Operations::Participant` records are located in the `data` and `results` collections and a `Operations::TaskParticipant` record created to join the model to the task. The `context` attribute records whether the association is in the `data` or `results` collection and the `role` attribute is the name of the hash key.
|
298
|
+
|
299
|
+
For example, you create your task as:
|
300
|
+
```ruby
|
301
|
+
@alice = User.find 123
|
302
|
+
@task = DoSomethingImportant.call user: @alice
|
303
|
+
```
|
304
|
+
There will not be a `TaskParticipant` record with a `context` of "data", `role` of "user" and `participant` of `@alice`.
|
305
|
+
|
306
|
+
Likewise, you can see all the tasks that Alice was involved with using:
|
307
|
+
```ruby
|
308
|
+
@alice.involved_in_operations_as("user") # => collection of tasks where Alice was a "user" in the "data" collection
|
309
|
+
@alice.involved_in_operations_as("user", context: "results") # => collection of tasks where Alice was a "user" in the "results" collection
|
310
|
+
```
|
294
311
|
|
295
312
|
### Failures and exceptions
|
296
313
|
If any handlers raise an exception, the task will be terminated. It will be marked as `failed?` and the `results` hash will contain `results[:failure_message]`, `results[:exception_class]` and `results[:exception_backtrace]` for the exception's message, class name and backtrace respectively.
|
@@ -626,12 +643,8 @@ The gem is available as open source under the terms of the [LGPL License](/LICEN
|
|
626
643
|
- [x] Simplify calling sub-tasks (and testing them)
|
627
644
|
- [ ] Figure out how to stub calling sub-tasks with known results data
|
628
645
|
- [ ] Figure out how to test the parameters passed to sub-tasks when they are called
|
629
|
-
- [ ] Split out the state-management definition stuff from the task class (so you can use it without subclassing Operations::Task)
|
630
646
|
- [x] Make Operations::Task work in the background using ActiveJob
|
631
647
|
- [x] Add pause/resume capabilities (for example, when a task needs to wait for user input)
|
632
648
|
- [x] Add wait for sub-tasks capabilities
|
633
649
|
- [x] Add GraphViz visualization export for task flows
|
634
|
-
- [ ] Add ActiveModel validations support for task parameters
|
635
650
|
- [ ] Option to change background job queue and priority settings
|
636
|
-
- [ ] Replace the ActiveJob::Arguments deserialiser with the [transporter](https://github.com/standard-procedure/plumbing/blob/main/lib/plumbing/actor/transporter.rb) from [plumbing](https://github.com/standard-procedure/plumbing)
|
637
|
-
- [ ] Maybe? Split this out into two gems - one defining an Operation (pure ruby) and another defining the Task (using ActiveJob as part of a Rails Engine)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Operations
|
2
|
+
module Participant
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_many :operations_task_participants, class_name: "Operations::TaskParticipant", as: :participant, dependent: :destroy
|
7
|
+
has_many :operations_tasks, class_name: "Operations::Task", through: :operations_task_participants, source: :task
|
8
|
+
|
9
|
+
scope :involved_in_operation_as, ->(role:, context: "data") do
|
10
|
+
joins(:operations_task_participants).tap do |scope|
|
11
|
+
scope.where(operations_task_participants: {role: role}) if role
|
12
|
+
scope.where(operations_task_participants: {context: context}) if context
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -8,9 +8,13 @@ module Operations
|
|
8
8
|
extend InputValidation
|
9
9
|
|
10
10
|
enum :status, in_progress: 0, waiting: 10, completed: 100, failed: -1
|
11
|
+
|
11
12
|
serialize :data, coder: Operations::GlobalIDSerialiser, type: Hash, default: {}
|
12
13
|
serialize :results, coder: Operations::GlobalIDSerialiser, type: Hash, default: {}
|
13
14
|
|
15
|
+
has_many :task_participants, class_name: "Operations::TaskParticipant", dependent: :destroy
|
16
|
+
after_save :record_participants
|
17
|
+
|
14
18
|
def call sub_task_class, **data, &result_handler
|
15
19
|
sub_task = sub_task_class.call(**data)
|
16
20
|
result_handler&.call(sub_task.results)
|
@@ -61,6 +65,20 @@ module Operations
|
|
61
65
|
|
62
66
|
private def carrier_for(data) = data.is_a?(DataCarrier) ? data : DataCarrier.new(data.merge(task: self))
|
63
67
|
|
68
|
+
private def record_participants
|
69
|
+
record_participants_in :data, data.select { |key, value| value.is_a? Participant }
|
70
|
+
record_participants_in :results, results.select { |key, value| value.is_a? Participant }
|
71
|
+
end
|
72
|
+
|
73
|
+
private def record_participants_in context, participants
|
74
|
+
task_participants.where(context: context).where.not(role: participants.keys).delete_all
|
75
|
+
participants.each do |role, participant|
|
76
|
+
task_participants.where(context: context, role: role).first_or_initialize.tap do |task_participant|
|
77
|
+
task_participant.update! participant: participant
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
64
82
|
def self.build(background:, **data)
|
65
83
|
validate_inputs! data
|
66
84
|
create!(state: initial_state, status: background ? "waiting" : "in_progress", data: data, status_message: "", background: background)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Operations
|
2
|
+
class TaskParticipant < ApplicationRecord
|
3
|
+
belongs_to :task
|
4
|
+
belongs_to :participant, polymorphic: true
|
5
|
+
|
6
|
+
validates :role, presence: true
|
7
|
+
validates :context, presence: true
|
8
|
+
validates :task_id, uniqueness: {scope: [:participant_type, :participant_id, :role, :context]}
|
9
|
+
|
10
|
+
scope :in, ->(context) { where(context: context) }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateOperationsTaskParticipants < ActiveRecord::Migration[7.1]
|
2
|
+
def change
|
3
|
+
create_table :operations_task_participants do |t|
|
4
|
+
t.references :task, null: false, foreign_key: {to_table: :operations_tasks}
|
5
|
+
t.references :participant, polymorphic: true, null: false
|
6
|
+
t.string :role, null: false
|
7
|
+
t.string :context, null: false, default: "data"
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
|
11
|
+
add_index :operations_task_participants, [:task_id, :participant_type, :participant_id, :role, :context],
|
12
|
+
name: "index_operations_task_participants_on_full_identity",
|
13
|
+
unique: true
|
14
|
+
end
|
15
|
+
end
|
data/lib/operations/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: standard_procedure_operations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rahoul Baruah
|
@@ -35,6 +35,7 @@ files:
|
|
35
35
|
- Rakefile
|
36
36
|
- app/jobs/operations/application_job.rb
|
37
37
|
- app/jobs/operations/task_runner_job.rb
|
38
|
+
- app/models/concerns/operations/participant.rb
|
38
39
|
- app/models/operations/task.rb
|
39
40
|
- app/models/operations/task/background.rb
|
40
41
|
- app/models/operations/task/data_carrier.rb
|
@@ -47,8 +48,10 @@ files:
|
|
47
48
|
- app/models/operations/task/state_management/decision_handler.rb
|
48
49
|
- app/models/operations/task/state_management/wait_handler.rb
|
49
50
|
- app/models/operations/task/testing.rb
|
51
|
+
- app/models/operations/task_participant.rb
|
50
52
|
- config/routes.rb
|
51
53
|
- db/migrate/20250127160616_create_operations_tasks.rb
|
54
|
+
- db/migrate/20250309_create_operations_task_participants.rb
|
52
55
|
- lib/operations.rb
|
53
56
|
- lib/operations/cannot_wait_in_foreground.rb
|
54
57
|
- lib/operations/engine.rb
|