stepped 0.1.0 → 1.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.
@@ -0,0 +1,77 @@
1
+ class CreateSteppedTablesIfMissing < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :stepped_achievements, if_not_exists: true do |t|
4
+ t.string :checksum, null: false
5
+ t.string :checksum_key, null: false
6
+ t.timestamps null: false
7
+ end
8
+ add_index :stepped_achievements, :checksum_key, unique: true, if_not_exists: true
9
+
10
+ create_table :stepped_actions, if_not_exists: true do |t|
11
+ t.bigint :actor_id, null: false
12
+ t.string :actor_type, null: false
13
+ t.integer :after_callbacks_failed_count
14
+ t.integer :after_callbacks_succeeded_count
15
+ t.json :arguments
16
+ t.string :checksum
17
+ t.string :checksum_key, null: false
18
+ t.datetime :completed_at
19
+ t.string :concurrency_key, null: false
20
+ t.integer :current_step_index, null: false, default: 0
21
+ t.string :job
22
+ t.string :name, null: false
23
+ t.boolean :outbound, null: false, default: false
24
+ t.bigint :performance_id
25
+ t.boolean :root, default: false
26
+ t.datetime :started_at
27
+ t.string :status, null: false, default: "pending"
28
+ t.integer :timeout_seconds
29
+ t.timestamps null: false
30
+ end
31
+
32
+ add_index :stepped_actions, %i[actor_type actor_id], name: "index_stepped_actions_on_actor", if_not_exists: true
33
+ add_index :stepped_actions, %i[performance_id outbound], name: "index_stepped_actions_on_performance_id_and_outbound", if_not_exists: true
34
+ add_index :stepped_actions, :performance_id, if_not_exists: true
35
+ add_index :stepped_actions, :root, if_not_exists: true
36
+
37
+ create_table :stepped_actions_steps, id: false, if_not_exists: true do |t|
38
+ t.bigint :action_id, null: false
39
+ t.bigint :step_id, null: false
40
+ end
41
+
42
+ add_index :stepped_actions_steps, %i[action_id step_id], unique: true, name: "index_stepped_actions_steps_on_action_id_and_step_id", if_not_exists: true
43
+ add_index :stepped_actions_steps, %i[step_id action_id], name: "index_stepped_actions_steps_on_step_id_and_action_id", if_not_exists: true
44
+
45
+ create_table :stepped_performances, if_not_exists: true do |t|
46
+ t.bigint :action_id, null: false
47
+ t.string :concurrency_key
48
+ t.string :outbound_complete_key
49
+ t.timestamps null: false
50
+ end
51
+
52
+ add_index :stepped_performances, :action_id, unique: true, name: "index_stepped_performances_on_action_id", if_not_exists: true
53
+ add_index :stepped_performances, :concurrency_key, unique: true, name: "index_stepped_performances_on_concurrency_key", if_not_exists: true
54
+ add_index :stepped_performances, :outbound_complete_key,
55
+ name: "index_stepped_performances_on_outbound_complete_key",
56
+ where: "(outbound_complete_key IS NOT NULL)",
57
+ if_not_exists: true
58
+
59
+ create_table :stepped_steps, if_not_exists: true do |t|
60
+ t.bigint :action_id, null: false
61
+ t.datetime :completed_at
62
+ t.integer :definition_index, null: false
63
+ t.integer :pending_actions_count, null: false, default: 0
64
+ t.datetime :started_at
65
+ t.string :status, null: false, default: "pending"
66
+ t.integer :unsuccessful_actions_count, default: 0
67
+ t.timestamps null: false
68
+ end
69
+
70
+ add_index :stepped_steps, %i[action_id definition_index], unique: true, name: "index_stepped_steps_on_action_id_and_definition_index", if_not_exists: true
71
+ add_index :stepped_steps, :action_id, name: "index_stepped_steps_on_action_id", if_not_exists: true
72
+
73
+ add_foreign_key :stepped_actions, :stepped_performances, column: :performance_id, if_not_exists: true
74
+ add_foreign_key :stepped_performances, :stepped_actions, column: :action_id, if_not_exists: true
75
+ add_foreign_key :stepped_steps, :stepped_actions, column: :action_id, if_not_exists: true
76
+ end
77
+ end
@@ -1,10 +1,12 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails/engine"
4
- require "action_dispatch"
5
-
6
1
  module Stepped
