standard_procedure_operations 0.5.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a14742a79230259d8cb55462cac7682b6adfe8993cacc0ec4f35ed3832910858
4
- data.tar.gz: 939c54f7cb755794ecd71ee920bfc1ff65f8e925187f6ccd9c1e98515d5a1b3d
3
+ metadata.gz: d9a47b950a0831c0bf0a1be69de7b7f75c80237ec73a75673397a0b8112aa846
4
+ data.tar.gz: 62e9741fcd4053d0f4139aa956f5d51025d644188b2da460d6b7550873257030
5
5
  SHA512:
6
- metadata.gz: 6c3ec1e16dada2b58d48aa72a11e64aaa7701b7a481836cc9b35ac268f752257b9dabf619eb43ecc6e4541fbc6ed50ae1b51d6ee4b4ed60fe4ad2ec302b1f8b8
7
- data.tar.gz: 67d54e8449c264dc2fb1dd4883b64a810cabd47a2b976e931f1060d1573d0579c4964336159f6f8acf1d6d599ae3edf4de64b818f08c4c46c963035534264500
6
+ metadata.gz: 611b50e5912231a593187ea4dc59c5e270674f65413b1c3bbd34d3e1f40849f817954282750d729bdadf23a1bacb35b202c9fb71b2edaccfa59dddd209746234
7
+ data.tar.gz: 5b4fbf04648dfe9347330385f1dfdfc32eeadfe4adbb89a015a562c79b0a7b7f6e6b0e91b8d65f3d934e7f3af19bbaf8856ea39555cdd1b649f2ae9f2937ab81
data/README.md CHANGED
@@ -48,7 +48,6 @@ class PrepareDocumentForDownload < Operations::Task
48
48
  starts_with :authorised?
49
49
 
50
50
  decision :authorised? do
51
- inputs :user
52
51
  condition { user.can?(:read, data.document) }
53
52
 
54
53
  if_true :within_download_limits?
@@ -56,7 +55,6 @@ class PrepareDocumentForDownload < Operations::Task
56
55
  end
57
56
 
58
57
  decision :within_download_limits? do
59
- inputs :user
60
58
  condition { user.within_download_limits? }
61
59
 
62
60
  if_true :use_filename_scrambler?
@@ -64,7 +62,6 @@ class PrepareDocumentForDownload < Operations::Task
64
62
  end
65
63
 
66
64
  decision :use_filename_scrambler? do
67
- inputs :use_filename_scrambler
68
65
  condition { use_filename_scrambler }
69
66
 
70
67
  if_true :scramble_filename
@@ -72,16 +69,11 @@ class PrepareDocumentForDownload < Operations::Task
72
69
  end
73
70
 
74
71
  action :scramble_filename do
75
- inputs :document
76
-
77
72
  self.filename = "#{Faker::Lorem.word}#{File.extname(document.filename.to_s)}"
78
- end
73
+ end
79
74
  go_to :return_filename
80
75
 
81
76
  result :return_filename do |results|
82
- inputs :document
83
- optional :filename
84
-
85
77
  results.filename = filename || document.filename.to_s
86
78
  end
87
79
  end
@@ -158,7 +150,9 @@ go_to :send_invitations
158
150
  You can also specify the required and optional data for your action handler using parameters or within the block. `optional` is decorative and helps with documentation. When using the block form, ensure you call `inputs` at the start of the block so that the task fails before doing any meaningful work.
159
151
 
160
152
  ```ruby
161
- action :have_a_party, inputs: [:number_of_guests], optional: [:music] do
153
+ action :have_a_party do
154
+ inputs :number_of_guests
155
+ optional :music
162
156
  self.food = task.buy_some_food_for(number_of_guests)
163
157
  self.beer = task.buy_some_beer_for(number_of_guests)
164
158
  self.music ||= task.plan_a_party_playlist
@@ -166,8 +160,6 @@ end
166
160
  go_to :send_invitations
167
161
  ```
168
162
 
