standard_procedure_operations 0.1.0 → 0.1.2
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 +9 -12
- data/app/models/operations/task/state_management.rb +9 -1
- data/app/models/operations/task.rb +6 -1
- data/db/migrate/20250127160616_create_operations_tasks.rb +3 -3
- data/lib/operations/missing_inputs_error.rb +2 -0
- data/lib/operations/version.rb +1 -1
- data/lib/operations.rb +3 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d0da11583bd5edf6619553813c302fbe675ce5563921e9960149d79888130c5
|
4
|
+
data.tar.gz: aefecc4a15ed64289822318feb10c68128c121a359e049ede7f8a2805ca6405c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 998bca6b52931b7048585a22ab6cc53a53a9a872290bd6243a84bed7442c57209a2ea71f0502b744aeaa8b65461e9e7d1aca22321a3811fdac7f7a7d156e15e1
|
7
|
+
data.tar.gz: 474207de30dbe046bd59587f1dfe0fa5f14f7a13d2f72075838e6594d8533c1c9b8ab79e245eafecaf490782329f220fe5dc0f9546e194138c0dafedf0544ef3
|
data/README.md
CHANGED
@@ -44,6 +44,7 @@ Here's how this would be represented using Operations.
|
|
44
44
|
|
45
45
|
```ruby
|
46
46
|
class PrepareDocumentForDownload < Operations::Task
|
47
|
+
inputs :user, :document, :use_filename_scrambler
|
47
48
|
starts_with :authorised?
|
48
49
|
|
49
50
|
decision :authorised? do
|
@@ -78,6 +79,8 @@ end
|
|
78
79
|
|
79
80
|
The five states are represented as three [decision](#decisions) handlers, one [action](#actions) handler and a [result](#results) handler.
|
80
81
|
|
82
|
+
The task also declares that it requires a `user`, `document` and `use_filename_scrambler` parameter to be provided, and also declares its initial state - `authorised?`.
|
83
|
+
|
81
84
|
### Decisions
|
82
85
|
A decision handler evaluates a condition, then changes state depending upon if the result is true or false.
|
83
86
|
|
@@ -194,14 +197,12 @@ Handlers can alternatively be implemented as methods on the task itself. This m
|
|
194
197
|
The final `results` data from any `result` handlers is stored, along with the task, in the database, so it can be examined later. It is accessed as an OpenStruct that is encoded into JSON. But any ActiveRecord models are translated using a [GlobalID](https://github.com/rails/globalid) using [ActiveJob::Arguments](https://guides.rubyonrails.org/active_job_basics.html#supported-types-for-arguments). Be aware that if you do store an ActiveRecord model into your `results` and that model is later deleted from the database, your task's `results` will be unavailable, as the `GlobalID::Locator` will fail when it tries to load the record. The data is not lost though - if the deserialisation fails, the routine will return the JSON string as `results.raw_data`.
|
195
198
|
|
196
199
|
### Failures and exceptions
|
197
|
-
|
198
|
-
If any handlers raise an exception, the task will be terminated. It will be marked as `failed?` and the `results` hash will contain `results.exception_message`, `results.exception_class` and `results.exception_backtrace` for the exception's message, class name and backtrace respectively.
|
200
|
+
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.
|
199
201
|
|
200
202
|
You can also stop a task at any point by calling `fail_with message`. This will mark the task as `failed?` and the `reeults` has will contain `results.failure_message`.
|
201
203
|
|
202
204
|
### Task life-cycle and the database
|
203
|
-
|
204
|
-
There is an ActiveRecord migration that creates the `operations_tasks` table. Use `bin/rails app:operations:install:migrations` to copy it to your application.
|
205
|
+
There is an ActiveRecord migration that creates the `operations_tasks` table. Use `bin/rails operations:install:migrations` to copy it to your application, then run `bin/rails db:migrate` to add the table to your application's database.
|
205
206
|
|
206
207
|
When you `call` a task, it is written to the database. Then whenever a state transition occurs, the task record is updated.
|
207
208
|
|
@@ -226,21 +227,17 @@ Coming soon.
|
|
226
227
|
Coming soon.
|
227
228
|
|
228
229
|
## Installation
|
229
|
-
Add
|
230
|
+
Add the gem to your Rails application's Gemfile:
|
230
231
|
|
231
232
|
```ruby
|
232
233
|
gem "standard_procedure_operations"
|
233
234
|
```
|
234
|
-
|
235
|
-
Run `bundle install`, copy and run the migrations to add the tasks table to your database:
|
236
|
-
|
235
|
+
Run `bundle install`, then copy and run the migrations to add the tasks table to your database:
|
237
236
|
```sh
|
238
|
-
bin/rails
|
237
|
+
bin/rails operations:install:migrations
|
239
238
|
bin/rails db:migrate
|
240
239
|
```
|
241
|
-
|
242
|
-
Then create your own operations by inheriting from `Operations::Task`.
|
243
|
-
|
240
|
+
Create your own operations by inheriting from `Operations::Task` and revel in the stateful flowcharts!
|
244
241
|
```ruby
|
245
242
|
class DailyLife < Operations::Task
|
246
243
|
starts_with :am_i_awake?
|
@@ -7,6 +7,10 @@ module Operations::Task::StateManagement
|
|
7
7
|
end
|
8
8
|
|
9
9
|
class_methods do
|
10
|
+
def inputs(*names) = @required_inputs = names.map(&:to_sym)
|
11
|
+
|
12
|
+
def required_inputs = @required_inputs ||= []
|
13
|
+
|
10
14
|
def starts_with(value) = @initial_state = value.to_sym
|
11
15
|
|
12
16
|
def initial_state = @initial_state
|
@@ -20,13 +24,17 @@ module Operations::Task::StateManagement
|
|
20
24
|
def state_handlers = @state_handlers ||= {}
|
21
25
|
|
22
26
|
def handler_for(state) = state_handlers[state.to_sym]
|
27
|
+
|
28
|
+
def required_inputs_are_present_in?(data) = missing_inputs_from(data).empty?
|
29
|
+
|
30
|
+
def missing_inputs_from(data) = (required_inputs - data.keys.map(&:to_sym))
|
23
31
|
end
|
24
32
|
|
25
33
|
private def handler_for(state) = self.class.handler_for(state.to_sym)
|
26
34
|
private def process_current_state(data)
|
27
35
|
handler_for(state).call(self, data)
|
28
36
|
rescue => ex
|
29
|
-
update! status: "failed", results: OpenStruct.new(
|
37
|
+
update! status: "failed", results: OpenStruct.new(failure_message: ex.message, exception_class: ex.class.name, exception_backtrace: ex.backtrace)
|
30
38
|
end
|
31
39
|
private def state_is_valid
|
32
40
|
errors.add :state, :invalid if state.blank? || handler_for(state.to_sym).nil?
|
@@ -6,7 +6,12 @@ module Operations
|
|
6
6
|
composed_of :results, class_name: "OpenStruct", constructor: ->(results) { results.to_h }, converter: ->(hash) { OpenStruct.new(hash) }
|
7
7
|
serialize :results, coder: Operations::GlobalIDSerialiser, type: Hash, default: {}
|
8
8
|
|
9
|
-
def self.call(data = {})
|
9
|
+
def self.call(data = {})
|
10
|
+
raise MissingInputsError, "Missing inputs: #{missing_inputs_from(data).join(", ")}" unless required_inputs_are_present_in?(data)
|
11
|
+
create!(state: initial_state).tap do |task|
|
12
|
+
task.send(:process_current_state, DataCarrier.new(data.merge(task: task)))
|
13
|
+
end
|
14
|
+
end
|
10
15
|
|
11
16
|
def go_to(state, data = {}, message = nil)
|
12
17
|
update!(state: state, status_message: message || state.to_s)
|
@@ -1,12 +1,12 @@
|
|
1
|
-
class CreateOperationsTasks < ActiveRecord::Migration[
|
1
|
+
class CreateOperationsTasks < ActiveRecord::Migration[7.1]
|
2
2
|
def change
|
3
3
|
create_table :operations_tasks do |t|
|
4
4
|
t.string :type
|
5
5
|
t.integer :status, default: 0, null: false
|
6
6
|
t.string :state, null: false
|
7
7
|
t.string :status_message, default: "", null: false
|
8
|
-
t.text :data
|
9
|
-
t.text :results
|
8
|
+
t.text :data
|
9
|
+
t.text :results
|
10
10
|
t.boolean :background, default: false, null: false
|
11
11
|
t.datetime :delete_at, null: false, index: true
|
12
12
|
t.timestamps
|
data/lib/operations/version.rb
CHANGED
data/lib/operations.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.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rahoul Baruah
|
@@ -42,6 +42,7 @@ files:
|
|
42
42
|
- lib/operations.rb
|
43
43
|
- lib/operations/engine.rb
|
44
44
|
- lib/operations/global_id_serialiser.rb
|
45
|
+
- lib/operations/missing_inputs_error.rb
|
45
46
|
- lib/operations/version.rb
|
46
47
|
- lib/standard_procedure_operations.rb
|
47
48
|
- lib/tasks/operations_tasks.rake
|
@@ -52,7 +53,7 @@ metadata:
|
|
52
53
|
allowed_push_host: https://rubygems.org
|
53
54
|
homepage_uri: https://theartandscienceofruby.com/
|
54
55
|
source_code_uri: https://github.com/standard-procedure/operations
|
55
|
-
changelog_uri: https://github.com/standard-procedure/operations/
|
56
|
+
changelog_uri: https://github.com/standard-procedure/operations/tags
|
56
57
|
rdoc_options: []
|
57
58
|
require_paths:
|
58
59
|
- lib
|