7
2
  class Engine < ::Rails::Engine
8
- isolate_namespace Stepped
3
+ config.stepped_actions = ActiveSupport::OrderedOptions.new
4
+ config.stepped_actions.handle_exceptions = Rails.env.test? ? [] : [ StandardError ]
5
+
6
+ initializer "stepped.active_record.extensions" do
7
+ ActiveSupport.on_load :active_record do
8
+ include Stepped::Actionable
9
+ end
10
+ end
9
11
  end
10
12
  end
@@ -0,0 +1,76 @@
1
+ module Stepped::TestHelper
2
+ def perform_stepped_actions(only: stepped_job_classes)
3
+ perform_enqueued_jobs_recursively(only:)
4
+ complete_stepped_outbound_performances
5
+
6
+ perform_stepped_actions(only:) if Stepped::Performance.any? || enqueued_jobs_with(only:).count > 0
7
+ end
8
+
9
+ def assert_stepped_action_job(name, actor = nil)
10
+ jobs_before = enqueued_jobs + performed_jobs
11
+ yield
12
+ jobs = (enqueued_jobs + performed_jobs) - jobs_before
13
+ found = false
14
+ all_actions = []
15
+ jobs.each do |job|
16
+ next unless job["job_class"] == "Stepped::ActionJob"
17
+
18
+ job_actor, job_action_name = ActiveJob::Arguments.deserialize job["arguments"]
19
+ all_actions << [ job_actor.to_global_id, job_action_name ].join("#")
20
+ next if found
21
+
22
+ found = job_action_name == name
23
+ if found && actor.present?
24
+ actor = eval(actor) if actor.is_a?(String)
25
+ found = job_actor == actor
26
+ end
27
+ end
28
+ assert found, <<~MESSAGE
29
+ Stepped action job for '#{name}' was not enqueued or performed#{actor.respond_to?(:to_debug_id) ? " on #{actor.to_debug_id}" : nil}.
30
+ Actions that were: #{all_actions.join(", ")}
31
+ MESSAGE
32
+ end
33
+
34
+ def assert_no_stepped_actions
35
+ jobs_before = enqueued_jobs + performed_jobs
36
+ yield
37
+ jobs = (enqueued_jobs + performed_jobs) - jobs_before
38
+ found = false
39
+ all_actions = []
40
+ jobs.each do |job|
41
+ next unless job["job_class"] == "Stepped::ActionJob"
42
+ found = true
43
+ job_actor, job_action_name = ActiveJob::Arguments.deserialize job["arguments"]
44
+ all_actions << [ job_actor.class.name, job_action_name ].join("#")
45
+ end
46
+ assert_not found, <<~MESSAGE
47
+ Stepped action jobs enqueued or performed: #{all_actions.join(", ")}
48
+ MESSAGE
49
+ end
50
+
51
+ def handle_stepped_action_exceptions(only: [ StandardError ])
52
+ was = Stepped::Engine.config.stepped_actions.handle_exceptions
53
+ Stepped::Engine.config.stepped_actions.handle_exceptions = Array(only)
54
+ yield
55
+ ensure
56
+ Stepped::Engine.config.stepped_actions.handle_exceptions = was
57
+ end
58
+
59
+ def perform_enqueued_jobs_recursively(only: nil)
60
+ total = 0
61
+ loop do
62
+ batch = perform_enqueued_jobs(only:)
63
+ break if batch == 0
64
+ total += batch
65
+ end
66
+ total
67
+ end
68
+
69
+ def stepped_job_classes
70
+ [ Stepped::ActionJob, Stepped::TimeoutJob, Stepped::WaitJob ] + Stepped::Registry.job_classes
71
+ end
72
+
73
+ def complete_stepped_outbound_performances
74
+ # Consuming app can redefine
75
+ end
76
+ end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Stepped
4
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
5
3
  end
data/lib/stepped.rb CHANGED
@@ -1,13 +1,26 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/lazy_load_hooks"
4
- require_relative "stepped/version"
5
- require_relative "stepped/active_record_extension"
6
- require_relative "stepped/engine"
1
+ require "stepped/version"
2
+ require "stepped/engine"
7
3
 
8
4
  module Stepped
