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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df269c0cadf76b1981c02b87f77ac89c81b4dc13896c5162956942cfe274855f
4
- data.tar.gz: 2a864cb0aa3573e77f83d8050cc40ce93988c7100838bce05de73e4db0b99f3b
3
+ metadata.gz: a9fd49a85e50473450a09a741acaf01d5e6565116cb6f945f314bd6e4dd74f6d
4
+ data.tar.gz: d5655ec674b4a86d99b267c8a851e2a8d1cd687e188c58c8a292bf5120cffc3b
5
5
  SHA512:
6
- metadata.gz: 4881d10048838fecb729eb48309aacebd77c4071636f7077fe2e3f2f1f3a1f4a8cce0b90ccad6ae59446790c635880c9ede070b245eb5354b2bb9d8e2dedd02d
7
- data.tar.gz: 8245277be9b2e65d7b9479a2f9c9740a3167cca1be20e680530d9dedc9ea6fabb8f6cb597f7a7ecabfb633421ce88362118908faafc10e7f889af2f428d30e49
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
- - improve jobs logging capabilities;
11
- - query historical data about job executions;
12
- - extract job's statistical data;
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
- Support some customizations:
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 jobs in a specific range of time:
90
+ Query information from a job (even when it's performing):
64
91
 
65
92
  ```rb
66
- SomeJob.job_executions.where(started_at: 16.minutes.ago...).pluck(:job_id, :result, :started_at)
67
- # => [["02beb3d6-a4eb-442c-8d78-29103ab894dc", "some_result", Wed, 09 Nov 2022 21:20:57.576018000 UTC +00:00],
68
- # ["267e087e-cfa7-4c88-8d3b-9d40f912733f", "some_result", Wed, 09 Nov 2022 21:13:18.011484000 UTC +00:00]]
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
- Some statistics on completed jobs:
105
+ ## Features' details
106
+
107
+ To store the custom data (ex. a progress value):
72
108
 
73
109
  ```rb
74
- SomeJob.job_executions.completed.map { |job| { id: job.id, execution_time: job.completed_at - job.started_at, started_at: job.started_at } }
75
- # => [{:id=>6, :execution_time=>1.005239, :started_at=>Wed, 09 Nov 2022 21:20:57.576018000 UTC +00:00},
76
- # {:id=>4, :execution_time=>1.004485, :started_at=>Wed, 09 Nov 2022 21:13:18.011484000 UTC +00:00},
77
- # {:id=>1, :execution_time=>0.011442, :started_at=>Wed, 09 Nov 2022 21:09:50.611355000 UTC +00:00}]
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
- ## Customizations
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
- If you need to store custom data, use `active_job_store_custom_data` accessor:
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
- If for any reason it's needed to process the result before storing it, just override `active_job_store_format_result`:
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  # :nocov:
4
4
  module ActiveJobStore
5
- VERSION = '0.1.1'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  # :nocov:
@@ -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
- IGNORE_ATTRS = %w[arguments job_id successfully_enqueued].freeze
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
- store_record = ::ActiveJobStore::Record.find_or_create_by!(job.active_job_store_reference) do |record|
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
- store_record = ::ActiveJobStore::Record.find_or_initialize_by(job.active_job_store_reference) do |record|
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
- def active_job_store_format_result(result)
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
- module ClassMethods
54
- def job_executions
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.1.1
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 00:00:00.000000000 Z
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: