odsl 0.1.1 → 0.3.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/CHANGELOG.md +54 -2
- data/CONTRIBUTING.md +40 -0
- data/README.md +319 -66
- data/docs/superpowers/plans/2026-10-09-background-adapters-audit-locking.md +1808 -0
- data/docs/superpowers/specs/2026-10-09-background-adapters-audit-design.md +267 -0
- data/lib/generators/odsl/install_generator.rb +23 -0
- data/lib/generators/odsl/templates/create_odsl_tables.rb.erb +29 -0
- data/lib/generators/odsl/templates/odsl_initializer.rb.erb +13 -0
- data/lib/odsl/adapters/active_job.rb +21 -0
- data/lib/odsl/adapters/base.rb +11 -0
- data/lib/odsl/adapters/odsl_job.rb +12 -0
- data/lib/odsl/adapters/thread.rb +17 -0
- data/lib/odsl/audit/execution_context.rb +34 -0
- data/lib/odsl/audit/serializer.rb +35 -0
- data/lib/odsl/audit/step_record.rb +27 -0
- data/lib/odsl/configuration.rb +36 -0
- data/lib/odsl/dsl.rb +67 -17
- data/lib/odsl/errors.rb +8 -0
- data/lib/odsl/locking/lock_handle.rb +9 -0
- data/lib/odsl/methods.rb +37 -8
- data/lib/odsl/railtie.rb +15 -0
- data/lib/odsl/runners.rb +117 -6
- data/lib/odsl/version.rb +1 -1
- data/lib/odsl.rb +12 -2
- data/odsl.gemspec +9 -4
- metadata +78 -5
- data/lib/odsl/error.rb +0 -4
data/lib/odsl/methods.rb
CHANGED
|
@@ -1,25 +1,33 @@
|
|
|
1
|
+
# lib/odsl/methods.rb
|
|
1
2
|
module Odsl
|
|
2
3
|
module Methods
|
|
3
4
|
|
|
4
5
|
def use(object)
|
|
5
6
|
@delegated = instance_variable_get("@#{object}")
|
|
6
|
-
|
|
7
7
|
self
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def call(methodo)
|
|
11
|
-
|
|
12
11
|
run_method(methodo)
|
|
13
12
|
restore_delegate
|
|
14
|
-
|
|
15
13
|
self
|
|
16
14
|
end
|
|
17
|
-
|
|
15
|
+
|
|
18
16
|
def to(*variables)
|
|
19
17
|
variables.each_with_index do |variable, index|
|
|
20
|
-
|
|
18
|
+
value = @last_results[index]
|
|
19
|
+
|
|
20
|
+
if @_resuming_steps&.include?(variable.to_s)
|
|
21
|
+
stored = @_execution_context
|
|
22
|
+
.step_records
|
|
23
|
+
.find_by(name: variable.to_s)
|
|
24
|
+
&.deserialized_result
|
|
25
|
+
instance_variable_set("@#{variable}", stored)
|
|
26
|
+
else
|
|
27
|
+
instance_variable_set("@#{variable}", value)
|
|
28
|
+
_record_step(variable, value)
|
|
29
|
+
end
|
|
21
30
|
end
|
|
22
|
-
|
|
23
31
|
self
|
|
24
32
|
end
|
|
25
33
|
|
|
@@ -27,7 +35,7 @@ module Odsl
|
|
|
27
35
|
|
|
28
36
|
def run_method(methodo)
|
|
29
37
|
@last_results = @delegated.send(methodo, *with_attributes_resolver)
|
|
30
|
-
@last_results = [
|
|
38
|
+
@last_results = [@last_results] unless @last_results.is_a?(Array)
|
|
31
39
|
end
|
|
32
40
|
|
|
33
41
|
def restore_delegate
|
|
@@ -35,7 +43,28 @@ module Odsl
|
|
|
35
43
|
end
|
|
36
44
|
|
|
37
45
|
def with_attributes_resolver
|
|
46
|
+
return [] if @with_attributes.nil?
|
|
38
47
|
@with_attributes.map { |attribute| instance_variable_get("@#{attribute}") }
|
|
39
48
|
end
|
|
49
|
+
|
|
50
|
+
def _record_step(name, value)
|
|
51
|
+
return unless @_execution_context
|
|
52
|
+
|
|
53
|
+
@_step_index ||= 0
|
|
54
|
+
serializable = Odsl::Audit::Serializer.serializable?(value)
|
|
55
|
+
|
|
56
|
+
Odsl::Audit::StepRecord.create!(
|
|
57
|
+
execution_context: @_execution_context,
|
|
58
|
+
name: name.to_s,
|
|
59
|
+
step_index: @_step_index,
|
|
60
|
+
status: "completed",
|
|
61
|
+
result: serializable ? Odsl::Audit::Serializer.dump(value) : nil,
|
|
62
|
+
non_resumable: !serializable,
|
|
63
|
+
started_at: Time.now,
|
|
64
|
+
completed_at: Time.now
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@_step_index += 1
|
|
68
|
+
end
|
|
40
69
|
end
|
|
41
|
-
end
|
|
70
|
+
end
|
data/lib/odsl/railtie.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require "rails/railtie"
|
|
2
|
+
|
|
3
|
+
module Odsl
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
rake_tasks do
|
|
6
|
+
namespace :odsl do
|
|
7
|
+
desc "Remove expired audit records"
|
|
8
|
+
task cleanup: :environment do
|
|
9
|
+
count = Odsl.cleanup_expired!
|
|
10
|
+
puts "Removed #{count} expired execution context(s)"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/odsl/runners.rb
CHANGED
|
@@ -1,15 +1,126 @@
|
|
|
1
|
+
# lib/odsl/runners.rb
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
1
4
|
module Odsl
|
|
2
5
|
module Runners
|
|
3
|
-
def run(method)
|
|
6
|
+
def run(method, key: nil)
|
|
7
|
+
if self.class.inmutable_mode
|
|
8
|
+
_run_immutable(method, key: key)
|
|
9
|
+
else
|
|
10
|
+
_run_method(method, key: key)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run_in_background(method, key: nil)
|
|
15
|
+
_background_adapter.enqueue(
|
|
16
|
+
service_class: self.class.name,
|
|
17
|
+
method_name: method.to_s,
|
|
18
|
+
kwargs: @kwargs,
|
|
19
|
+
key: key
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def run_in_parallel(*methods_and_opts)
|
|
24
|
+
key = methods_and_opts.last.is_a?(Hash) ? methods_and_opts.pop[:key] : nil
|
|
25
|
+
handles = methods_and_opts.map { |m| run_in_background(m, key: key) }
|
|
26
|
+
handles.map { |h| h.respond_to?(:value) ? h.value : h }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def _background_adapter
|
|
32
|
+
case Odsl.config.background_adapter
|
|
33
|
+
when :thread then Odsl::Adapters::Thread
|
|
34
|
+
when :active_job then Odsl::Adapters::ActiveJob
|
|
35
|
+
else raise Odsl::Error, "Unknown background adapter: #{Odsl.config.background_adapter.inspect}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def _run_method(method, key: nil)
|
|
40
|
+
ctx_result = _setup_context(method, key)
|
|
41
|
+
return nil if ctx_result == :already_completed
|
|
42
|
+
|
|
43
|
+
@_execution_context = ctx_result
|
|
44
|
+
@_current_lock = nil
|
|
45
|
+
@_step_index = 0
|
|
46
|
+
@_current_method = method.to_s
|
|
47
|
+
|
|
48
|
+
begin
|
|
49
|
+
instance_eval(method.to_s)
|
|
50
|
+
@_execution_context&.update!(status: "completed")
|
|
51
|
+
_finish_result
|
|
52
|
+
rescue Odsl::LockError
|
|
53
|
+
raise
|
|
54
|
+
rescue
|
|
55
|
+
@_execution_context&.update!(status: "failed")
|
|
56
|
+
raise
|
|
57
|
+
ensure
|
|
58
|
+
@_current_lock&.unlock rescue nil
|
|
59
|
+
@_current_lock = nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def _run_immutable(method, key: nil)
|
|
64
|
+
copy = dup
|
|
65
|
+
copy.instance_variable_set(:@context, @context.dup)
|
|
66
|
+
copy.instance_variable_set(:@last_results, [])
|
|
67
|
+
copy.instance_variable_set(:@with_attributes, nil)
|
|
68
|
+
copy.instance_variable_set(:@finish_with, nil)
|
|
69
|
+
copy.send(:_run_method, method, key: key)
|
|
70
|
+
end
|
|
4
71
|
|
|
5
|
-
|
|
72
|
+
def _resume_method(method, key: nil)
|
|
73
|
+
@_current_lock = nil
|
|
74
|
+
@_step_index = @_execution_context.step_records.count
|
|
75
|
+
@_current_method = method.to_s
|
|
76
|
+
|
|
77
|
+
@_execution_context&.update!(status: "running")
|
|
78
|
+
|
|
79
|
+
begin
|
|
80
|
+
instance_eval(method.to_s)
|
|
81
|
+
@_execution_context&.update!(status: "completed")
|
|
82
|
+
_finish_result
|
|
83
|
+
rescue Odsl::LockError
|
|
84
|
+
raise
|
|
85
|
+
rescue
|
|
86
|
+
@_execution_context&.update!(status: "failed")
|
|
87
|
+
raise
|
|
88
|
+
ensure
|
|
89
|
+
@_current_lock&.unlock rescue nil
|
|
90
|
+
@_current_lock = nil
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def _setup_context(method, key)
|
|
95
|
+
return nil unless key && defined?(Odsl::Audit::ExecutionContext)
|
|
96
|
+
|
|
97
|
+
ctx = Odsl::Audit::ExecutionContext.find_by(idempotency_key: key)
|
|
98
|
+
|
|
99
|
+
if ctx
|
|
100
|
+
return :already_completed if ctx.completed?
|
|
101
|
+
raise Odsl::AlreadyRunningError, "Job with key '#{key}' is already running" if ctx.running?
|
|
102
|
+
else
|
|
103
|
+
ttl = Odsl.config.audit_ttl
|
|
104
|
+
ctx = Odsl::Audit::ExecutionContext.create!(
|
|
105
|
+
idempotency_key: key,
|
|
106
|
+
service_class: self.class.name,
|
|
107
|
+
method_name: method.to_s,
|
|
108
|
+
kwargs: JSON.generate(@kwargs),
|
|
109
|
+
status: "pending",
|
|
110
|
+
expires_at: ttl ? Time.now + ttl : nil
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
ctx.update!(status: "running")
|
|
115
|
+
ctx
|
|
116
|
+
end
|
|
6
117
|
|
|
7
|
-
|
|
8
|
-
|
|
118
|
+
def _finish_result
|
|
119
|
+
if @finish_with.is_a?(Array)
|
|
9
120
|
@finish_with.map { |variable| instance_variable_get("@#{variable}") }
|
|
10
121
|
else
|
|
11
|
-
instance_variable_get
|
|
122
|
+
instance_variable_get("@#{@finish_with}")
|
|
12
123
|
end
|
|
13
124
|
end
|
|
14
125
|
end
|
|
15
|
-
end
|
|
126
|
+
end
|
data/lib/odsl/version.rb
CHANGED
data/lib/odsl.rb
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require "odsl/version"
|
|
2
|
+
require "odsl/errors"
|
|
3
|
+
require "odsl/configuration"
|
|
4
|
+
require "odsl/adapters/base"
|
|
5
|
+
require "odsl/adapters/thread"
|
|
6
|
+
require "odsl/adapters/active_job"
|
|
7
|
+
require "odsl/audit/serializer"
|
|
8
|
+
require "odsl/audit/execution_context"
|
|
9
|
+
require "odsl/audit/step_record"
|
|
10
|
+
require "odsl/locking/lock_handle"
|
|
11
|
+
require "odsl/dsl"
|
|
12
|
+
require "odsl/railtie" if defined?(Rails::Railtie)
|
data/odsl.gemspec
CHANGED
|
@@ -18,11 +18,12 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
- Decouple aspects from code ( background, paralelization )."
|
|
19
19
|
spec.homepage = "https://github.com/aleguro/ODSL"
|
|
20
20
|
spec.license = "MIT"
|
|
21
|
-
spec.required_ruby_version = ">= 2.
|
|
21
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
22
22
|
|
|
23
|
-
spec.metadata["homepage_uri"]
|
|
24
|
-
spec.metadata["source_code_uri"]
|
|
25
|
-
spec.metadata["changelog_uri"]
|
|
23
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
24
|
+
spec.metadata["source_code_uri"] = "https://github.com/aleguro/ODSL"
|
|
25
|
+
spec.metadata["changelog_uri"] = "https://github.com/aleguro/ODSL/blob/master/CHANGELOG.md"
|
|
26
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
26
27
|
|
|
27
28
|
# Specify which files should be added to the gem when it is released.
|
|
28
29
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
@@ -38,6 +39,10 @@ Gem::Specification.new do |spec|
|
|
|
38
39
|
|
|
39
40
|
spec.add_development_dependency "minitest", ">= 5.8"
|
|
40
41
|
spec.add_development_dependency "minitest-reporters", ">= 1.1"
|
|
42
|
+
spec.add_development_dependency "activerecord", ">= 7.0"
|
|
43
|
+
spec.add_development_dependency "sqlite3", ">= 1.7"
|
|
44
|
+
spec.add_development_dependency "redlock", ">= 1.3"
|
|
45
|
+
spec.add_development_dependency "railties", ">= 7.0"
|
|
41
46
|
|
|
42
47
|
# For more information and examples about making a new gem, check out our
|
|
43
48
|
# guide at: https://bundler.io/guides/creating_gem.html
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: odsl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- alejandro gurovich
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-10-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -38,6 +38,62 @@ dependencies:
|
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '1.1'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: activerecord
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '7.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '7.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: sqlite3
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.7'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.7'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: redlock
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.3'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.3'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: railties
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '7.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '7.0'
|
|
41
97
|
description: |-
|
|
42
98
|
This is a prototype oriented to use OOP and a functional programming approach by providing a DSL to decouple code logic from coding itself while enforcing good practices and providing - Design patterns
|
|
43
99
|
- Inmutability
|
|
@@ -51,15 +107,31 @@ extra_rdoc_files: []
|
|
|
51
107
|
files:
|
|
52
108
|
- CHANGELOG.md
|
|
53
109
|
- CODE_OF_CONDUCT.md
|
|
110
|
+
- CONTRIBUTING.md
|
|
54
111
|
- LICENSE.txt
|
|
55
112
|
- README.md
|
|
56
113
|
- Rakefile
|
|
114
|
+
- docs/superpowers/plans/2026-10-09-background-adapters-audit-locking.md
|
|
115
|
+
- docs/superpowers/specs/2026-10-09-background-adapters-audit-design.md
|
|
116
|
+
- lib/generators/odsl/install_generator.rb
|
|
117
|
+
- lib/generators/odsl/templates/create_odsl_tables.rb.erb
|
|
118
|
+
- lib/generators/odsl/templates/odsl_initializer.rb.erb
|
|
57
119
|
- lib/odsl.rb
|
|
120
|
+
- lib/odsl/adapters/active_job.rb
|
|
121
|
+
- lib/odsl/adapters/base.rb
|
|
122
|
+
- lib/odsl/adapters/odsl_job.rb
|
|
123
|
+
- lib/odsl/adapters/thread.rb
|
|
124
|
+
- lib/odsl/audit/execution_context.rb
|
|
125
|
+
- lib/odsl/audit/serializer.rb
|
|
126
|
+
- lib/odsl/audit/step_record.rb
|
|
58
127
|
- lib/odsl/builder.rb
|
|
59
128
|
- lib/odsl/configurable.rb
|
|
129
|
+
- lib/odsl/configuration.rb
|
|
60
130
|
- lib/odsl/dsl.rb
|
|
61
|
-
- lib/odsl/
|
|
131
|
+
- lib/odsl/errors.rb
|
|
132
|
+
- lib/odsl/locking/lock_handle.rb
|
|
62
133
|
- lib/odsl/methods.rb
|
|
134
|
+
- lib/odsl/railtie.rb
|
|
63
135
|
- lib/odsl/runners.rb
|
|
64
136
|
- lib/odsl/version.rb
|
|
65
137
|
- odsl.gemspec
|
|
@@ -71,6 +143,7 @@ metadata:
|
|
|
71
143
|
homepage_uri: https://github.com/aleguro/ODSL
|
|
72
144
|
source_code_uri: https://github.com/aleguro/ODSL
|
|
73
145
|
changelog_uri: https://github.com/aleguro/ODSL/blob/master/CHANGELOG.md
|
|
146
|
+
rubygems_mfa_required: 'true'
|
|
74
147
|
post_install_message:
|
|
75
148
|
rdoc_options: []
|
|
76
149
|
require_paths:
|
|
@@ -79,14 +152,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
79
152
|
requirements:
|
|
80
153
|
- - ">="
|
|
81
154
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 2.
|
|
155
|
+
version: 3.2.0
|
|
83
156
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
157
|
requirements:
|
|
85
158
|
- - ">="
|
|
86
159
|
- !ruby/object:Gem::Version
|
|
87
160
|
version: '0'
|
|
88
161
|
requirements: []
|
|
89
|
-
rubygems_version: 3.
|
|
162
|
+
rubygems_version: 3.5.22
|
|
90
163
|
signing_key:
|
|
91
164
|
specification_version: 4
|
|
92
165
|
summary: DSL Approach to OOP and functional programming.
|
data/lib/odsl/error.rb
DELETED