9
- end
5
+ def self.table_name_prefix
6
+ "stepped_"
7
+ end
8
+
9
+ def self.handled_exception_classes
10
+ Array(Stepped::Engine.config.stepped_actions.handle_exceptions)
11
+ end
12
+
13
+ def self.handle_exception(context: {})
14
+ yield
15
+ true
16
+ rescue StandardError => e
17
+ raise unless handled_exception_classes.any? { e.class <= _1 }
18
+ Rails.error.report(e, handled: false, context:)
19
+ false
20
+ end
10
21
 
11
- ActiveSupport.on_load(:active_record) do
12
- include Stepped::ActiveRecordExtension
22
+ def self.checksum(value)
23
+ return if value.nil?
24
+ Digest::SHA256.hexdigest JSON.dump(value)
25
+ end
13
26
  end
@@ -0,0 +1,12 @@
1
+ namespace :stepped do
2
+ desc "Install Stepped Actions"
3
+ task install: "stepped:install:migrations"
4
+
5
+ namespace :install do
6
+ desc "Copy Stepped migrations to the host app"
7
+ task migrations: :environment do
8
+ ENV["FROM"] = Stepped::Engine.railtie_name
9
+ Rake::Task["railties:install:migrations"].invoke
10
+ end
11
+ end
12
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stepped
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Starsi
@@ -15,72 +15,62 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '8.1'
18
+ version: 8.1.1
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '8.1'
25
+ version: 8.1.1
26
26
  - !ruby/object:Gem::Dependency
27
- name: bundler
27
+ name: temping
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '2.5'
32
+ version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '2.5'
40
- - !ruby/object:Gem::Dependency
41
- name: minitest
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '5.0'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: '5.0'
54
- - !ruby/object:Gem::Dependency
55
- name: rake
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: '13.0'
61
- type: :development
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: '13.0'
39
+ version: '0'
68
40
  email:
69
41
  - klevo@klevo.sk
70
42
  executables: []
71
43
  extensions: []
72
44
  extra_rdoc_files: []
73
45
  files:
74
- - LICENSE
46
+ - MIT-LICENSE
75
47
  - README.md
48
+ - Rakefile
49
+ - app/jobs/stepped/action_job.rb
50
+ - app/jobs/stepped/complete_action_job.rb
51
+ - app/jobs/stepped/timeout_job.rb
52
+ - app/jobs/stepped/wait_job.rb
53
+ - app/models/concerns/stepped/actionable.rb
54
+ - app/models/stepped/achievement.rb
55
+ - app/models/stepped/action.rb
56
+ - app/models/stepped/arguments.rb
57
+ - app/models/stepped/definition.rb
58
+ - app/models/stepped/performance.rb
59
+ - app/models/stepped/registry.rb
60
+ - app/models/stepped/step.rb
61
+ - config/routes.rb
62
+ - db/migrate/20251214104829_create_stepped_tables_if_missing.rb
76
63
  - lib/stepped.rb
77
- - lib/stepped/active_record_extension.rb
78
64
  - lib/stepped/engine.rb
65
+ - lib/stepped/test_helper.rb
79
66
  - lib/stepped/version.rb
67
+ - lib/tasks/stepped_tasks.rake
80
68
  homepage: https://github.com/envirobly/stepped
81
69
  licenses:
82
70
  - MIT
83
- metadata: {}
71
+ metadata:
72
+ homepage_uri: https://github.com/envirobly/stepped
73
+ source_code_uri: https://github.com/envirobly/stepped
84
74
  rdoc_options: []
85
75
  require_paths:
86
76
  - lib
@@ -88,14 +78,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
78
  requirements:
89
79
  - - ">="
90
80
  - !ruby/object:Gem::Version
91
- version: '3.2'
81
+ version: '0'
92
82
  required_rubygems_version: !ruby/object:Gem::Requirement
93
83
  requirements:
94
84
  - - ">="
95
85
  - !ruby/object:Gem::Version
96
86
  version: '0'
97
87
  requirements: []
98
- rubygems_version: 4.0.0
88
+ rubygems_version: 4.0.1
99
89
  specification_version: 4
100
- summary: Stepped Actions orchestrate checksumable and reusable tasks.
90
+ summary: Rails engine for orchestrating complex action trees.
101
91
  test_files: []
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stepped
4
- module ActiveRecordExtension
5
- def stepped_test
6
- "stepped here"
7
- end
8
- end
9
- end
File without changes