169
- Defining state transitions statically with `go_to` ensures that all transitions are known when the operation class is loaded, making the flow easier to understand and analyze. The `go_to` method will automatically associate the transition with the most recently defined action handler.
170
-
171
163
  ### Waiting
172
164
  Wait handlers are very similar to decision handlers but only work within [background tasks](#background-operations-and-pauses).
173
165
 
@@ -280,15 +272,17 @@ task = CombineNames.call first_name: "Alice", last_name: "Aardvark"
280
272
  task.results[:name] # => Alice Aardvark
281
273
  ```
282
274
 
283
- Because handlers are run in the context of the data carrier, you do not have direct access to methods or properties on your task object. However, the data carrier holds a reference to your task; use `task.do_something` or `task.some_attribute` to access it. The exception is the `fail_with`, `call` and `start` methods which the data carrier understands (and are intercepted when you are [testing](#testing)). Note that `go_to` has been removed from the data carrier to enforce static state transitions with the `go_to` method.
275
+ Because handlers are run in the context of the data carrier, you do not have direct access to methods or properties on your task object. However, the data carrier holds a reference to your task; use `task.do_something` or `task.some_attribute` to access it. The exception is the `fail_with`, `call` and `start` methods which the data carrier understands (and are intercepted when you are [testing](#testing)).
284
276
 
285
277
  Both your task's `data` and its final `results` are stored in the database, so they can be examined later. The `results` because that's what you're interested in, the `data` as it can be useful for debugging or auditing purposes.
286
278
 
287
279
  They are both stored as hashes that are encoded into JSON.
288
280
 
289
- Instead of using the standard [JSON coder](https://api.rubyonrails.org/v4.2/classes/ActiveModel/Serializers/JSON.html), we use a [GlobalIDSerialiser](/lib/operations/global_id_serialiser.rb). This uses [ActiveJob::Arguments](https://guides.rubyonrails.org/active_job_basics.html#supported-types-for-arguments) to transform any models into [GlobalIDs](https://github.com/rails/globalid) before storage and convert them back to models upon retrieval.
281
+ Instead of using the standard [JSON coder](https://api.rubyonrails.org/v4.2/classes/ActiveModel/Serializers/JSON.html), we use a [GlobalIdSerialiser](https://github.com/standard-procedure/global_id_serialiser). This serialises most data into standard JSON types, as you would expect, but it also takes any [GlobalID::Identification](https://github.com/rails/globalid) objects (which includes all ActiveRecord models) and converts them to a GlobalID string. Then when the data is deserialised from the database, the GlobalID is converted back into the appropriate model.
290
282
 
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]`.
283
+ If the original database record was deleted between the time the hash was serialised and when it was retrieved, the `GlobalID::Locator` will fail. In this case, the deserialised data will contain a `nil` for the value in question.
284
+
285
+ Also note that the GlobalIdSerialiser automatically converts all hash keys into symbols (unlike the standard JSON coder which uses strings).
292
286
 
293
287
  #### Indexing data and results
294
288
 
@@ -386,15 +380,11 @@ class UserRegistration < Operations::Task
386
380
  starts_with :create_user
387
381
 
388
382
  action :create_user do
389
- inputs :email
390
-
391
383
  self.user = User.create! email: email
392
384
  end
393
385
  go_to :send_verification_email
394
386
 
395
387
  action :send_verification_email do
396
- inputs :user
397
-
398
388
  UserMailer.with(user: user).verification_email.deliver_later
399
389
  end
400
390
  go_to :verified?
@@ -405,8 +395,6 @@ class UserRegistration < Operations::Task
405
395
  end
406
396
 
407
397
  action :notify_administrator do
408
- inputs :user
409
-
410
398
  AdminMailer.with(user: user).verification_completed.deliver_later
411
399
  end
412
400
  end
@@ -424,7 +412,6 @@ class ParallelTasks < Operations::Task
424
412
  starts_with :start_sub_tasks
425
413
 
426
414
  action :start_sub_tasks do
427
- inputs :number_of_sub_tasks
428
415
  self.sub_tasks = (1..number_of_sub_tasks).collect { |i| start LongRunningTask, number: i }
429
416
  end
430
417
  go_to :do_something_else
@@ -598,7 +585,6 @@ end
598
585
 
599
586
  The visualization includes:
600
587
  - Color-coded nodes by state type (decisions, actions, wait states, results)
601
- - Required and optional inputs for each state
602
588
  - Transition conditions between states with custom labels when provided
603
589
  - Special handling for custom transition blocks
604
590
 
@@ -9,8 +9,8 @@ module Operations
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: {}
13
- serialize :results, coder: Operations::GlobalIDSerialiser, type: Hash, default: {}
12
+ serialize :data, coder: GlobalIdSerialiser, type: Hash, default: {}
13
+ serialize :results, coder: GlobalIdSerialiser, type: Hash, default: {}
14
14
 
15
15
  has_many :task_participants, class_name: "Operations::TaskParticipant", dependent: :destroy
16
16
  after_save :record_participants
@@ -1,3 +1,3 @@
1
1
  module Operations
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.1"
3
3
  end
data/lib/operations.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "ostruct"
2
+ require "global_id_serialiser"
2
3
 
3
4
  module Operations
4
5
  class Error < StandardError
@@ -10,7 +11,6 @@ module Operations
10
11
  end
11
12
  require "operations/version"
12
13
  require "operations/engine"
13
- require "operations/global_id_serialiser"
14
14
  require "operations/failure"
15
15
  require "operations/cannot_wait_in_foreground"
16
16
  require "operations/timeout"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_procedure_operations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rahoul Baruah
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-10 00:00:00.000000000 Z
10
+ date: 2025-03-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: 7.1.3
26
+ - !ruby/object:Gem::Dependency
27
+ name: standard_procedure_global_id_serialiser
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
26
40
  description: Pipelines and State Machines for composable, trackable business logic
27
41
  email:
28
42
  - rahoulb@echodek.co
@@ -57,7 +71,6 @@ files:
57
71
  - lib/operations/engine.rb
58
72
  - lib/operations/exporters/graphviz.rb
59
73
  - lib/operations/failure.rb
60
- - lib/operations/global_id_serialiser.rb
61
74
  - lib/operations/matchers.rb
62
75
  - lib/operations/no_decision.rb
63
76
  - lib/operations/timeout.rb
@@ -1,29 +0,0 @@
1
- module Operations
2
- # Serialise and deserialise data to and from JSON
3
- # Unlike the standard JSON coder, this coder uses the ActiveJob::Arguments serializer.
4
- # This means that if the data contains an ActiveRecord model, it will be serialised as a GlobalID string
5
- #
6
- # Usage:
7
- # class MyModel < ApplicationRecord
8
- # serialize :data, coder: GlobalIDSerialiser, type: Hash, default: {}
9
- # end
10
- # @my_model = MyModel.create! data: {hello: "world", user: User.first}
11
- # puts @my_model[:data] # => {hello: "world", user: #<User id: 1>}
12
- class GlobalIDSerialiser
13
- def self.dump(data) = ActiveSupport::JSON.dump(ActiveJob::Arguments.serialize([data]))
14
-
15
- def self.load(json)
16
- ActiveJob::Arguments.deserialize(ActiveSupport::JSON.decode(json)).first
17
- rescue => ex
18
- _load_without_global_ids(json).merge exception_message: ex.message, exception_class: ex.class.name, raw_data: json.to_s
19
- end
20
-
21
- def self._load_without_global_ids(json)
22
- ActiveSupport::JSON.decode(json).first.tap do |hash|
23
- hash.delete("_aj_symbol_keys")
24
- end.transform_values do |value|
25
- (value.is_a?(Hash) && value.key?("_aj_globalid")) ? value["_aj_globalid"] : value
26
- end.transform_keys(&:to_sym)
27
- end
28
- end
29
- end