active_job_store 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/README.md +68 -20
- data/lib/active_job_store/store.rb +76 -0
- data/lib/active_job_store/version.rb +1 -1
- data/lib/active_job_store.rb +40 -36
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9fd49a85e50473450a09a741acaf01d5e6565116cb6f945f314bd6e4dd74f6d
|
4
|
+
data.tar.gz: d5655ec674b4a86d99b267c8a851e2a8d1cd687e188c58c8a292bf5120cffc3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9e1a166e181d32afb0265ac062b954930b8d748af36b21e837d2ee6fe28a7d57edc2f9ad590339e49fefbc3256e6b43317d19ab4e9ff1de36e7f2aa67d0dac4
|
7
|
+
data.tar.gz: a407a4446dec032e4bf6f7b5f55536dadd703c0d1d1a75d61cdf37ace3388e09c836c520ffde6c0b31043867bd92c82ad04c57b54d7398ca126baa080715e0a5
|
data/README.md
CHANGED
@@ -7,14 +7,11 @@
|
|
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 or 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
|
-
- set custom data attributes (via `active_job_store_custom_data` accessor);
|
17
|
-
- format the job result to store (overriding `active_job_store_format_result` method).
|
14
|
+
Please ⭐ if you like it.
|
18
15
|
|
19
16
|
## Installation
|
20
17
|
|
@@ -24,6 +21,19 @@ Support some customizations:
|
|
24
21
|
- Add to your job `include ActiveJobStore` (or to your `ApplicationJob` class if you prefer)
|
25
22
|
- Access to the job executions data using the class method `job_executions` on your job (ex. `YourJob.job_executions`)
|
26
23
|
|
24
|
+
## API
|
25
|
+
|
26
|
+
attr_accessor on the jobs:
|
27
|
+
- `active_job_store_custom_data`: to set / manipulate job's custom data
|
28
|
+
|
29
|
+
Instance methods on the jobs:
|
30
|
+
- `active_job_store_format_result(result) => result2`: to format / manipulate / serialize the job result
|
31
|
+
- `active_job_store_record => store record`: returns the store's record
|
32
|
+
- `save_job_custom_data(custom_data = nil)`: to persist custom data while the job is performing
|
33
|
+
|
34
|
+
Class methods on the jobs:
|
35
|
+
- `job_executions => relation`: query the list of job executions for the specific job class (returns an ActiveRecord Relation)
|
36
|
+
|
27
37
|
## Usage examples
|
28
38
|
|
29
39
|
```rb
|
@@ -48,6 +58,23 @@ SomeJob.job_executions.first
|
|
48
58
|
# created_at: Wed, 09 Nov 2022 21:09:50.611900000 UTC +00:00>
|
49
59
|
```
|
50
60
|
|
61
|
+
Query jobs in a specific range of time:
|
62
|
+
|
63
|
+
```rb
|
64
|
+
SomeJob.job_executions.where(started_at: 16.minutes.ago...).pluck(:job_id, :result, :started_at)
|
65
|
+
# => [["02beb3d6-a4eb-442c-8d78-29103ab894dc", "some_result", Wed, 09 Nov 2022 21:20:57.576018000 UTC +00:00],
|
66
|
+
# ["267e087e-cfa7-4c88-8d3b-9d40f912733f", "some_result", Wed, 09 Nov 2022 21:13:18.011484000 UTC +00:00]]
|
67
|
+
```
|
68
|
+
|
69
|
+
Some statistics on completed jobs:
|
70
|
+
|
71
|
+
```rb
|
72
|
+
SomeJob.job_executions.completed.map { |job| { id: job.id, execution_time: job.completed_at - job.started_at, started_at: job.started_at } }
|
73
|
+
# => [{:id=>6, :execution_time=>1.005239, :started_at=>Wed, 09 Nov 2022 21:20:57.576018000 UTC +00:00},
|
74
|
+
# {:id=>4, :execution_time=>1.004485, :started_at=>Wed, 09 Nov 2022 21:13:18.011484000 UTC +00:00},
|
75
|
+
# {:id=>1, :execution_time=>0.011442, :started_at=>Wed, 09 Nov 2022 21:09:50.611355000 UTC +00:00}]
|
76
|
+
```
|
77
|
+
|
51
78
|
Extract some logs:
|
52
79
|
|
53
80
|
```rb
|
@@ -60,26 +87,45 @@ puts ::ActiveJobStore::Record.order(id: :desc).pluck(:created_at, :job_class, :a
|
|
60
87
|
# 2022-11-09 21:09:50 UTC, SomeJob, Some test, completed, 2022-11-09 21:09:50 UTC
|
61
88
|
```
|
62
89
|
|
63
|
-
Query
|
90
|
+
Query information from a job (even when it's performing):
|
64
91
|
|
65
92
|
```rb
|
66
|
-
SomeJob.
|
67
|
-
|
68
|
-
#
|
93
|
+
job = SomeJob.perform_later 123
|
94
|
+
job.active_job_store_record.slice(:job_id, :job_class, :arguments)
|
95
|
+
# => {"job_id"=>"b009f7c7-a264-4fb5-a1f8-68a8141f323b", "job_class"=>"SomeJob", "arguments"=>[123]}
|
96
|
+
|
97
|
+
job = AnotherJob.perform_later 456
|
98
|
+
job.active_job_store_record.custom_data
|
99
|
+
# => {"progress"=>0.5}
|
100
|
+
### After a while:
|
101
|
+
job.active_job_store_record.reload.custom_data
|
102
|
+
# => {"progress"=>1.0}
|
69
103
|
```
|
70
104
|
|
71
|
-
|
105
|
+
## Features' details
|
106
|
+
|
107
|
+
To store the custom data (ex. a progress value):
|
72
108
|
|
73
109
|
```rb
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
110
|
+
class AnotherJob < ApplicationJob
|
111
|
+
include ActiveJobStore
|
112
|
+
|
113
|
+
def perform
|
114
|
+
# do something...
|
115
|
+
save_job_custom_data(progress: 0.5)
|
116
|
+
# do something else...
|
117
|
+
save_job_custom_data(progress: 1.0)
|
79
118
|
|
80
|
-
|
119
|
+
'some_result'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Usage example:
|
124
|
+
AnotherJob.perform_later(456)
|
125
|
+
AnotherJob.job_executions.last.custom_data['progress'] # 1.0 (at the end)
|
126
|
+
```
|
81
127
|
|
82
|
-
|
128
|
+
To manipulate the custom data, there is the `active_job_store_custom_data` accessor:
|
83
129
|
|
84
130
|
```rb
|
85
131
|
class AnotherJob < ApplicationJob
|
@@ -96,12 +142,13 @@ class AnotherJob < ApplicationJob
|
|
96
142
|
end
|
97
143
|
end
|
98
144
|
|
145
|
+
# Usage example:
|
99
146
|
AnotherJob.perform_now(123)
|
100
147
|
AnotherJob.job_executions.last.custom_data
|
101
148
|
# => [{"time"=>"2022-11-09T21:20:57.580Z", "message"=>"SomeJob step 1"}, {"time"=>"2022-11-09T21:20:58.581Z", "message"=>"SomeJob step 2"}]
|
102
149
|
```
|
103
150
|
|
104
|
-
|
151
|
+
To process the result before storing it (ex. for serialization), override `active_job_store_format_result`:
|
105
152
|
|
106
153
|
```rb
|
107
154
|
class AnotherJob < ApplicationJob
|
@@ -116,6 +163,7 @@ class AnotherJob < ApplicationJob
|
|
116
163
|
end
|
117
164
|
end
|
118
165
|
|
166
|
+
# Usage example:
|
119
167
|
AnotherJob.perform_now(123)
|
120
168
|
AnotherJob.job_executions.last.result
|
121
169
|
# => 84
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJobStore
|
4
|
+
class Store
|
5
|
+
DETAILS_ATTRS = %w[exception_executions executions priority queue_name scheduled_at timezone].freeze
|
6
|
+
|
7
|
+
attr_reader :record
|
8
|
+
|
9
|
+
def around_enqueue(job)
|
10
|
+
prepare_record_on_enqueue(job)
|
11
|
+
record.lock! # NOTE: needed to avoid update conflicts with perform when setting the state to enqueued
|
12
|
+
yield
|
13
|
+
job_enqueued!
|
14
|
+
end
|
15
|
+
|
16
|
+
def around_perform(job)
|
17
|
+
prepare_record_on_perform(job)
|
18
|
+
job_started!
|
19
|
+
result = yield
|
20
|
+
formatted_result = job.active_job_store_format_result(result)
|
21
|
+
job_competed!(custom_data: job.active_job_store_custom_data, result: formatted_result)
|
22
|
+
rescue StandardError => e
|
23
|
+
job_failed!(exception: e, custom_data: job.active_job_store_custom_data)
|
24
|
+
raise
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_job_custom_data(custom_data)
|
28
|
+
record.update!(custom_data: custom_data)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def job_competed!(result:, custom_data:)
|
34
|
+
record.update!(state: :completed, completed_at: Time.current, result: result, custom_data: custom_data)
|
35
|
+
record
|
36
|
+
end
|
37
|
+
|
38
|
+
def job_enqueued!
|
39
|
+
record.update!(state: :enqueued, enqueued_at: Time.current)
|
40
|
+
record
|
41
|
+
end
|
42
|
+
|
43
|
+
def job_failed!(exception:, custom_data:)
|
44
|
+
record.update!(state: :error, exception: exception.inspect, custom_data: custom_data)
|
45
|
+
record
|
46
|
+
end
|
47
|
+
|
48
|
+
def job_started!
|
49
|
+
record.update!(state: :started, started_at: Time.current)
|
50
|
+
record
|
51
|
+
end
|
52
|
+
|
53
|
+
def prepare_record_on_enqueue(job)
|
54
|
+
@record = ::ActiveJobStore::Record.find_or_create_by!(record_reference(job)) do |record|
|
55
|
+
record.arguments = job.arguments
|
56
|
+
record.details = DETAILS_ATTRS.zip(DETAILS_ATTRS.map { job.send(_1) }).to_h
|
57
|
+
record.state = :initialized
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def prepare_record_on_perform(job)
|
62
|
+
@record = ::ActiveJobStore::Record.find_or_initialize_by(record_reference(job)) do |record|
|
63
|
+
record.arguments = job.arguments
|
64
|
+
end
|
65
|
+
record.details = DETAILS_ATTRS.zip(DETAILS_ATTRS.map { job.send(_1) }).to_h
|
66
|
+
record
|
67
|
+
end
|
68
|
+
|
69
|
+
def record_reference(job)
|
70
|
+
{
|
71
|
+
job_id: job.job_id,
|
72
|
+
job_class: job.class.to_s
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/active_job_store.rb
CHANGED
@@ -1,58 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'active_job_store/engine'
|
4
|
+
require_relative 'active_job_store/store'
|
4
5
|
|
5
6
|
module ActiveJobStore
|
6
|
-
|
7
|
-
|
7
|
+
# Set / manipulate job's custom data
|
8
8
|
attr_accessor :active_job_store_custom_data
|
9
9
|
|
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
|
15
|
+
def active_job_store_format_result(result)
|
16
|
+
result
|
17
|
+
end
|
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)
|
22
|
+
def save_job_custom_data(custom_data = nil)
|
23
|
+
self.active_job_store_custom_data = custom_data if custom_data
|
24
|
+
store.update_job_custom_data(active_job_store_custom_data)
|
25
|
+
end
|
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
|
+
module ClassMethods
|
35
|
+
# Query the list of job executions for the current job class
|
36
|
+
#
|
37
|
+
# @return [ActiveRecord Relation] query result
|
38
|
+
def job_executions
|
39
|
+
::ActiveJobStore::Record.where(job_class: to_s)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
10
43
|
class << self
|
11
44
|
def included(base)
|
12
45
|
base.extend(ClassMethods)
|
13
46
|
|
14
47
|
base.around_enqueue do |job, block|
|
15
|
-
|
16
|
-
record.arguments = job.arguments
|
17
|
-
record.details = job.as_json.except(*IGNORE_ATTRS)
|
18
|
-
record.state = :initialized
|
19
|
-
end
|
20
|
-
store_record.lock! # NOTE: needed to avoid update conflicts with perform when setting the state to enqueued
|
21
|
-
block.call
|
22
|
-
store_record.update!(state: :enqueued, enqueued_at: Time.current)
|
48
|
+
store.around_enqueue(job) { block.call }
|
23
49
|
end
|
24
50
|
|
25
51
|
base.around_perform do |job, block|
|
26
|
-
|
27
|
-
record.arguments = job.arguments
|
28
|
-
end
|
29
|
-
store_record.update!(details: job.as_json.except(*IGNORE_ATTRS), state: :started, started_at: Time.current)
|
30
|
-
result = block.call
|
31
|
-
formatted_result = job.active_job_store_format_result(result)
|
32
|
-
store_record.update!(
|
33
|
-
state: :completed,
|
34
|
-
completed_at: Time.current,
|
35
|
-
result: formatted_result,
|
36
|
-
custom_data: active_job_store_custom_data
|
37
|
-
)
|
38
|
-
rescue StandardError => e
|
39
|
-
store_record.update!(state: :error, exception: e.inspect, custom_data: active_job_store_custom_data)
|
40
|
-
raise
|
52
|
+
store.around_perform(job) { block.call }
|
41
53
|
end
|
42
54
|
end
|
43
55
|
end
|
44
56
|
|
45
|
-
|
46
|
-
result
|
47
|
-
end
|
48
|
-
|
49
|
-
def active_job_store_reference
|
50
|
-
{ job_id: job_id, job_class: self.class.to_s }
|
51
|
-
end
|
57
|
+
private
|
52
58
|
|
53
|
-
|
54
|
-
|
55
|
-
::ActiveJobStore::Record.where(job_class: to_s)
|
56
|
-
end
|
59
|
+
def store
|
60
|
+
@store ||= ::ActiveJobStore::Store.new
|
57
61
|
end
|
58
62
|
end
|
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.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mattia Roccoberton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-11-
|
11
|
+
date: 2022-11-15 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: []
|
@@ -36,6 +50,7 @@ files:
|
|
36
50
|
- db/migrate/20221101010101_create_active_job_store.rb
|
37
51
|
- lib/active_job_store.rb
|
38
52
|
- lib/active_job_store/engine.rb
|
53
|
+
- lib/active_job_store/store.rb
|
39
54
|
- lib/active_job_store/version.rb
|
40
55
|
homepage: https://github.com/blocknotes/active_job_store
|
41
56
|
licenses:
|