job-iteration 1.1.4 → 1.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -2
- data/CHANGELOG.md +21 -0
- data/Gemfile +3 -0
- data/README.md +12 -6
- data/dev.yml +1 -1
- data/gemfiles/rails_6_0.gemfile +6 -0
- data/guides/custom-enumerator.md +3 -1
- data/job-iteration.gemspec +1 -0
- data/lib/job-iteration/active_record_cursor.rb +2 -0
- data/lib/job-iteration/csv_enumerator.rb +9 -5
- data/lib/job-iteration/iteration.rb +22 -2
- data/lib/job-iteration/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a4821d55797f7597b3c916d6fbca2195c054bd3aab0ffcc40c9ce21ae0b1ec4
|
4
|
+
data.tar.gz: e71ce0fb7ec744b502f018ccfc7ea5e819bfe06f9fb2ca5a32c70cc1b875abc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 943795fabf60cff76fa7b8678f1f5d5a83bd98340194e142092dc891ff1abe991c0410572cebd09155c6c220ff96aa5530fb27e1b4fe119c6bbbf22fe12670ef
|
7
|
+
data.tar.gz: 11ea6f0165aa31350c6486236a33d66c94316a08f52905aeaab049388b433c229ccf52e69ae3e43eb8bca9a5c56dc4aae46803aae4add9d2a052d5d49af14d8d
|
data/.travis.yml
CHANGED
@@ -3,8 +3,9 @@ services:
|
|
3
3
|
- redis-server
|
4
4
|
language: ruby
|
5
5
|
rvm:
|
6
|
-
- 2.5
|
7
|
-
- 2.6
|
6
|
+
- 2.5
|
7
|
+
- 2.6
|
8
|
+
- 2.7
|
8
9
|
before_install:
|
9
10
|
- mysql -e 'CREATE DATABASE job_iteration_test;'
|
10
11
|
script:
|
@@ -14,4 +15,5 @@ script:
|
|
14
15
|
|
15
16
|
gemfile:
|
16
17
|
- 'gemfiles/rails_5_2.gemfile'
|
18
|
+
- 'gemfiles/rails_6_0.gemfile'
|
17
19
|
- 'gemfiles/rails_edge.gemfile'
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,27 @@
|
|
4
4
|
|
5
5
|
#### Bug fix
|
6
6
|
|
7
|
+
## v1.1.9 (January 6, 2021)
|
8
|
+
|
9
|
+
- [61](https://github.com/Shopify/job-iteration/pull/61) Call `super` in `method_added`
|
10
|
+
|
11
|
+
## v1.1.8 (June 8, 2020)
|
12
|
+
|
13
|
+
- Preserve ruby2_keywords tags in arguments on Ruby 2.7
|
14
|
+
|
15
|
+
## v1.1.7 (June 4, 2020)
|
16
|
+
|
17
|
+
- [54](https://github.com/Shopify/job-iteration/pull/54) - Fix warnings on Ruby 2.7
|
18
|
+
|
19
|
+
## v1.1.6 (May 22, 2020)
|
20
|
+
|
21
|
+
- [49](https://github.com/Shopify/job-iteration/pull/49) - Log when enumerator has nothing to iterate
|
22
|
+
- [52](https://github.com/Shopify/job-iteration/pull/52) - Fix CSVEnumerator cursor to properly remove already processed rows
|
23
|
+
|
24
|
+
## v1.1.5 (February 27, 2020)
|
25
|
+
|
26
|
+
- [47](https://github.com/Shopify/job-iteration/pull/47) - Optional `sorbet-runtime` support for `JobIteration::Iteration` interface validation
|
27
|
+
|
7
28
|
## v1.1.4 (December 13, 2019)
|
8
29
|
|
9
30
|
- [45](https://github.com/Shopify/job-iteration/pull/45) - Add Throttle enumerator
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@ Meet Iteration, an extension for [ActiveJob](https://github.com/rails/rails/tree
|
|
9
9
|
Imagine the following job:
|
10
10
|
|
11
11
|
```ruby
|
12
|
-
class SimpleJob <
|
12
|
+
class SimpleJob < ApplicationJob
|
13
13
|
def perform
|
14
14
|
User.find_each do |user|
|
15
15
|
user.notify_about_something
|
@@ -43,7 +43,7 @@ And then execute:
|
|
43
43
|
In the job, include `JobIteration::Iteration` module and start describing the job with two methods (`build_enumerator` and `each_iteration`) instead of `perform`:
|
44
44
|
|
45
45
|
```ruby
|
46
|
-
class NotifyUsersJob <
|
46
|
+
class NotifyUsersJob < ApplicationJob
|
47
47
|
include JobIteration::Iteration
|
48
48
|
|
49
49
|
def build_enumerator(cursor:)
|
@@ -64,7 +64,9 @@ end
|
|
64
64
|
Check out more examples of Iterations:
|
65
65
|
|
66
66
|
```ruby
|
67
|
-
class BatchesJob <
|
67
|
+
class BatchesJob < ApplicationJob
|
68
|
+
include JobIteration::Iteration
|
69
|
+
|
68
70
|
def build_enumerator(product_id, cursor:)
|
69
71
|
enumerator_builder.active_record_on_batches(
|
70
72
|
Product.find(product_id).comments,
|
@@ -81,7 +83,9 @@ end
|
|
81
83
|
```
|
82
84
|
|
83
85
|
```ruby
|
84
|
-
class ArrayJob <
|
86
|
+
class ArrayJob < ApplicationJob
|
87
|
+
include JobIteration::Iteration
|
88
|
+
|
85
89
|
def build_enumerator(cursor:)
|
86
90
|
enumerator_builder.array(['build', 'enumerator', 'from', 'any', 'array'], cursor: cursor)
|
87
91
|
end
|
@@ -93,7 +97,9 @@ end
|
|
93
97
|
```
|
94
98
|
|
95
99
|
```ruby
|
96
|
-
class CsvJob <
|
100
|
+
class CsvJob < ApplicationJob
|
101
|
+
include JobIteration::Iteration
|
102
|
+
|
97
103
|
def build_enumerator(import_id, cursor:)
|
98
104
|
import = Import.find(import_id)
|
99
105
|
JobIteration::CsvEnumerator.new(import.csv).rows(cursor: cursor)
|
@@ -153,7 +159,7 @@ There a few configuration assumptions that are required for Iteration to work wi
|
|
153
159
|
**My job has a complex flow. How do I write my own Enumerator?** Iteration API takes care of persisting the cursor (that you may use to calculate an offset) and controlling the job state. The power of Enumerator object is that you can use the cursor in any way you want. One example is a cursorless job that pops records from a datastore until the job is interrupted:
|
154
160
|
|
155
161
|
```ruby
|
156
|
-
class MyJob <
|
162
|
+
class MyJob < ApplicationJob
|
157
163
|
include JobIteration::Iteration
|
158
164
|
|
159
165
|
def build_enumerator(cursor:)
|
data/dev.yml
CHANGED
data/guides/custom-enumerator.md
CHANGED
@@ -8,7 +8,7 @@ class ListJob < ActiveJob::Base
|
|
8
8
|
|
9
9
|
def build_enumerator(*)
|
10
10
|
@redis = Redis.new
|
11
|
-
Enumerator.new |yielder|
|
11
|
+
Enumerator.new do |yielder|
|
12
12
|
yielder.yield @redis.lpop(key), nil
|
13
13
|
end
|
14
14
|
end
|
@@ -72,3 +72,5 @@ end
|
|
72
72
|
```
|
73
73
|
|
74
74
|
We recommend that you read the implementation of the other enumerators that come with the library (`CsvEnumerator`, `ActiveRecordEnumerator`) to gain a better understanding of building Enumerator objects.
|
75
|
+
|
76
|
+
Code that is written after the `yield` in a custom enumerator is not guaranteed to execute. In the case that a job is forced to exit ie `job_should_exit?` is true, then the job is re-enqueued during the yield and the rest of the code in the enumerator does not run. You can follow that logic [here](https://github.com/Shopify/job-iteration/blob/9641f455b9126efff2214692c0bef423e0d12c39/lib/job-iteration/iteration.rb#L128-L131).
|
data/job-iteration.gemspec
CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.require_paths = %w(lib)
|
24
24
|
|
25
25
|
spec.metadata["changelog_uri"] = "https://github.com/Shopify/job-iteration/blob/master/CHANGELOG.md"
|
26
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
26
27
|
|
27
28
|
spec.add_development_dependency("activerecord")
|
28
29
|
spec.add_dependency("activejob", ">= 5.2")
|
@@ -32,8 +32,8 @@ module JobIteration
|
|
32
32
|
def rows(cursor:)
|
33
33
|
@csv.lazy
|
34
34
|
.each_with_index
|
35
|
-
.drop(cursor
|
36
|
-
.to_enum {
|
35
|
+
.drop(count_of_processed_rows(cursor))
|
36
|
+
.to_enum { count_of_rows_in_file }
|
37
37
|
end
|
38
38
|
|
39
39
|
# Constructs a enumerator on batches of CSV rows
|
@@ -42,13 +42,13 @@ module JobIteration
|
|
42
42
|
@csv.lazy
|
43
43
|
.each_slice(batch_size)
|
44
44
|
.each_with_index
|
45
|
-
.drop(cursor
|
46
|
-
.to_enum { (
|
45
|
+
.drop(count_of_processed_rows(cursor))
|
46
|
+
.to_enum { (count_of_rows_in_file.to_f / batch_size).ceil }
|
47
47
|
end
|
48
48
|
|
49
49
|
private
|
50
50
|
|
51
|
-
def
|
51
|
+
def count_of_rows_in_file
|
52
52
|
# TODO: Remove rescue for NoMethodError when Ruby 2.6 is no longer supported.
|
53
53
|
begin
|
54
54
|
filepath = @csv.path
|
@@ -63,5 +63,9 @@ module JobIteration
|
|
63
63
|
count -= 1 if @csv.headers
|
64
64
|
count
|
65
65
|
end
|
66
|
+
|
67
|
+
def count_of_processed_rows(cursor)
|
68
|
+
cursor.nil? ? 0 : cursor + 1
|
69
|
+
end
|
66
70
|
end
|
67
71
|
end
|
@@ -22,6 +22,7 @@ module JobIteration
|
|
22
22
|
module ClassMethods
|
23
23
|
def method_added(method_name)
|
24
24
|
ban_perform_definition if method_name.to_sym == :perform
|
25
|
+
super
|
25
26
|
end
|
26
27
|
|
27
28
|
def on_start(*filters, &blk)
|
@@ -49,6 +50,7 @@ module JobIteration
|
|
49
50
|
self.total_time = 0.0
|
50
51
|
assert_implements_methods!
|
51
52
|
end
|
53
|
+
ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
|
52
54
|
|
53
55
|
def serialize # @private
|
54
56
|
super.merge(
|
@@ -69,7 +71,7 @@ module JobIteration
|
|
69
71
|
interruptible_perform(*params)
|
70
72
|
end
|
71
73
|
|
72
|
-
def retry_job(
|
74
|
+
def retry_job(*, **)
|
73
75
|
super unless defined?(@retried) && @retried
|
74
76
|
@retried = true
|
75
77
|
end
|
@@ -116,8 +118,10 @@ module JobIteration
|
|
116
118
|
|
117
119
|
def iterate_with_enumerator(enumerator, arguments)
|
118
120
|
arguments = arguments.dup.freeze
|
121
|
+
found_record = false
|
119
122
|
enumerator.each do |object_from_enumerator, index|
|
120
123
|
record_unit_of_work do
|
124
|
+
found_record = true
|
121
125
|
each_iteration(object_from_enumerator, *arguments)
|
122
126
|
self.cursor_position = index
|
123
127
|
end
|
@@ -128,6 +132,11 @@ module JobIteration
|
|
128
132
|
return false
|
129
133
|
end
|
130
134
|
|
135
|
+
logger.info(
|
136
|
+
"[JobIteration::Iteration] Enumerator found nothing to iterate! " \
|
137
|
+
"times_interrupted=#{times_interrupted} cursor_position=#{cursor_position}"
|
138
|
+
) unless found_record
|
139
|
+
|
131
140
|
true
|
132
141
|
end
|
133
142
|
|
@@ -176,7 +185,7 @@ module JobIteration
|
|
176
185
|
end
|
177
186
|
|
178
187
|
if respond_to?(:build_enumerator, true)
|
179
|
-
parameters =
|
188
|
+
parameters = method_parameters(:build_enumerator)
|
180
189
|
unless valid_cursor_parameter?(parameters)
|
181
190
|
raise ArgumentError, "Iteration job (#{self.class}) #build_enumerator " \
|
182
191
|
"expects the keyword argument `cursor`"
|
@@ -187,6 +196,17 @@ module JobIteration
|
|
187
196
|
end
|
188
197
|
end
|
189
198
|
|
199
|
+
def method_parameters(method_name)
|
200
|
+
method = method(method_name)
|
201
|
+
|
202
|
+
if defined?(T::Private::Methods)
|
203
|
+
signature = T::Private::Methods.signature_for_method(method)
|
204
|
+
method = signature.method if signature
|
205
|
+
end
|
206
|
+
|
207
|
+
method.parameters
|
208
|
+
end
|
209
|
+
|
190
210
|
def iteration_instrumentation_tags
|
191
211
|
{ job_class: self.class.name }
|
192
212
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: job-iteration
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -58,6 +58,7 @@ files:
|
|
58
58
|
- bin/setup
|
59
59
|
- dev.yml
|
60
60
|
- gemfiles/rails_5_2.gemfile
|
61
|
+
- gemfiles/rails_6_0.gemfile
|
61
62
|
- gemfiles/rails_edge.gemfile
|
62
63
|
- guides/best-practices.md
|
63
64
|
- guides/custom-enumerator.md
|
@@ -81,6 +82,7 @@ licenses:
|
|
81
82
|
- MIT
|
82
83
|
metadata:
|
83
84
|
changelog_uri: https://github.com/Shopify/job-iteration/blob/master/CHANGELOG.md
|
85
|
+
allowed_push_host: https://rubygems.org
|
84
86
|
post_install_message:
|
85
87
|
rdoc_options: []
|
86
88
|
require_paths:
|