standard_procedure_operations 0.7.1 → 0.7.3

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: 4d3727c9772fdc1cd01eb3438c01f0fdea6ea03b02e9ab631363bc20fac73ffc
4
- data.tar.gz: 8d75786ef589b7029d49d743a25c7e38723124d9cf095e59cae072969a61ad0e
3
+ metadata.gz: 24336966a7874886f00207073fbe8f5f3f53d55bb7594c8c36d085f4c74dd369
4
+ data.tar.gz: 3bdf232858859b1003c7b90c911932aa5e4e7c321a0fefbcbcfc62c49801cda2
5
5
  SHA512:
6
- metadata.gz: 58662325c764ab6fcbca8f047d4077a773e980bfcfda739d0a1076e8b9f5b579744de1938d2119897b33091a5de579e5d6747bd090f80a4f27a2237b287f4253
7
- data.tar.gz: 1aff44b8fd34fe03bc92e1da8ab56db4f80b29cc5fc3a1e659022c2be61b8caf9ed92e23ce2572be58421f1a6ad03e96360a2f504424b1b00860a9ddd67b5d51
6
+ metadata.gz: 1398774410e430157a8cd45e874ca1cd631ccc3159d554d32b573d4e6f261504727b31aceaf05340c4a75f60d87309977485f163bcf79b1dc15fb7fa0a1d20d2
7
+ data.tar.gz: 7ee18d5f9889f902f74bc5277f71e72d1d029676bb39fe2dae7d3185accded3d3573f2b784233b21c4830b6422771ea9d47e3281975230c97eddd931c61623d2
data/README.md CHANGED
@@ -9,7 +9,7 @@ In effect, that flowchart is a state machine - with "decision states" and "actio
9
9
 
10
10
  ## Breaking Change
11
11
 
12
- Version 0.7.0 includes breaking changes. There are migrations which rename your existing `operations_tasks` and `operations_task_participants` tables (so the data is not deleted), then a new `operations_tasks` table is created with a simplified structure.
12
+ Version 0.7.0 includes breaking changes. When you run `bin/rails operations:migrations:install` one of the migrations will drop your existing `operations_tasks` and `operations_task_participants` tables. If you need the historic data in those tables, then edit the migration to rename the tables instead. Also you will need to update your tests to use the new `test` method.
13
13
 
14
14
  ## Usage
15
15
 
@@ -193,7 +193,7 @@ end
193
193
  ```
194
194
  #### Wait handlers
195
195
 
196
- The registration process performs an action, `send_invitation` and then waits until a `name_provided?`. A `wait handler` is similar to a `decision handler` but if the conditions are not met, instead of raising an error, the task goes to sleep. A background process (see bwlow) wakes the task periodically to reevaluate the condition. Or, an `interaction` can be triggered; this is similar to an action because it does something, but it also immediately reevaluates the current wait handler. So in this case, when the `register!` interaction completes, the `name_provided?` wait handler is reevaluated and, because the `name` has now been supplied, it can move on to the `create_user` state.
196
+ The registration process performs an action, `send_invitation` and then waits until a `name_provided?`. A `wait handler` is similar to a `decision handler` but if the conditions are not met, instead of raising an error, the task goes to sleep. A background process (see below) wakes the task periodically to reevaluate the condition. Or, an `interaction` can be triggered; this is similar to an action because it does something, but it also immediately reevaluates the current wait handler. So in this case, when the `register!` interaction completes, the `name_provided?` wait handler is reevaluated and, because the `name` has now been supplied, it can move on to the `create_user` state.
197
197
 
198
198
  When a task reaches a wait handler, it goes to sleep and expects to be woken up at some point in the future. You can specify how often it is woken up by adding a `delay 10.minutes` declaration to your class. The default is `1.minute`. Likewise, if a task does not change state after a certain period it fails with an `Operations::Timeout` exception. You can set this timeout by declaring `timeout 48.hours` (the default is `24.hours`).
199
199
 
@@ -217,6 +217,18 @@ If you want the task to be run completely in the background (so it sleeps immedi
217
217
 
218
218
  [Example wait and interaction handlers](spec/examples/waiting_and_interactions_spec.rb)
219
219
 
220
+ Because background tasks are woken using ActiveJob, you may wish to control exactly how these jobs are handled.
221
+
222
+ You can specify which ActiveJob queue they are placed on (with the default value being `:default`) - this is the equivalent of setting `queue_as :my_queue` in ActiveJob. And you can even specify which queue adapter they use (if, for example, you want to use SolidQueue for most of your background jobs, but Sidekiq for a certain subset of tasks).
223
+
224
+ ```ruby
225
+ class MyBackgroundOperation < Operations::Task
226
+ queue :low_priority
227
+ runs_on :sidekiq
228
+ # ...
229
+ end
230
+ ```
231
+
220
232
  ### Sub tasks
221
233
 
222
234
  If your task needs to start sub-tasks, it can use the `start` method, passing the sub-task class and arguments.
@@ -298,10 +310,10 @@ class WeekendChecker < Operations::Task
298
310
  result :weekday
299
311
  end
300
312
 
301
- task = WeekendChecker.verify :is_it_the_weekend?, day_of_week: "Saturday"
313
+ task = WeekendChecker.test :is_it_the_weekend?, day_of_week: "Saturday"
302
314
  expect(task).to be_in :weekend
303
315
 
304
- task = WeekendChecker.verify :is_it_the_weekend?, day_of_week: "Wednesday"
316
+ task = WeekendChecker.test :is_it_the_weekend?, day_of_week: "Wednesday"
305
317
  expect(task).to be_in :weekday
306
318
  ```
307
319
 
@@ -350,4 +362,4 @@ The gem is available as open source under the terms of the [LGPL License](/LICEN
350
362
  - [x] Add visualization export for task flows
351
363
  - [ ] Replace ActiveJob with a background process
352
364
  - [ ] Rename StateManagent with Plan
353
- - [ ] Add interactions
365
+ - [ ] Add interactions
@@ -1,5 +1,5 @@
1
1
  class Operations::WakeTaskJob < ApplicationJob
2
- queue_as :default
2
+ queue_as { arguments.first.class.queue_as }
3
3
 
4
4
  def perform(task) = task.wake_up!
5
5
  end
@@ -32,6 +32,12 @@ module Operations::Task::Plan
32
32
 
33
33
  def timeout(value) = @execution_timeout = value
34
34
 
35
+ def queue(value) = @queue_as = value
36
+
37
+ def runs_on(value) = @queue_adapter ||= value
38
+ alias_method :runs, :runs_on
39
+ alias_method :runs_using, :runs_on
40
+
35
41
  def delete_after(value) = @deletion_time = value
36
42
 
37
43
  def on_timeout(&handler) = @on_timeout = handler
@@ -40,6 +46,10 @@ module Operations::Task::Plan
40
46
 
41
47
  def execution_timeout = @execution_timeout ||= 24.hours
42
48
 
49
+ def queue_as = @queue_as ||= :default
50
+
51
+ def queue_adapter = @queue_adapter ||= Operations::WakeTaskJob.queue_adapter
52
+
43
53
  def timeout_handler = @on_timeout
44
54
 
45
55
  def deletion_time = @deletion_time ||= 90.days
@@ -5,17 +5,18 @@ module Operations
5
5
  include Index
6
6
  include Testing
7
7
 
8
+ scope :active, -> { where(status: %w[active waiting]) }
8
9
  scope :ready_to_wake, -> { ready_to_wake_at(Time.current) }
9
- scope :ready_to_wake_at, ->(time) { where(wakes_at: ..time) }
10
+ scope :ready_to_wake_at, ->(time) { waiting.where(wakes_at: ..time) }
10
11
  scope :expired, -> { expires_at(Time.current) }
11
- scope :expired_at, ->(time) { where(expires_at: ..time) }
12
+ scope :expired_at, ->(time) { waiting.where(expires_at: ..time) }
12
13
  scope :ready_to_delete, -> { ready_to_delete_at(Time.current) }
13
14
  scope :ready_to_delete_at, ->(time) { where(delete_at: ..time) }
14
15
 
15
16
  # Task hierarchy relationships
16
17
  belongs_to :parent, class_name: "Operations::Task", optional: true
17
18
  has_many :sub_tasks, class_name: "Operations::Task", foreign_key: "parent_id", dependent: :nullify
18
- has_many :active_sub_tasks, -> { where(status: ["active", "waiting"]) }, class_name: "Operations::Task", foreign_key: "parent_id"
19
+ has_many :active_sub_tasks, -> { active }, class_name: "Operations::Task", foreign_key: "parent_id"
19
20
  has_many :failed_sub_tasks, -> { failed }, class_name: "Operations::Task", foreign_key: "parent_id"
20
21
  has_many :completed_sub_tasks, -> { completed }, class_name: "Operations::Task", foreign_key: "parent_id"
21
22
 
@@ -26,16 +27,18 @@ module Operations
26
27
  has_attribute :exception_backtrace, :string
27
28
 
28
29
  def call(immediate: false)
29
- while active?
30
+ state = ""
31
+ while active? && (state != current_state)
32
+ state = current_state
30
33
  Rails.logger.debug { "--- #{self}: #{current_state}" }
31
- (handler_for(current_state).immediate? || immediate) ? call_handler : go_to_sleep!
34
+ (immediate || state_is_immediate?(current_state)) ? call_handler : go_to_sleep!
32
35
  end
33
36
  rescue => ex
34
37
  record_error! ex
35
38
  raise ex
36
39
  end
37
40
 
38
- def go_to(next_state) = update! current_state: next_state
41
+ def go_to(next_state) = update! current_state: next_state, task_status: (state_is_immediate?(next_state) ? "active" : "waiting")
39
42
 
40
43
  def wake_up! = timeout_expired? ? call_timeout_handler : activate_and_call
41
44
 
@@ -45,6 +48,8 @@ module Operations
45
48
 
46
49
  def call_handler = handler_for(current_state).call(self)
47
50
 
51
+ private def state_is_immediate?(state) = handler_for(state).immediate?
52
+
48
53
  private def go_to_sleep! = update!(default_times.merge(task_status: "waiting"))
49
54
 
50
55
  private def activate_and_call
@@ -59,16 +64,34 @@ module Operations
59
64
  raise ex
60
65
  end
61
66
 
62
- def self.call(task_status: "active", **attributes) = create!(attributes.merge(task_status: task_status, current_state: initial_state).merge(default_times)).tap { |t| t.call }
63
-
64
- def self.later(**attributes) = call(task_status: "waiting", **attributes)
65
-
66
- def self.perform_now(...) = call(...)
67
-
68
- def self.perform_later(...) = later(...)
69
-
70
- def self.wake_sleeping = Task.ready_to_wake.find_each { |t| Operations::WakeTaskJob.perform_later(t) }
67
+ class << self
68
+ def call(task_status: "active", **attributes)
69
+ create!(attributes.merge(task_status: task_status, current_state: initial_state).merge(default_times)).tap do |t|
70
+ t.call
71
+ end
72
+ end
73
+ alias_method :perform_now, :call
74
+
75
+ def later(**attributes) = call(task_status: "waiting", **attributes)
76
+ alias_method :perform_later, :later
77
+
78
+ def wake_sleeping
79
+ adapter = Operations::WakeTaskJob.queue_adapter
80
+ begin
81
+ Task.ready_to_wake.find_each do |task|
82
+ Operations::WakeTaskJob.queue_adapter = task.class.queue_adapter
83
+ Operations::WakeTaskJob.perform_later task
84
+ end
85
+ ensure
86
+ Operatives::WakeTaskJob.queue_adapter = adapter
87
+ end
88
+ end
71
89
 
72
- def self.delete_old = Task.ready_to_delete.find_each { |t| Operations::DeleteOldTaskJob.perform_later(t) }
90
+ def delete_old
91
+ Task.ready_to_delete.find_each do |t|
92
+ Operations::DeleteOldTaskJob.perform_later(t)
93
+ end
94
+ end
95
+ end
73
96
  end
74
97
  end
@@ -1,3 +1,3 @@
1
1
  module Operations
2
- VERSION = "0.7.1"
2
+ VERSION = "0.7.3"
3
3
  end
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.7.1
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rahoul Baruah
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-07-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -98,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
98
  - !ruby/object:Gem::Version
99
99
  version: '0'
100
100
  requirements: []
101
- rubygems_version: 3.6.2
101
+ rubygems_version: 3.6.7
102
102
  specification_version: 4
103
103
  summary: Operations
104
104
  test_files: []