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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21dc74805b967b6082859b0c9a07d955f9f77ca3e82f250ceaf4f2aecf4afd7b
4
- data.tar.gz: 23ea1a3e03c8b0578dcf852449204f60e5391bdb92f26987da7d42f45b052595
3
+ metadata.gz: 1a4821d55797f7597b3c916d6fbca2195c054bd3aab0ffcc40c9ce21ae0b1ec4
4
+ data.tar.gz: e71ce0fb7ec744b502f018ccfc7ea5e819bfe06f9fb2ca5a32c70cc1b875abc7
5
5
  SHA512:
6
- metadata.gz: b3b5b27d999f8ea00a443d01857f0483fef67edcd0b0525d8ef8003c8814b9efe1e34713d6f45d0aa2de68c79c2517be141595f37c2501265f04ea6a4da7e0af
7
- data.tar.gz: e47797081fc96034f2fe21746528047cba4f2873a0e6a36a7a38f2265d38a1dcb045f66216c3e9579699d63a9b6416136cf456e630404bb6d5381c433ef7232c
6
+ metadata.gz: 943795fabf60cff76fa7b8678f1f5d5a83bd98340194e142092dc891ff1abe991c0410572cebd09155c6c220ff96aa5530fb27e1b4fe119c6bbbf22fe12670ef
7
+ data.tar.gz: 11ea6f0165aa31350c6486236a33d66c94316a08f52905aeaab049388b433c229ccf52e69ae3e43eb8bca9a5c56dc4aae46803aae4add9d2a052d5d49af14d8d
@@ -3,8 +3,9 @@ services:
3
3
  - redis-server
4
4
  language: ruby
5
5
  rvm:
6
- - 2.5.5
7
- - 2.6.2
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'
@@ -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
@@ -23,3 +23,6 @@ gem 'mocha'
23
23
  gem 'rubocop', '~> 0.77.0'
24
24
  gem 'yard'
25
25
  gem 'rake'
26
+
27
+ # for unit testing optional sorbet support
28
+ gem 'sorbet-runtime'
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 < ActiveJob::Base
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 < ActiveJob::Base
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 < ActiveJob::Iteration
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 < ActiveJob::Iteration
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 < ActiveJob::Iteration
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 < ActiveJob::Base
162
+ class MyJob < ApplicationJob
157
163
  include JobIteration::Iteration
158
164
 
159
165
  def build_enumerator(cursor:)
data/dev.yml CHANGED
@@ -7,7 +7,7 @@ up:
7
7
  - mysql-client:
8
8
  or: [mysql@5.7]
9
9
  - ruby:
10
- version: 2.6.2
10
+ version: 2.6.5
11
11
  - railgun
12
12
  - bundler
13
13
  - custom:
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ eval_gemfile '../Gemfile'
4
+
5
+ gem 'activejob', '~> 6.0.0'
6
+ gem 'activerecord', '~> 6.0.0'
@@ -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).
@@ -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")
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JobIteration
4
+ # Curious about how this works from the SQL perspective?
5
+ # Check "Pagination Done the Right way": https://bit.ly/2Rq7iPF
4
6
  class ActiveRecordCursor # @private
5
7
  include Comparable
6
8
 
@@ -32,8 +32,8 @@ module JobIteration
32
32
  def rows(cursor:)
33
33
  @csv.lazy
34
34
  .each_with_index
35
- .drop(cursor.to_i)
36
- .to_enum { count_rows_in_file }
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.to_i)
46
- .to_enum { (count_rows_in_file.to_f / batch_size).ceil }
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 count_rows_in_file
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 = method(:build_enumerator).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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JobIteration
4
- VERSION = "1.1.4"
4
+ VERSION = "1.1.9"
5
5
  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
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: 2019-12-13 00:00:00.000000000 Z
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: