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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 913b4d81b0dfb938f013f907696dc04354a106818140d25003ae44ab4e4ed9f9
4
- data.tar.gz: 39874892b9ee2443311d54cfedf7589697959c5ccb4184d8d934d67960838c7d
3
+ metadata.gz: 0ef6d562f0f4a15a496ba433ec3c540e141936afb70c25c49d733989c5244662
4
+ data.tar.gz: bb6790c404ed7aecfc16a54838a01a25d366221e23898cb9bd52e73b2311de9e
5
5
  SHA512:
6
- metadata.gz: 18b9c5696a4b4092ae9ea5d6ae8ed74ed45aaef567804f53a7b59fee306038f6ce2e63f0832b9bdc6f6ad61dbb7d69b81239c5801fe39db2b163be50f0e468ad
7
- data.tar.gz: 0ad46829f63b6a6347812df19f0b0a3ef8b9e0c092ecf6f8f1743b08ddfeef9077735cdcb19690421222ca9557b2c6338b9e122d7e747f76a50d3357d62e3534
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
- - improve jobs logging capabilities;
11
- - query historical data about job executions;
12
- - extract job's statistical data;
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
- 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
+ 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
- ## Customizations
81
+ Extract some logs:
81
82
 
82
- To store the custom data (ex. a progress value):
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
- If you need to manipulate the custom data, there is the `active_job_store_custom_data` accessor:
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
- If for any reason it's needed to process the result before storing it, just override `active_job_store_format_result`:
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
- record.update!(state: :completed, completed_at: Time.current, result: result, custom_data: custom_data)
11
- record
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
- record.lock! # NOTE: needed to avoid update conflicts with perform when setting the state to enqueued
16
- yield
17
- record.update!(state: :enqueued, enqueued_at: Time.current)
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
- record.update!(state: :error, exception: exception.inspect, custom_data: custom_data)
23
- record
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
- record.update!(state: :started, started_at: Time.current)
28
- record
60
+ wrap_errors do
61
+ record.update!(state: :started, started_at: Time.current)
62
+ end
29
63
  end
30
64
 
31
- def prepare_record_on_enqueue(job)
32
- @record = ::ActiveJobStore::Record.find_or_create_by!(record_reference(job)) do |record|
33
- record.arguments = job.arguments
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 prepare_record_on_perform(job)
40
- @record = ::ActiveJobStore::Record.find_or_initialize_by(record_reference(job)) do |record|
41
- record.arguments = job.arguments
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 update_job_custom_data(custom_data)
48
- record.update!(custom_data: custom_data)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  # :nocov:
4
4
  module ActiveJobStore
5
- VERSION = '0.2.0'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  # :nocov:
@@ -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
- class << self
10
- def included(base)
11
- base.extend(ClassMethods)
12
-
13
- base.around_enqueue do |job, block|
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.2.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-12 00:00:00.000000000 Z
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: []