active_job_store 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: