active_job_store 0.2.0 → 0.4.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: 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: []