active_job_store 0.2.0 → 0.4.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/README.md +66 -23
- data/lib/active_job_store/store.rb +70 -24
- data/lib/active_job_store/version.rb +1 -1
- data/lib/active_job_store.rb +40 -24
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ef6d562f0f4a15a496ba433ec3c540e141936afb70c25c49d733989c5244662
|
4
|
+
data.tar.gz: bb6790c404ed7aecfc16a54838a01a25d366221e23898cb9bd52e73b2311de9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a88f03f2af219714771a7d9dcf320e72d99753f4327ce7f1c699ef8f0132bf62d124872e83ae2be589d88afba4f06aaecdc9506fc13cfe87b36e0777b46c4a3
|
7
|
+
data.tar.gz: b2544a3499f8a0d3a18b34fee58e171ef37cc3402890e1790746c75b97a6f22c236582185769738301aca986afa05e44fa27645a76a75f95480368dbee376aaf
|
data/README.md
CHANGED
@@ -7,14 +7,13 @@
|
|
7
7
|
Persist job execution information on a support model `ActiveJobStore::Record`.
|
8
8
|
|
9
9
|
It can be useful to:
|
10
|
-
-
|
11
|
-
- query historical data about job executions;
|
12
|
-
-
|
13
|
-
- track a job's state / set progress value / add custom data to the jobs.
|
10
|
+
- store the job's state / set progress value / add custom data to the jobs;
|
11
|
+
- query historical data about job executions / extract job's statistical data;
|
12
|
+
- improve jobs' logging capabilities.
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
By default gem's internal errors are sent to stderr without compromising the job's perform.
|
15
|
+
|
16
|
+
Please ⭐ if you like it.
|
18
17
|
|
19
18
|
## Installation
|
20
19
|
|
@@ -24,6 +23,20 @@ Support some customizations:
|
|
24
23
|
- Add to your job `include ActiveJobStore` (or to your `ApplicationJob` class if you prefer)
|
25
24
|
- Access to the job executions data using the class method `job_executions` on your job (ex. `YourJob.job_executions`)
|
26
25
|
|
26
|
+
## API
|
27
|
+
|
28
|
+
attr_accessor on the jobs:
|
29
|
+
- `active_job_store_custom_data`: to set / manipulate job's custom data
|
30
|
+
|
31
|
+
Instance methods on the jobs:
|
32
|
+
- `active_job_store_format_result(result) => result2`: to format / manipulate / serialize the job result
|
33
|
+
- `active_job_store_internal_error(context, exception)`: handler for internal errors
|
34
|
+
- `active_job_store_record => store record`: returns the store's record
|
35
|
+
- `save_job_custom_data(custom_data = nil)`: to persist custom data while the job is performing
|
36
|
+
|
37
|
+
Class methods on the jobs:
|
38
|
+
- `job_executions => relation`: query the list of job executions for the specific job class (returns an ActiveRecord Relation)
|
39
|
+
|
27
40
|
## Usage examples
|
28
41
|
|
29
42
|
```rb
|
@@ -48,18 +61,6 @@ SomeJob.job_executions.first
|
|
48
61
|
# created_at: Wed, 09 Nov 2022 21:09:50.611900000 UTC +00:00>
|
49
62
|
```
|
50
63
|
|
51
|
-
Extract some logs:
|
52
|
-
|
53
|
-
```rb
|
54
|
-
puts ::ActiveJobStore::Record.order(id: :desc).pluck(:created_at, :job_class, :arguments, :state, :completed_at).map { _1.join(', ') }
|
55
|
-
# 2022-11-09 21:20:57 UTC, SomeJob, 123, completed, 2022-11-09 21:20:58 UTC
|
56
|
-
# 2022-11-09 21:18:26 UTC, AnotherJob, another test 2, completed, 2022-11-09 21:18:26 UTC
|
57
|
-
# 2022-11-09 21:13:18 UTC, SomeJob, Some test 3, completed, 2022-11-09 21:13:19 UTC
|
58
|
-
# 2022-11-09 21:12:18 UTC, SomeJob, Some test 2, error,
|
59
|
-
# 2022-11-09 21:10:13 UTC, AnotherJob, another test, completed, 2022-11-09 21:10:13 UTC
|
60
|
-
# 2022-11-09 21:09:50 UTC, SomeJob, Some test, completed, 2022-11-09 21:09:50 UTC
|
61
|
-
```
|
62
|
-
|
63
64
|
Query jobs in a specific range of time:
|
64
65
|
|
65
66
|
```rb
|
@@ -77,9 +78,36 @@ SomeJob.job_executions.completed.map { |job| { id: job.id, execution_time: job.c
|
|
77
78
|
# {:id=>1, :execution_time=>0.011442, :started_at=>Wed, 09 Nov 2022 21:09:50.611355000 UTC +00:00}]
|
78
79
|
```
|
79
80
|
|
80
|
-
|
81
|
+
Extract some logs:
|
81
82
|
|
82
|
-
|
83
|
+
```rb
|
84
|
+
puts ::ActiveJobStore::Record.order(id: :desc).pluck(:created_at, :job_class, :arguments, :state, :completed_at).map { _1.join(', ') }
|
85
|
+
# 2022-11-09 21:20:57 UTC, SomeJob, 123, completed, 2022-11-09 21:20:58 UTC
|
86
|
+
# 2022-11-09 21:18:26 UTC, AnotherJob, another test 2, completed, 2022-11-09 21:18:26 UTC
|
87
|
+
# 2022-11-09 21:13:18 UTC, SomeJob, Some test 3, completed, 2022-11-09 21:13:19 UTC
|
88
|
+
# 2022-11-09 21:12:18 UTC, SomeJob, Some test 2, error,
|
89
|
+
# 2022-11-09 21:10:13 UTC, AnotherJob, another test, completed, 2022-11-09 21:10:13 UTC
|
90
|
+
# 2022-11-09 21:09:50 UTC, SomeJob, Some test, completed, 2022-11-09 21:09:50 UTC
|
91
|
+
```
|
92
|
+
|
93
|
+
Query information from a job (even while performing):
|
94
|
+
|
95
|
+
```rb
|
96
|
+
job = SomeJob.perform_later 123
|
97
|
+
job.active_job_store_record.slice(:job_id, :job_class, :arguments)
|
98
|
+
# => {"job_id"=>"b009f7c7-a264-4fb5-a1f8-68a8141f323b", "job_class"=>"SomeJob", "arguments"=>[123]}
|
99
|
+
|
100
|
+
job = AnotherJob.perform_later 456
|
101
|
+
job.active_job_store_record.custom_data
|
102
|
+
# => {"progress"=>0.5}
|
103
|
+
### After a while:
|
104
|
+
job.active_job_store_record.reload.custom_data
|
105
|
+
# => {"progress"=>1.0}
|
106
|
+
```
|
107
|
+
|
108
|
+
## Features' examples
|
109
|
+
|
110
|
+
To persist some custom data during the perform (ex. a progress value):
|
83
111
|
|
84
112
|
```rb
|
85
113
|
class AnotherJob < ApplicationJob
|
@@ -100,7 +128,7 @@ AnotherJob.perform_later(456)
|
|
100
128
|
AnotherJob.job_executions.last.custom_data['progress'] # 1.0 (at the end)
|
101
129
|
```
|
102
130
|
|
103
|
-
|
131
|
+
To manipulate the custom data persisted only at the end:
|
104
132
|
|
105
133
|
```rb
|
106
134
|
class AnotherJob < ApplicationJob
|
@@ -123,7 +151,7 @@ AnotherJob.job_executions.last.custom_data
|
|
123
151
|
# => [{"time"=>"2022-11-09T21:20:57.580Z", "message"=>"SomeJob step 1"}, {"time"=>"2022-11-09T21:20:58.581Z", "message"=>"SomeJob step 2"}]
|
124
152
|
```
|
125
153
|
|
126
|
-
|
154
|
+
To process the job's result before storing it (ex. for serialization):
|
127
155
|
|
128
156
|
```rb
|
129
157
|
class AnotherJob < ApplicationJob
|
@@ -144,6 +172,21 @@ AnotherJob.job_executions.last.result
|
|
144
172
|
# => 84
|
145
173
|
```
|
146
174
|
|
175
|
+
To raise an exception also when there is a gem's internal error:
|
176
|
+
|
177
|
+
```rb
|
178
|
+
class AnotherJob < ApplicationJob
|
179
|
+
include ActiveJobStore
|
180
|
+
|
181
|
+
# ...
|
182
|
+
|
183
|
+
def active_job_store_internal_error(context, exception)
|
184
|
+
raise exception
|
185
|
+
# Or simply monitor these errors using services like Sentry/Honeybadger/etc.
|
186
|
+
end
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
147
190
|
## Do you like it? Star it!
|
148
191
|
|
149
192
|
If you use this component just star it. A developer is more motivated to improve a project when there is some interest.
|
@@ -6,55 +6,101 @@ module ActiveJobStore
|
|
6
6
|
|
7
7
|
attr_reader :record
|
8
8
|
|
9
|
+
def around_enqueue(job)
|
10
|
+
prepare_record_on_enqueue(job)
|
11
|
+
lock_record!
|
12
|
+
result = yield
|
13
|
+
job_enqueued!
|
14
|
+
job.active_job_store_internal_error('ActiveJobStore::Store around_enqueue', internal_error) if internal_error
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
def around_perform(job)
|
19
|
+
prepare_record_on_perform(job)
|
20
|
+
job_started!
|
21
|
+
result = yield
|
22
|
+
formatted_result = job.active_job_store_format_result(result)
|
23
|
+
job_competed!(custom_data: job.active_job_store_custom_data, result: formatted_result)
|
24
|
+
job.active_job_store_internal_error('ActiveJobStore::Store around_perform', internal_error) if internal_error
|
25
|
+
result
|
26
|
+
rescue StandardError => e
|
27
|
+
job_failed!(exception: e, custom_data: job.active_job_store_custom_data)
|
28
|
+
raise
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_job_custom_data(custom_data)
|
32
|
+
wrap_errors do
|
33
|
+
record.update!(custom_data: custom_data)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_accessor :internal_error
|
40
|
+
|
9
41
|
def job_competed!(result:, custom_data:)
|
10
|
-
|
11
|
-
|
42
|
+
wrap_errors do
|
43
|
+
record.update!(state: :completed, completed_at: Time.current, result: result, custom_data: custom_data)
|
44
|
+
end
|
12
45
|
end
|
13
46
|
|
14
47
|
def job_enqueued!
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
record
|
48
|
+
wrap_errors do
|
49
|
+
record.update!(state: :enqueued, enqueued_at: Time.current)
|
50
|
+
end
|
19
51
|
end
|
20
52
|
|
21
53
|
def job_failed!(exception:, custom_data:)
|
22
|
-
|
23
|
-
|
54
|
+
wrap_errors do
|
55
|
+
record.update!(state: :error, exception: exception.inspect, custom_data: custom_data)
|
56
|
+
end
|
24
57
|
end
|
25
58
|
|
26
59
|
def job_started!
|
27
|
-
|
28
|
-
|
60
|
+
wrap_errors do
|
61
|
+
record.update!(state: :started, started_at: Time.current)
|
62
|
+
end
|
29
63
|
end
|
30
64
|
|
31
|
-
def
|
32
|
-
|
33
|
-
record.
|
34
|
-
record.details = DETAILS_ATTRS.zip(DETAILS_ATTRS.map { job.send(_1) }).to_h
|
35
|
-
record.state = :initialized
|
65
|
+
def lock_record!
|
66
|
+
wrap_errors do
|
67
|
+
record.lock! # NOTE: needed to avoid update conflicts with perform when setting the state to enqueued
|
36
68
|
end
|
37
69
|
end
|
38
70
|
|
39
|
-
def
|
40
|
-
|
41
|
-
record
|
71
|
+
def prepare_record_on_enqueue(job)
|
72
|
+
wrap_errors do
|
73
|
+
@record = ::ActiveJobStore::Record.find_or_create_by!(record_reference(job)) do |record|
|
74
|
+
record.arguments = job.arguments
|
75
|
+
record.details = DETAILS_ATTRS.zip(DETAILS_ATTRS.map { job.send(_1) }).to_h
|
76
|
+
record.state = :initialized
|
77
|
+
end
|
42
78
|
end
|
43
|
-
record.details = DETAILS_ATTRS.zip(DETAILS_ATTRS.map { job.send(_1) }).to_h
|
44
|
-
record
|
45
79
|
end
|
46
80
|
|
47
|
-
def
|
48
|
-
|
81
|
+
def prepare_record_on_perform(job)
|
82
|
+
wrap_errors do
|
83
|
+
@record = ::ActiveJobStore::Record.find_or_initialize_by(record_reference(job)) do |record|
|
84
|
+
record.arguments = job.arguments
|
85
|
+
end
|
86
|
+
record.details = DETAILS_ATTRS.zip(DETAILS_ATTRS.map { job.send(_1) }).to_h
|
87
|
+
record
|
88
|
+
end
|
49
89
|
end
|
50
90
|
|
51
|
-
private
|
52
|
-
|
53
91
|
def record_reference(job)
|
54
92
|
{
|
55
93
|
job_id: job.job_id,
|
56
94
|
job_class: job.class.to_s
|
57
95
|
}
|
58
96
|
end
|
97
|
+
|
98
|
+
def wrap_errors
|
99
|
+
return if internal_error
|
100
|
+
|
101
|
+
yield
|
102
|
+
rescue StandardError => e
|
103
|
+
self.internal_error = e
|
104
|
+
end
|
59
105
|
end
|
60
106
|
end
|
data/lib/active_job_store.rb
CHANGED
@@ -4,47 +4,63 @@ require_relative 'active_job_store/engine'
|
|
4
4
|
require_relative 'active_job_store/store'
|
5
5
|
|
6
6
|
module ActiveJobStore
|
7
|
+
# Set / manipulate job's custom data
|
7
8
|
attr_accessor :active_job_store_custom_data
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
store.prepare_record_on_enqueue(job)
|
15
|
-
store.job_enqueued! do
|
16
|
-
block.call
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
base.around_perform do |job, block|
|
21
|
-
store.prepare_record_on_perform(job)
|
22
|
-
store.job_started!
|
23
|
-
result = block.call
|
24
|
-
formatted_result = job.active_job_store_format_result(result)
|
25
|
-
store.job_competed!(custom_data: active_job_store_custom_data, result: formatted_result)
|
26
|
-
rescue StandardError => e
|
27
|
-
store.job_failed!(exception: e, custom_data: active_job_store_custom_data)
|
28
|
-
raise
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
10
|
+
# Format / manipulate / serialize the job result
|
11
|
+
#
|
12
|
+
# @param result [any] Job's return value
|
13
|
+
#
|
14
|
+
# @return [any] Processed job's return value
|
33
15
|
def active_job_store_format_result(result)
|
34
16
|
result
|
35
17
|
end
|
36
18
|
|
19
|
+
# Persist custom data while the job is performing
|
20
|
+
#
|
21
|
+
# @param custom_data [any] Attributes to serialize (it must be serializable in JSON)
|
37
22
|
def save_job_custom_data(custom_data = nil)
|
38
23
|
self.active_job_store_custom_data = custom_data if custom_data
|
39
24
|
store.update_job_custom_data(active_job_store_custom_data)
|
40
25
|
end
|
41
26
|
|
27
|
+
# Return the associated Active Job Store record
|
28
|
+
#
|
29
|
+
# @return [ActiveJobStore::Record] the corresponding record
|
30
|
+
def active_job_store_record
|
31
|
+
store.record
|
32
|
+
end
|
33
|
+
|
34
|
+
# Internal errors handler method
|
35
|
+
#
|
36
|
+
# @param exception [exception] The internal exception
|
37
|
+
def active_job_store_internal_error(context, exception)
|
38
|
+
warn("#{context}: #{exception}")
|
39
|
+
end
|
40
|
+
|
42
41
|
module ClassMethods
|
42
|
+
# Query the list of job executions for the current job class
|
43
|
+
#
|
44
|
+
# @return [ActiveRecord Relation] query result
|
43
45
|
def job_executions
|
44
46
|
::ActiveJobStore::Record.where(job_class: to_s)
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
50
|
+
class << self
|
51
|
+
def included(base)
|
52
|
+
base.extend(ClassMethods)
|
53
|
+
|
54
|
+
base.around_enqueue do |job, block|
|
55
|
+
store.around_enqueue(job) { block.call }
|
56
|
+
end
|
57
|
+
|
58
|
+
base.around_perform do |job, block|
|
59
|
+
store.around_perform(job) { block.call }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
48
64
|
private
|
49
65
|
|
50
66
|
def store
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_job_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mattia Roccoberton
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-11-
|
11
|
+
date: 2022-11-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '6.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '6.0'
|
27
41
|
description: ActiveJob Store permits to store jobs state and custom data on a database
|
28
42
|
email: mat@blocknot.es
|
29
43
|
executables: []
|
@@ -46,7 +60,7 @@ metadata:
|
|
46
60
|
source_code_uri: https://github.com/blocknotes/active_job_store
|
47
61
|
changelog_uri: https://github.com/blocknotes/active_job_store/blob/master/CHANGELOG.md
|
48
62
|
rubygems_mfa_required: 'true'
|
49
|
-
post_install_message:
|
63
|
+
post_install_message:
|
50
64
|
rdoc_options: []
|
51
65
|
require_paths:
|
52
66
|
- lib
|
@@ -62,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
76
|
version: '0'
|
63
77
|
requirements: []
|
64
78
|
rubygems_version: 3.1.6
|
65
|
-
signing_key:
|
79
|
+
signing_key:
|
66
80
|
specification_version: 4
|
67
81
|
summary: Persist jobs information on DB
|
68
82
|
test_files: []
|