job-iteration 1.3.6 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +42 -22
- data/.github/workflows/cla.yml +22 -0
- data/.rubocop.yml +3 -3
- data/CHANGELOG.md +23 -1
- data/Gemfile +0 -1
- data/Gemfile.lock +64 -67
- data/README.md +26 -8
- data/dev.yml +2 -2
- data/guides/argument-semantics.md +128 -0
- data/guides/best-practices.md +72 -32
- data/guides/custom-enumerator.md +76 -28
- data/guides/iteration-how-it-works.md +2 -18
- data/{railgun.yml → isogun.yml} +0 -4
- data/lib/job-iteration/active_record_batch_enumerator.rb +1 -1
- data/lib/job-iteration/active_record_cursor.rb +6 -3
- data/lib/job-iteration/active_record_enumerator.rb +5 -1
- data/lib/job-iteration/csv_enumerator.rb +1 -1
- data/lib/job-iteration/enumerator_builder.rb +47 -9
- data/lib/job-iteration/iteration.rb +64 -35
- data/lib/job-iteration/log_subscriber.rb +38 -0
- data/lib/job-iteration/nested_enumerator.rb +48 -0
- data/lib/job-iteration/version.rb +1 -1
- data/lib/job-iteration.rb +25 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f875dad03c85d6eb2d3f25d2dd799e6b26082c90e3f70237e7c7a813b513e9d
|
4
|
+
data.tar.gz: 3bd26bbaf398285c98a60c1f6f84daacd47d7ff8f21d10f91bcb37a8fce6071d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 556835d8b4d7c9d1954936e758fb2c7ecde63063b7260e21e88e5118a603db76dd8689bea28066ea5695c147b2ae7084ed5743450da8b0c62f55463be11c6878
|
7
|
+
data.tar.gz: 05042e53baa957afd99f75d6975958175187dfe77f076d9776c5cf1251b50fd1cb5fda98ddbc39491b60f4cdd6a10036c7f7598dc708cf7ab54a22cb0bfcd796
|
data/.github/workflows/ci.yml
CHANGED
@@ -6,14 +6,15 @@ jobs:
|
|
6
6
|
build:
|
7
7
|
runs-on: ubuntu-latest
|
8
8
|
name: Ruby ${{ matrix.ruby }} | Gemfile ${{ matrix.gemfile }}
|
9
|
+
continue-on-error: ${{ matrix.gemfile == 'rails_edge' }}
|
9
10
|
services:
|
10
11
|
redis:
|
11
12
|
image: redis
|
12
13
|
ports:
|
13
|
-
|
14
|
+
- 6379:6379
|
14
15
|
strategy:
|
15
16
|
matrix:
|
16
|
-
ruby: ["2.6", "2.7", "3.0", "3.1"]
|
17
|
+
ruby: ["2.6", "2.7", "3.0", "3.1", "3.2"]
|
17
18
|
gemfile: [rails_5_2, rails_6_0, rails_6_1, rails_7_0, rails_edge]
|
18
19
|
exclude:
|
19
20
|
- ruby: "2.6"
|
@@ -24,31 +25,50 @@ jobs:
|
|
24
25
|
gemfile: rails_5_2
|
25
26
|
- ruby: "3.1"
|
26
27
|
gemfile: rails_5_2
|
28
|
+
- ruby: "3.2"
|
29
|
+
gemfile: rails_5_2
|
27
30
|
- ruby: "3.1"
|
28
31
|
gemfile: rails_6_0
|
32
|
+
- ruby: "3.2"
|
33
|
+
gemfile: rails_6_0
|
34
|
+
- ruby: "3.2"
|
35
|
+
gemfile: rails_6_1
|
36
|
+
|
29
37
|
include:
|
30
38
|
- ruby: head
|
31
39
|
gemfile: rails_edge
|
32
40
|
env:
|
33
41
|
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
|
34
42
|
steps:
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
-
|
54
|
-
|
43
|
+
- name: Check out code
|
44
|
+
uses: actions/checkout@v3
|
45
|
+
- name: Set up Ruby ${{ matrix.ruby }}
|
46
|
+
uses: ruby/setup-ruby@v1
|
47
|
+
with:
|
48
|
+
ruby-version: ${{ matrix.ruby }}
|
49
|
+
bundler-cache: true
|
50
|
+
- name: Start MySQL and create DB
|
51
|
+
run: |
|
52
|
+
sudo systemctl start mysql.service
|
53
|
+
mysql -uroot -h localhost -proot -e "CREATE DATABASE job_iteration_test;"
|
54
|
+
- name: Ruby tests
|
55
|
+
run: bundle exec rake test
|
56
|
+
env:
|
57
|
+
REDIS_HOST: localhost
|
58
|
+
REDIS_PORT: ${{ job.services.redis.ports[6379] }}
|
59
|
+
|
60
|
+
lint:
|
61
|
+
runs-on: ubuntu-latest
|
62
|
+
name: Lint
|
63
|
+
steps:
|
64
|
+
- name: Check out code
|
65
|
+
uses: actions/checkout@v3
|
66
|
+
- name: Set up Ruby
|
67
|
+
uses: ruby/setup-ruby@v1
|
68
|
+
with:
|
69
|
+
ruby-version: "3.2"
|
70
|
+
bundler-cache: true
|
71
|
+
- name: Rubocop
|
72
|
+
run: bundle exec rubocop
|
73
|
+
- name: Documentation correctly written
|
74
|
+
run: bundle exec yardoc --no-output --no-save --no-stats --fail-on-warning
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: Contributor License Agreement (CLA)
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request_target:
|
5
|
+
types: [opened, synchronize]
|
6
|
+
issue_comment:
|
7
|
+
types: [created]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
cla:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
if: |
|
13
|
+
(github.event.issue.pull_request
|
14
|
+
&& !github.event.issue.pull_request.merged_at
|
15
|
+
&& contains(github.event.comment.body, 'signed')
|
16
|
+
)
|
17
|
+
|| (github.event.pull_request && !github.event.pull_request.merged)
|
18
|
+
steps:
|
19
|
+
- uses: Shopify/shopify-cla-action@v1
|
20
|
+
with:
|
21
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
22
|
+
cla-token: ${{ secrets.CLA_TOKEN }}
|
data/.rubocop.yml
CHANGED
@@ -2,9 +2,9 @@ inherit_gem:
|
|
2
2
|
rubocop-shopify: rubocop.yml
|
3
3
|
|
4
4
|
AllCops:
|
5
|
-
TargetRubyVersion: 2.6
|
5
|
+
TargetRubyVersion: 2.6
|
6
6
|
Exclude:
|
7
|
-
-
|
7
|
+
- "vendor/bundle/**/*"
|
8
8
|
Lint/SuppressedException:
|
9
9
|
Exclude:
|
10
10
|
- lib/job-iteration.rb
|
@@ -16,5 +16,5 @@ Naming/FileName:
|
|
16
16
|
- lib/job-iteration.rb
|
17
17
|
Style/MethodCallWithArgsParentheses:
|
18
18
|
Exclude:
|
19
|
-
-
|
19
|
+
- "gemfiles/*"
|
20
20
|
- Gemfile
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,26 @@
|
|
1
|
-
###
|
1
|
+
### Main (unreleased)
|
2
|
+
|
3
|
+
Nil
|
4
|
+
|
5
|
+
## v1.4.0 (Aug 23, 2023)
|
6
|
+
|
7
|
+
### Changes
|
8
|
+
|
9
|
+
- [338](https://github.com/Shopify/job-iteration/pull/338) - All logs are now `ActiveSupport::Notifications` events and logged using `ActiveSupport::LogSubscriber` to allow customization. Events now always include the `cursor_position` tag.
|
10
|
+
- [418](https://github.com/Shopify/job-iteration/pull/418) - Return `nil` from `Iteration#perform`, to signal not to rely on return value.
|
11
|
+
|
12
|
+
### Features
|
13
|
+
|
14
|
+
- [240](https://github.com/Shopify/job-iteration/pull/240) - Allow setting inheritable per-job `job_iteration_max_job_runtime`
|
15
|
+
- [310](https://github.com/Shopify/job-iteration/pull/310) - Support nested iteration
|
16
|
+
- [341](https://github.com/Shopify/job-iteration/pull/341) - Add `JobIteration.default_retry_backoff`, which sets a default delay when jobs are re-enqueued after being interrupted. Defaults to `nil`, meaning no delay, which matches the current behaviour.
|
17
|
+
- [365](https://github.com/Shopify/job-iteration/pull/365) - Support composite primary key as a cursor
|
18
|
+
|
19
|
+
### Bug fixes
|
20
|
+
|
21
|
+
- [289](https://github.com/Shopify/job-iteration/pull/289) - Fix uninitialized constant error when raising `ConditionNotSupportedError` from `ActiveRecordBatchEnumerator`
|
22
|
+
- [346](https://github.com/Shopify/job-iteration/pull/346) - Include failed jobs in `total_time`
|
23
|
+
- [417](https://github.com/Shopify/job-iteration/pull/417) - Ensure that numerical values are deserialized as such and not as strings.
|
2
24
|
|
3
25
|
## v1.3.6 (Mar 9, 2022)
|
4
26
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,117 +1,114 @@
|
|
1
1
|
GIT
|
2
2
|
remote: https://github.com/brianmario/mysql2
|
3
|
-
revision:
|
3
|
+
revision: 79f78f940685396f1b5f30ec502544bb7e3ba9cf
|
4
4
|
specs:
|
5
|
-
mysql2 (0.5.
|
5
|
+
mysql2 (0.5.5)
|
6
6
|
|
7
7
|
PATH
|
8
8
|
remote: .
|
9
9
|
specs:
|
10
|
-
job-iteration (1.
|
10
|
+
job-iteration (1.4.0)
|
11
11
|
activejob (>= 5.2)
|
12
12
|
|
13
13
|
GEM
|
14
14
|
remote: https://rubygems.org/
|
15
15
|
specs:
|
16
|
-
activejob (
|
17
|
-
activesupport (=
|
16
|
+
activejob (7.0.7)
|
17
|
+
activesupport (= 7.0.7)
|
18
18
|
globalid (>= 0.3.6)
|
19
|
-
activemodel (
|
20
|
-
activesupport (=
|
21
|
-
activerecord (
|
22
|
-
activemodel (=
|
23
|
-
activesupport (=
|
24
|
-
activesupport (
|
19
|
+
activemodel (7.0.7)
|
20
|
+
activesupport (= 7.0.7)
|
21
|
+
activerecord (7.0.7)
|
22
|
+
activemodel (= 7.0.7)
|
23
|
+
activesupport (= 7.0.7)
|
24
|
+
activesupport (7.0.7)
|
25
25
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
26
26
|
i18n (>= 1.6, < 2)
|
27
27
|
minitest (>= 5.1)
|
28
28
|
tzinfo (~> 2.0)
|
29
|
-
zeitwerk (~> 2.3)
|
30
29
|
ast (2.4.2)
|
31
30
|
coderay (1.1.3)
|
32
|
-
concurrent-ruby (1.
|
33
|
-
connection_pool (2.
|
34
|
-
|
35
|
-
database_cleaner-active_record (~> 2.0.0)
|
36
|
-
database_cleaner-active_record (2.0.1)
|
37
|
-
activerecord (>= 5.a)
|
38
|
-
database_cleaner-core (~> 2.0.0)
|
39
|
-
database_cleaner-core (2.0.1)
|
40
|
-
globalid (1.0.0)
|
31
|
+
concurrent-ruby (1.2.2)
|
32
|
+
connection_pool (2.4.1)
|
33
|
+
globalid (1.1.0)
|
41
34
|
activesupport (>= 5.0)
|
42
|
-
i18n (1.
|
35
|
+
i18n (1.14.1)
|
43
36
|
concurrent-ruby (~> 1.0)
|
37
|
+
json (2.6.3)
|
38
|
+
language_server-protocol (3.17.0.3)
|
44
39
|
method_source (1.0.0)
|
45
|
-
minitest (5.
|
46
|
-
mocha (1.
|
47
|
-
|
40
|
+
minitest (5.19.0)
|
41
|
+
mocha (2.1.0)
|
42
|
+
ruby2_keywords (>= 0.0.5)
|
43
|
+
mono_logger (1.1.2)
|
48
44
|
multi_json (1.15.0)
|
49
|
-
mustermann (
|
45
|
+
mustermann (3.0.0)
|
50
46
|
ruby2_keywords (~> 0.0.1)
|
51
|
-
parallel (1.
|
52
|
-
parser (3.
|
47
|
+
parallel (1.23.0)
|
48
|
+
parser (3.2.2.3)
|
53
49
|
ast (~> 2.4.1)
|
54
|
-
|
50
|
+
racc
|
51
|
+
pry (0.14.2)
|
55
52
|
coderay (~> 1.1)
|
56
53
|
method_source (~> 1.0)
|
57
|
-
|
58
|
-
rack
|
59
|
-
|
54
|
+
racc (1.7.1)
|
55
|
+
rack (2.2.8)
|
56
|
+
rack-protection (3.1.0)
|
57
|
+
rack (~> 2.2, >= 2.2.4)
|
60
58
|
rainbow (3.1.1)
|
61
59
|
rake (13.0.6)
|
62
|
-
redis (
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
60
|
+
redis (5.0.7)
|
61
|
+
redis-client (>= 0.9.0)
|
62
|
+
redis-client (0.16.0)
|
63
|
+
connection_pool
|
64
|
+
redis-namespace (1.11.0)
|
65
|
+
redis (>= 4)
|
66
|
+
regexp_parser (2.8.1)
|
67
|
+
resque (2.6.0)
|
67
68
|
mono_logger (~> 1.0)
|
68
69
|
multi_json (~> 1.0)
|
69
70
|
redis-namespace (~> 1.6)
|
70
71
|
sinatra (>= 0.9.2)
|
71
|
-
vegas (~> 0.1.2)
|
72
72
|
rexml (3.2.5)
|
73
|
-
rubocop (1.
|
73
|
+
rubocop (1.54.2)
|
74
|
+
json (~> 2.3)
|
75
|
+
language_server-protocol (>= 3.17.0)
|
74
76
|
parallel (~> 1.10)
|
75
|
-
parser (>= 3.
|
77
|
+
parser (>= 3.2.2.3)
|
76
78
|
rainbow (>= 2.2.2, < 4.0)
|
77
79
|
regexp_parser (>= 1.8, < 3.0)
|
78
|
-
rexml
|
79
|
-
rubocop-ast (>= 1.
|
80
|
+
rexml (>= 3.2.5, < 4.0)
|
81
|
+
rubocop-ast (>= 1.28.0, < 2.0)
|
80
82
|
ruby-progressbar (~> 1.7)
|
81
|
-
unicode-display_width (>=
|
82
|
-
rubocop-ast (1.
|
83
|
-
parser (>= 3.
|
84
|
-
rubocop-shopify (2.
|
85
|
-
rubocop (~> 1.
|
86
|
-
ruby-progressbar (1.
|
83
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
84
|
+
rubocop-ast (1.29.0)
|
85
|
+
parser (>= 3.2.1.0)
|
86
|
+
rubocop-shopify (2.14.0)
|
87
|
+
rubocop (~> 1.51)
|
88
|
+
ruby-progressbar (1.13.0)
|
87
89
|
ruby2_keywords (0.0.5)
|
88
|
-
sidekiq (
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
rack
|
90
|
+
sidekiq (7.1.2)
|
91
|
+
concurrent-ruby (< 2)
|
92
|
+
connection_pool (>= 2.3.0)
|
93
|
+
rack (>= 2.2.4)
|
94
|
+
redis-client (>= 0.14.0)
|
95
|
+
sinatra (3.1.0)
|
96
|
+
mustermann (~> 3.0)
|
97
|
+
rack (~> 2.2, >= 2.2.4)
|
98
|
+
rack-protection (= 3.1.0)
|
96
99
|
tilt (~> 2.0)
|
97
|
-
sorbet-runtime (0.5.
|
98
|
-
tilt (2.0
|
99
|
-
tzinfo (2.0.
|
100
|
+
sorbet-runtime (0.5.10978)
|
101
|
+
tilt (2.2.0)
|
102
|
+
tzinfo (2.0.6)
|
100
103
|
concurrent-ruby (~> 1.0)
|
101
|
-
unicode-display_width (2.
|
102
|
-
|
103
|
-
rack (>= 1.0.0)
|
104
|
-
webrick (1.7.0)
|
105
|
-
yard (0.9.27)
|
106
|
-
webrick (~> 1.7.0)
|
107
|
-
zeitwerk (2.5.4)
|
104
|
+
unicode-display_width (2.4.2)
|
105
|
+
yard (0.9.34)
|
108
106
|
|
109
107
|
PLATFORMS
|
110
108
|
ruby
|
111
109
|
|
112
110
|
DEPENDENCIES
|
113
111
|
activerecord
|
114
|
-
database_cleaner
|
115
112
|
globalid
|
116
113
|
i18n
|
117
114
|
job-iteration!
|
data/README.md
CHANGED
@@ -69,17 +69,15 @@ class BatchesJob < ApplicationJob
|
|
69
69
|
|
70
70
|
def build_enumerator(product_id, cursor:)
|
71
71
|
enumerator_builder.active_record_on_batches(
|
72
|
-
|
72
|
+
Comment.where(product_id: product_id).select(:id),
|
73
73
|
cursor: cursor,
|
74
74
|
batch_size: 100,
|
75
75
|
)
|
76
76
|
end
|
77
77
|
|
78
78
|
def each_iteration(batch_of_comments, product_id)
|
79
|
-
|
80
|
-
|
81
|
-
DeleteCommentJob.perform_later(comment)
|
82
|
-
end
|
79
|
+
comment_ids = batch_of_comments.map(&:id)
|
80
|
+
CommentService.call(comment_ids: comment_ids)
|
83
81
|
end
|
84
82
|
end
|
85
83
|
```
|
@@ -126,17 +124,39 @@ class CsvJob < ApplicationJob
|
|
126
124
|
enumerator_builder.csv(import.csv, cursor: cursor)
|
127
125
|
end
|
128
126
|
|
129
|
-
def each_iteration(csv_row)
|
127
|
+
def each_iteration(csv_row, import_id)
|
130
128
|
# insert csv_row to database
|
131
129
|
end
|
132
130
|
end
|
133
131
|
```
|
134
132
|
|
133
|
+
```ruby
|
134
|
+
class NestedIterationJob < ApplicationJob
|
135
|
+
include JobIteration::Iteration
|
136
|
+
|
137
|
+
def build_enumerator(cursor:)
|
138
|
+
enumerator_builder.nested(
|
139
|
+
[
|
140
|
+
->(cursor) { enumerator_builder.active_record_on_records(Shop.all, cursor: cursor) },
|
141
|
+
->(shop, cursor) { enumerator_builder.active_record_on_records(shop.products, cursor: cursor) },
|
142
|
+
->(_shop, product, cursor) { enumerator_builder.active_record_on_batch_relations(product.product_variants, cursor: cursor) }
|
143
|
+
],
|
144
|
+
cursor: cursor
|
145
|
+
)
|
146
|
+
end
|
147
|
+
|
148
|
+
def each_iteration(product_variants_relation)
|
149
|
+
# do something
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
135
154
|
Iteration hooks into Sidekiq and Resque out of the box to support graceful interruption. No extra configuration is required.
|
136
155
|
|
137
156
|
## Guides
|
138
157
|
|
139
158
|
* [Iteration: how it works](guides/iteration-how-it-works.md)
|
159
|
+
* [Job argument semantics](guides/argument-semantics.md)
|
140
160
|
* [Best practices](guides/best-practices.md)
|
141
161
|
* [Writing custom enumerator](guides/custom-enumerator.md)
|
142
162
|
* [Throttling](guides/throttling.md)
|
@@ -171,8 +191,6 @@ There a few configuration assumptions that are required for Iteration to work wi
|
|
171
191
|
|
172
192
|
**Why is it important that `each_iteration` takes less than 30 seconds?** When the job worker is scheduled for restart or shutdown, it gets a notice to finish remaining unit of work. To guarantee that no progress is lost we need to make sure that `each_iteration` completes within a reasonable amount of time.
|
173
193
|
|
174
|
-
**What do I do if each iteration takes a long time, because it's doing nested operations?** If your `each_iteration` is complex, we recommend enqueuing another job, which will run your nested business logic. We may expose primitives in the future to do this more effectively, but this is not terribly common today.
|
175
|
-
|
176
194
|
**Why do I use have to use this ugly helper in `build_enumerator`? Why can't you automatically infer it?** This is how the first version of the API worked. We checked the type of object returned by `build_enumerable`, and whether it was ActiveRecord Relation or an Array, we used the matching adapter. This caused opaque type branching in Iteration internals and it didn’t allow developers to craft their own Enumerators and control the cursor value. We made a decision to _always_ return Enumerator instance from `build_enumerator`. Now we provide explicit helpers to convert ActiveRecord Relation or an Array to Enumerator, and for more complex iteration flows developers can build their own `Enumerator` objects.
|
177
195
|
|
178
196
|
**What is the difference between Enumerable and Enumerator?** We recomend [this post](http://blog.arkency.com/2014/01/ruby-to-enum-for-enumerator/) to learn more about Enumerators in Ruby.
|
data/dev.yml
CHANGED
@@ -0,0 +1,128 @@
|
|
1
|
+
`job-iteration` overrides the `perform` method of `ActiveJob::Base` to allow for iteration. The `perform` method preserves all the standard calling conventions of the original, but the way the subsequent methods work might differ from what one expects from an ActiveJob subclass.
|
2
|
+
|
3
|
+
The call sequence is usually 3 methods:
|
4
|
+
|
5
|
+
`perform -> build_enumerator -> each_iteration|each_batch`
|
6
|
+
|
7
|
+
In that sense `job-iteration` works like a framework (it calls your code) rather than like a library (that you call). When using jobs with parameters, the following rules of thumb are good to keep in mind.
|
8
|
+
|
9
|
+
### Jobs without arguments
|
10
|
+
|
11
|
+
Jobs without arguments do not pass anything into either `build_enumerator` or `each_iteration` except for the `cursor` which `job-iteration` persists by itself:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
class ArglessJob < ActiveJob::Base
|
15
|
+
include JobIteration::Iteration
|
16
|
+
|
17
|
+
def build_enumerator(cursor:)
|
18
|
+
# ...
|
19
|
+
end
|
20
|
+
|
21
|
+
def each_iteration(single_object_yielded_from_enumerator)
|
22
|
+
# ...
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
To enqueue the job:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
ArglessJob.perform_later
|
31
|
+
```
|
32
|
+
|
33
|
+
### Jobs with positional arguments
|
34
|
+
|
35
|
+
Jobs with positional arguments will have those arguments available to both `build_enumerator` and `each_iteration`:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class ArgumentativeJob < ActiveJob::Base
|
39
|
+
include JobIteration::Iteration
|
40
|
+
|
41
|
+
def build_enumerator(arg1, arg2, arg3, cursor:)
|
42
|
+
# ...
|
43
|
+
end
|
44
|
+
|
45
|
+
def each_iteration(single_object_yielded_from_enumerator, arg1, arg2, arg3)
|
46
|
+
# ...
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
To enqueue the job:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
ArgumentativeJob.perform_later(_arg1 = "One", _arg2 = "Two", _arg3 = "Three")
|
55
|
+
```
|
56
|
+
|
57
|
+
### Jobs with keyword arguments
|
58
|
+
|
59
|
+
Jobs with keyword arguments will have the keyword arguments available to both `build_enumerator` and `each_iteration`, but these arguments come packaged into a Hash in both cases. You will need to `fetch` or `[]` your parameter from the `Hash` you get passed in:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class ParameterizedJob < ActiveJob::Base
|
63
|
+
include JobIteration::Iteration
|
64
|
+
|
65
|
+
def build_enumerator(kwargs, cursor:)
|
66
|
+
name = kwargs.fetch(:name)
|
67
|
+
email = kwargs.fetch(:email)
|
68
|
+
# ...
|
69
|
+
end
|
70
|
+
|
71
|
+
def each_iteration(object_yielded_from_enumerator, kwargs)
|
72
|
+
name = kwargs.fetch(:name)
|
73
|
+
email = kwargs.fetch(:email)
|
74
|
+
# ...
|
75
|
+
end
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
To enqueue the job:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
ParameterizedJob.perform_later(name: "Jane", email: "jane@host.example")
|
83
|
+
```
|
84
|
+
|
85
|
+
Note that you cannot use `ruby2_keywords` at present, and the keyword arguments syntax is not supported in `each_iteration` / `build_enumerator`.
|
86
|
+
|
87
|
+
### Jobs with both positional and keyword arguments
|
88
|
+
|
89
|
+
Jobs with keyword arguments will have the keyword arguments available to both `build_enumerator` and `each_iteration`, but these arguments come packaged into a Hash in both cases. You will need to `fetch` or `[]` your parameter from the `Hash` you get passed in. Positional arguments get passed first and "unsplatted" (not combined into an array), the `Hash` containing keyword arguments comes after:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class HighlyConfigurableGreetingJob < ActiveJob::Base
|
93
|
+
include JobIteration::Iteration
|
94
|
+
|
95
|
+
def build_enumerator(subject_line, kwargs, cursor:)
|
96
|
+
name = kwargs.fetch(:sender_name)
|
97
|
+
email = kwargs.fetch(:sender_email)
|
98
|
+
# ...
|
99
|
+
end
|
100
|
+
|
101
|
+
def each_iteration(object_yielded_from_enumerator, subject_line, kwargs)
|
102
|
+
name = kwargs.fetch(:sender_name)
|
103
|
+
email = kwargs.fetch(:sender_email)
|
104
|
+
# ...
|
105
|
+
end
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
To enqueue the job:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
HighlyConfigurableGreetingJob.perform_later(_subject_line = "Greetings everybody!", sender_name: "Jane", sender_email: "jane@host.example")
|
113
|
+
```
|
114
|
+
|
115
|
+
Note that you cannot use `ruby2_keywords` at present, and the keyword arguments syntax is not supported in `each_iteration` / `build_enumerator`.
|
116
|
+
|
117
|
+
### Returning (yielding) from enumerators
|
118
|
+
|
119
|
+
When defining a custom enumerator (see the [custom enumerator guide](custom-enumerator.md)) you need to yield two positional arguments from it: the object that will be the value for the current iteration (like a single ActiveModel instance, a single number...) and the value you want to be persisted as the `cursor` value should `job-iteration` decide to interrupt you after this iteration. Calling the enumerator with that cursor should return the next object after the one returned in this iteration. That new `cursor` value does not get passed to `each_iteration`:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
Enumerator.new do |yielder|
|
123
|
+
# In this case `cursor` is an Integer
|
124
|
+
cursor.upto(99999) do |offset|
|
125
|
+
yielder.yield(fetch_record_at(offset), offset)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|