rocketjob 7.0.0 → 7.1.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 +4 -4
- data/README.md +1 -1
- data/lib/rocket_job/batch/categories.rb +16 -3
- data/lib/rocket_job/batch/io.rb +32 -7
- data/lib/rocket_job/category/base.rb +119 -1
- data/lib/rocket_job/category/input.rb +166 -40
- data/lib/rocket_job/category/output.rb +7 -0
- data/lib/rocket_job/deprecation.rb +51 -0
- data/lib/rocket_job/jobs/copy_file_job.rb +11 -1
- data/lib/rocket_job/plugins/retry.rb +35 -34
- data/lib/rocket_job/sliced/writer/input.rb +4 -4
- data/lib/rocket_job/version.rb +1 -1
- data/lib/rocketjob.rb +1 -0
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a97fb3e6b6ed4130f52e5796bdb0737661c20e83f158c1f00667629438ea316
|
|
4
|
+
data.tar.gz: 298727e29cd1f8d37cd4cab34c23d7a8b3fee805f0f264c784e72af873309640
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df159598ced406d32f34802c65361c6f429075862722afb078324e265f4c71e779f12646292b1fe03d3a9e53bcd94c32f9f9e18aea3853260e21368b0e46bb5a
|
|
7
|
+
data.tar.gz: f1c0e817b687fa470f66ee3cbe7b519d52835813ad9ec99c96f092d557e1438f09358af1391b6288832df47c45249011d7d151668e782cf0207b25a431a274df
|
data/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Rocket Job
|
|
2
|
-
[](https://rubygems.org/gems/rocketjob) [](https://rubygems.org/gems/rocketjob) [](http://opensource.org/licenses/Apache-2.0) 
|
|
2
|
+
[](https://rubygems.org/gems/rocketjob) [](https://rubygems.org/gems/rocketjob) [](https://github.com/reidmorrison/rocketjob/actions/workflows/ci.yml) [](http://opensource.org/licenses/Apache-2.0) 
|
|
3
3
|
|
|
4
4
|
**Process millions of records across thousands of workers.**
|
|
5
5
|
|
|
@@ -29,6 +29,7 @@ module RocketJob
|
|
|
29
29
|
# Define a new input category
|
|
30
30
|
# @see RocketJob::Category::Input
|
|
31
31
|
def input_category(**args)
|
|
32
|
+
RocketJob::Category::Input.warn_deprecated_options(args)
|
|
32
33
|
category = RocketJob::Category::Input.new(**args)
|
|
33
34
|
if defined_input_categories.nil?
|
|
34
35
|
self.defined_input_categories = [category]
|
|
@@ -40,6 +41,7 @@ module RocketJob
|
|
|
40
41
|
# Define a new output category
|
|
41
42
|
# @see RocketJob::Category::Output
|
|
42
43
|
def output_category(**args)
|
|
44
|
+
RocketJob::Category::Output.warn_deprecated_options(args)
|
|
43
45
|
category = RocketJob::Category::Output.new(**args)
|
|
44
46
|
if defined_output_categories.nil?
|
|
45
47
|
self.defined_output_categories = [category]
|
|
@@ -120,28 +122,39 @@ module RocketJob
|
|
|
120
122
|
false
|
|
121
123
|
end
|
|
122
124
|
|
|
125
|
+
# Sets the supplied properties of each input category on it, such as those that a Dirmon entry or an
|
|
126
|
+
# UploadFileJob holds, and reports a deprecation warning for a deprecated option among them, see
|
|
127
|
+
# Category::Base.warn_deprecated_options.
|
|
123
128
|
def merge_input_categories(categories)
|
|
124
129
|
return if categories.blank?
|
|
125
130
|
|
|
126
131
|
categories.each do |properties|
|
|
127
132
|
category_name = (properties["name"] || properties[:name] || :main).to_sym
|
|
128
133
|
category = input_category(category_name)
|
|
129
|
-
|
|
134
|
+
rocketjob_categories_merge(category, properties)
|
|
130
135
|
end
|
|
131
136
|
end
|
|
132
137
|
|
|
138
|
+
# Sets the supplied properties of each output category on it, see #merge_input_categories.
|
|
133
139
|
def merge_output_categories(categories)
|
|
134
140
|
return if categories.blank?
|
|
135
141
|
|
|
136
142
|
categories.each do |properties|
|
|
137
143
|
category_name = (properties["name"] || properties[:name] || :main).to_sym
|
|
138
144
|
category = output_category(category_name)
|
|
139
|
-
|
|
145
|
+
rocketjob_categories_merge(category, properties)
|
|
140
146
|
end
|
|
141
147
|
end
|
|
142
148
|
|
|
143
149
|
private
|
|
144
150
|
|
|
151
|
+
# Sets the supplied properties on the category, after reporting a deprecation warning for a deprecated option
|
|
152
|
+
# among them, which includes the category's schema, since an input category's columns are not deprecated with one.
|
|
153
|
+
def rocketjob_categories_merge(category, properties)
|
|
154
|
+
category.class.warn_deprecated_options(category.attributes.slice("schema").merge(properties.to_h.stringify_keys))
|
|
155
|
+
properties.each { |key, value| category.public_send(:"#{key}=", value) }
|
|
156
|
+
end
|
|
157
|
+
|
|
145
158
|
def rocketjob_categories_assign
|
|
146
159
|
# Input categories defaults to :main if none was set in the class
|
|
147
160
|
if input_categories.empty?
|
|
@@ -218,7 +231,7 @@ module RocketJob
|
|
|
218
231
|
|
|
219
232
|
# Migrate existing v5 batch jobs to v6
|
|
220
233
|
def rocketjob_categories_migrate
|
|
221
|
-
return unless
|
|
234
|
+
return unless attributes["input_categories"]&.first.is_a?(Symbol)
|
|
222
235
|
|
|
223
236
|
serializer = :none
|
|
224
237
|
if attribute_present?(:compress)
|
data/lib/rocket_job/batch/io.rb
CHANGED
|
@@ -95,6 +95,9 @@ module RocketJob
|
|
|
95
95
|
# Parses each line from the file as an Array and uploads each array for processing by workers.
|
|
96
96
|
# :hash
|
|
97
97
|
# Parses each line from the file into a Hash and uploads each hash for processing by workers.
|
|
98
|
+
# Default: :line
|
|
99
|
+
# With a schema, the default is the mode of the input category, and the lines of the file are uploaded
|
|
100
|
+
# in the :line and :hash modes, see RocketJob::Category::Input#mode.
|
|
98
101
|
# See IOStreams::Stream#each.
|
|
99
102
|
#
|
|
100
103
|
# category [Symbol|RocketJob::Category::Input]
|
|
@@ -109,7 +112,7 @@ module RocketJob
|
|
|
109
112
|
# Example:
|
|
110
113
|
# # Load plain text records from a file, stripping all non-printable characters,
|
|
111
114
|
# # as well as any characters that cannot be converted to UTF-8
|
|
112
|
-
# path = IOStreams.path('hello.csv').
|
|
115
|
+
# path = IOStreams.path('hello.csv').encoding(cleaner: :printable, replace: '')
|
|
113
116
|
# job.upload(path)
|
|
114
117
|
#
|
|
115
118
|
# Example: Zip
|
|
@@ -120,11 +123,11 @@ module RocketJob
|
|
|
120
123
|
# job.upload('myfile.csv.zip.enc')
|
|
121
124
|
#
|
|
122
125
|
# Example: Explicitly set the streams
|
|
123
|
-
# path = IOStreams.path('myfile.ze').stream(:
|
|
126
|
+
# path = IOStreams.path('myfile.ze').stream(:zip).stream(:enc).encoding('UTF-8')
|
|
124
127
|
# job.upload(path)
|
|
125
128
|
#
|
|
126
129
|
# Example: Supply custom options
|
|
127
|
-
# path = IOStreams.path('myfile.csv.enc').option(:enc, compress: false).
|
|
130
|
+
# path = IOStreams.path('myfile.csv.enc').option(:enc, compress: false).encoding('UTF-8')
|
|
128
131
|
# job.upload(path)
|
|
129
132
|
#
|
|
130
133
|
# Example: Read from a tempfile and use the original file name to determine which streams to apply
|
|
@@ -324,14 +327,16 @@ module RocketJob
|
|
|
324
327
|
else
|
|
325
328
|
raise(ArgumentError, "Unknown keyword argument :columns when uploading a file") if columns
|
|
326
329
|
|
|
330
|
+
rocketjob_warn_deprecated_upload_options(args)
|
|
327
331
|
category = input_category(category)
|
|
328
332
|
|
|
329
|
-
#
|
|
333
|
+
# The path sets the format of a category whose format is :auto, which decides whether the file has a
|
|
334
|
+
# header row to extract during the upload.
|
|
335
|
+
path = category.upload_path(object, original_file_name: file_name)
|
|
330
336
|
extract_header = category.extract_header_callback(on_first)
|
|
331
|
-
path = category.upload_path(object, original_file_name: file_name)
|
|
332
337
|
|
|
333
338
|
input_collection.upload(on_first: extract_header, slice_batch_size: slice_batch_size) do |io|
|
|
334
|
-
|
|
339
|
+
category.each_record(path, mode: stream_mode, **args) { |record| io << record }
|
|
335
340
|
end
|
|
336
341
|
|
|
337
342
|
end
|
|
@@ -392,6 +397,11 @@ module RocketJob
|
|
|
392
397
|
count
|
|
393
398
|
end
|
|
394
399
|
|
|
400
|
+
# The options that IOStreams reads a file with which describe its columns, and are deprecated, see
|
|
401
|
+
# RocketJob::Category::Input#schema.
|
|
402
|
+
DEPRECATED_UPLOAD_OPTIONS = %i[allowed_columns required_columns skip_unknown cleanse_header].freeze
|
|
403
|
+
private_constant :DEPRECATED_UPLOAD_OPTIONS
|
|
404
|
+
|
|
395
405
|
# Download the output data into the supplied file, io, IOStreams::Path, or IOStreams::Stream.
|
|
396
406
|
# Returns [Integer] the number of records / lines downloaded.
|
|
397
407
|
#
|
|
@@ -417,7 +427,7 @@ module RocketJob
|
|
|
417
427
|
# job.download(path)
|
|
418
428
|
#
|
|
419
429
|
# Example: Supply custom options. Set the file name within the zip file.
|
|
420
|
-
# path = IOStreams.path('myfile.csv.zip').option(:zip,
|
|
430
|
+
# path = IOStreams.path('myfile.csv.zip').option(:zip, entry_file_name: 'myfile.csv')
|
|
421
431
|
# job.download(path)
|
|
422
432
|
#
|
|
423
433
|
# Example: Download into a tempfile, or stream, using the original file name to determine the streams to apply:
|
|
@@ -479,6 +489,21 @@ module RocketJob
|
|
|
479
489
|
end
|
|
480
490
|
end
|
|
481
491
|
end
|
|
492
|
+
|
|
493
|
+
private
|
|
494
|
+
|
|
495
|
+
# Reports a deprecation warning, see RocketJob::Deprecation, at the line of the application that uploads a file
|
|
496
|
+
# with options that describe the columns of the file, which the schema of the input category replaces.
|
|
497
|
+
def rocketjob_warn_deprecated_upload_options(args)
|
|
498
|
+
names = args.keys & DEPRECATED_UPLOAD_OPTIONS
|
|
499
|
+
return if names.empty?
|
|
500
|
+
|
|
501
|
+
RocketJob::Deprecation.warn(
|
|
502
|
+
"#{names.collect { |name| "#{name}:" }.join(', ')} #{names.size == 1 ? 'is' : 'are'} deprecated, describe " \
|
|
503
|
+
"the columns of the file with the schema: of the input category instead, see " \
|
|
504
|
+
"#{RocketJob::Deprecation::UPGRADE_GUIDE}"
|
|
505
|
+
)
|
|
506
|
+
end
|
|
482
507
|
end
|
|
483
508
|
end
|
|
484
509
|
end
|
|
@@ -15,6 +15,10 @@ module RocketJob
|
|
|
15
15
|
# The header columns when the file does not include a header row.
|
|
16
16
|
# Note:
|
|
17
17
|
# - All column names must be strings so that it can be serialized into MongoDB.
|
|
18
|
+
# - With a schema, see #schema, the columns of an input category hold the header row read from the uploaded
|
|
19
|
+
# file, see RocketJob::Category::Input, and those of an output category cannot be set.
|
|
20
|
+
# - Deprecated without a schema, use `schema`, whose fields are the columns, with `"header": false` for a file
|
|
21
|
+
# without a header row.
|
|
18
22
|
field :columns, type: Array
|
|
19
23
|
|
|
20
24
|
# On an input collection `format` specifies the format of the input data so that it can be
|
|
@@ -28,17 +32,76 @@ module RocketJob
|
|
|
28
32
|
#
|
|
29
33
|
# Default: `nil`
|
|
30
34
|
field :format, type: ::Mongoid::StringifiedSymbol
|
|
31
|
-
validates_inclusion_of :format, in: [nil, :auto] + IOStreams
|
|
35
|
+
validates_inclusion_of :format, in: [nil, :auto] + IOStreams.tabular_formats
|
|
32
36
|
|
|
33
37
|
# Any specialized format specific options. For example, `:fixed` format requires a `:layout`.
|
|
38
|
+
#
|
|
39
|
+
# Deprecated, use `schema`, see #schema, which gives the width of each field of a fixed width file. It cannot be
|
|
40
|
+
# set with a schema.
|
|
34
41
|
field :format_options, type: Hash
|
|
35
42
|
|
|
43
|
+
# The fields of the records in this category and the type of each one, as a Frictionless Table Schema, see
|
|
44
|
+
# https://datapackage.org/standard/table-schema/, so that records are read as typed values, such as an Integer,
|
|
45
|
+
# a BigDecimal or a Date, and written in the text form of each type, for every tabular format. It also gives
|
|
46
|
+
# the position of each field of a fixed width file, with its `width`, and `"header": false` describes a file
|
|
47
|
+
# without a header row. See `IOStreams::Schema`, and `IOStreams::Stream#schema`.
|
|
48
|
+
#
|
|
49
|
+
# A Hash, or the path or url of a JSON file that holds the schema, which is read when the category is first
|
|
50
|
+
# used to read or write records.
|
|
51
|
+
#
|
|
52
|
+
# Example, for a fixed width file:
|
|
53
|
+
# {"fields" => [{"name" => "name", "type" => "string", "width" => 10},
|
|
54
|
+
# {"name" => "zip", "type" => "integer", "width" => 5}]}
|
|
55
|
+
#
|
|
56
|
+
# The schema describes the columns of the file, so `format_options` and `columns` cannot be set with it, nor on
|
|
57
|
+
# an input category `allowed_columns`, `required_columns`, `skip_unknown` and `header_cleanser`. It needs a
|
|
58
|
+
# `format`, such as `:csv`, or `:auto` to take it from the file name.
|
|
59
|
+
#
|
|
60
|
+
# Default: nil, which reads and writes each value as text, as before.
|
|
61
|
+
field :schema
|
|
62
|
+
validate :schema_valid
|
|
63
|
+
validate :schema_replaces_fields
|
|
64
|
+
|
|
65
|
+
# Setting the schema builds the tabular again from it when it is next used, see #tabular.
|
|
66
|
+
define_method(:schema=) do |value|
|
|
67
|
+
@tabular = nil
|
|
68
|
+
super(value)
|
|
69
|
+
end
|
|
70
|
+
|
|
36
71
|
# When `:format` is not supplied the file name can be used to infer the required format.
|
|
37
72
|
# Optional.
|
|
38
73
|
# Default: nil
|
|
39
74
|
field :file_name, type: IOStreams::Path
|
|
40
75
|
end
|
|
41
76
|
|
|
77
|
+
module ClassMethods
|
|
78
|
+
# Returns [Array<String>] the names of the deprecated options in the supplied properties of a category, with
|
|
79
|
+
# String keys, that its schema replaces, see #schema: its `columns`. Overridden by the input category.
|
|
80
|
+
def deprecated_column_options(properties)
|
|
81
|
+
properties["columns"].present? ? ["columns"] : []
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Reports a deprecation warning, see RocketJob::Deprecation, for the deprecated options in the supplied
|
|
85
|
+
# properties of a category, such as those that a job class defines it with, or that a Dirmon entry holds.
|
|
86
|
+
def warn_deprecated_options(properties)
|
|
87
|
+
properties = properties.to_h.stringify_keys
|
|
88
|
+
names = deprecated_column_options(properties)
|
|
89
|
+
unless names.empty?
|
|
90
|
+
options = names.collect { |name| "#{name}:" }.join(", ")
|
|
91
|
+
RocketJob::Deprecation.warn(
|
|
92
|
+
"#{options} #{names.size == 1 ? 'is' : 'are'} deprecated, describe the columns of the file with schema: " \
|
|
93
|
+
"instead, see #{RocketJob::Deprecation::UPGRADE_GUIDE}"
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
return if properties["format_options"].blank?
|
|
97
|
+
|
|
98
|
+
RocketJob::Deprecation.warn(
|
|
99
|
+
"format_options: is deprecated, use schema: instead, which gives the width of each field of a fixed width " \
|
|
100
|
+
"file, see #{RocketJob::Deprecation::UPGRADE_GUIDE}"
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
42
105
|
# Return which slice serializer class to use that matches the current options.
|
|
43
106
|
def serializer_class
|
|
44
107
|
case serializer
|
|
@@ -58,6 +121,8 @@ module RocketJob
|
|
|
58
121
|
end
|
|
59
122
|
|
|
60
123
|
def tabular
|
|
124
|
+
return @tabular ||= schema_tabular if schema.present?
|
|
125
|
+
|
|
61
126
|
@tabular ||= IOStreams::Tabular.new(
|
|
62
127
|
columns: columns,
|
|
63
128
|
format: format == :auto ? nil : format,
|
|
@@ -76,6 +141,59 @@ module RocketJob
|
|
|
76
141
|
collection_name << ".#{name}" unless name == :main
|
|
77
142
|
collection_name
|
|
78
143
|
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Returns [IOStreams::Tabular] that reads or writes the records of this category by its schema, see #schema,
|
|
148
|
+
# reading the file that holds it when it is a path or url.
|
|
149
|
+
def schema_tabular
|
|
150
|
+
IOStreams::Tabular.new(
|
|
151
|
+
schema: IOStreams::Schema::Source.new(schema).schema,
|
|
152
|
+
format: format == :auto ? nil : format,
|
|
153
|
+
file_name: file_name
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Returns [Array<Symbol>] the fields of this category that are set, which its schema replaces, see #schema.
|
|
158
|
+
def fields_replaced_by_schema
|
|
159
|
+
format_options.present? ? [:format_options] : []
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# The fields that the schema replaces cannot be set with it, since IOStreams raises for them with a schema, rather
|
|
163
|
+
# than apply one and ignore the other.
|
|
164
|
+
def schema_replaces_fields
|
|
165
|
+
return if schema.blank?
|
|
166
|
+
|
|
167
|
+
errors.add(:schema, "needs a format, such as :csv, or :auto to take it from the file name") if format.blank?
|
|
168
|
+
replaced = fields_replaced_by_schema
|
|
169
|
+
errors.add(:schema, "replaces #{replaced.to_sentence}, which cannot be set with it") unless replaced.empty?
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Checks the schema without reading a file that holds it, since its store may not be available yet.
|
|
173
|
+
def schema_valid
|
|
174
|
+
case schema
|
|
175
|
+
when nil
|
|
176
|
+
nil
|
|
177
|
+
when Hash
|
|
178
|
+
IOStreams::Schema.new(schema)
|
|
179
|
+
when String
|
|
180
|
+
schema_path_valid
|
|
181
|
+
else
|
|
182
|
+
errors.add(:schema, "must be a Hash, or the path or url of a JSON file")
|
|
183
|
+
end
|
|
184
|
+
rescue ArgumentError => e
|
|
185
|
+
errors.add(:schema, "is not valid: #{e.message}")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def schema_path_valid
|
|
189
|
+
IOStreams.path(schema)
|
|
190
|
+
rescue LoadError
|
|
191
|
+
# A gem that the path needs is not installed in this process, so the path is checked when the job runs.
|
|
192
|
+
nil
|
|
193
|
+
rescue StandardError
|
|
194
|
+
# The path is not included in the message, since a url can include credentials.
|
|
195
|
+
errors.add(:schema, "is not a valid path or url")
|
|
196
|
+
end
|
|
79
197
|
end
|
|
80
198
|
end
|
|
81
199
|
end
|
|
@@ -21,9 +21,13 @@ module RocketJob
|
|
|
21
21
|
# Note:
|
|
22
22
|
# When supplied any columns that are rejected will be returned in the cleansed columns
|
|
23
23
|
# as nil so that they can be ignored during processing.
|
|
24
|
+
#
|
|
25
|
+
# Deprecated, use `schema`, whose `fieldsMatch` says which columns a file can have.
|
|
24
26
|
field :allowed_columns, type: Array
|
|
25
27
|
|
|
26
28
|
# List of columns that must be present, otherwise an Exception is raised.
|
|
29
|
+
#
|
|
30
|
+
# Deprecated, use `schema`, with `constraints: {required: true}` on each field that a file must have.
|
|
27
31
|
field :required_columns, type: Array
|
|
28
32
|
|
|
29
33
|
# Whether to skip unknown columns in the uploaded file.
|
|
@@ -41,29 +45,26 @@ module RocketJob
|
|
|
41
45
|
# Notes:
|
|
42
46
|
# - Only applicable when `allowed_columns` has been set.
|
|
43
47
|
# - Recommended to leave as `false` otherwise a misspelled column can result in missed columns.
|
|
48
|
+
# - Deprecated, use `schema`, whose `fieldsMatch` of "subset" or "partial" skips the other columns.
|
|
44
49
|
field :skip_unknown, type: ::Mongoid::Boolean, default: false
|
|
45
50
|
validates_inclusion_of :skip_unknown, in: [true, false]
|
|
46
51
|
|
|
47
|
-
#
|
|
52
|
+
# How `#upload` reads a file into a category with a schema, see #schema, unless the upload is given a
|
|
53
|
+
# `stream_mode:`:
|
|
48
54
|
# :line
|
|
49
|
-
# Uploads the file
|
|
50
|
-
# This is the default
|
|
51
|
-
# up to the workers themselves.
|
|
52
|
-
# :array
|
|
53
|
-
# Parses each line from the file as an Array and uploads each array for processing by workers.
|
|
54
|
-
# Every line in the input file is parsed and converted into an array before uploading.
|
|
55
|
-
# This approach ensures that the entire files is valid before starting to process it.
|
|
56
|
-
# Ideal for when files may contain invalid lines.
|
|
57
|
-
# Not recommended for large files since the CSV or other parsing is performed sequentially during the
|
|
58
|
-
# upload process.
|
|
55
|
+
# Uploads the lines of the file, which the workers parse by the schema, so that each value reaches `#perform` as
|
|
56
|
+
# its type, such as a Date. This is the default, and the most performant, since the workers parse the lines.
|
|
59
57
|
# :hash
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
58
|
+
# Also parses every line by the schema when it is uploaded, so that a file with a value that is not valid is not
|
|
59
|
+
# uploaded at all. The lines are uploaded, as in :line mode. Not recommended for large files, since the lines
|
|
60
|
+
# are parsed one after the other during the upload.
|
|
61
|
+
# :array
|
|
62
|
+
# Cannot be used with a schema.
|
|
63
63
|
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
64
|
+
# Without a schema the mode does not change how a file is uploaded: its lines are uploaded, unless the upload is
|
|
65
|
+
# given `stream_mode: :array` or `stream_mode: :hash`, which parses each line into an Array or a Hash first, see
|
|
66
|
+
# RocketJob::Batch::IO#upload. A category with a format whose files have a header row, such as CSV, cannot be
|
|
67
|
+
# uploaded with `stream_mode: :hash` without a schema.
|
|
67
68
|
field :mode, type: ::Mongoid::StringifiedSymbol, default: :line
|
|
68
69
|
validates_inclusion_of :mode, in: %i[line array hash]
|
|
69
70
|
|
|
@@ -80,11 +81,27 @@ module RocketJob
|
|
|
80
81
|
# Do not cleanse the columns names supplied in the header row.
|
|
81
82
|
#
|
|
82
83
|
# Note: Submit a ticket if you have other cleansers that you want added.
|
|
84
|
+
#
|
|
85
|
+
# Deprecated, use `schema`, whose fields name the keys of each record, and which the columns of the header row
|
|
86
|
+
# are matched to whatever their case, spaces and punctuation.
|
|
83
87
|
field :header_cleanser, type: ::Mongoid::StringifiedSymbol, default: :default
|
|
84
88
|
validates :header_cleanser, inclusion: %i[default none]
|
|
85
89
|
|
|
86
90
|
validates_inclusion_of :serializer, in: %i[none compress encrypt]
|
|
87
91
|
|
|
92
|
+
# Returns [Array<String>] the names of the deprecated options in the supplied properties of an input category,
|
|
93
|
+
# with String keys, see Category::Base.deprecated_column_options: `allowed_columns`, `required_columns`,
|
|
94
|
+
# `skip_unknown` when true, `header_cleanser` when :none, and `columns` without a schema, since with one they
|
|
95
|
+
# hold the header row read from the file.
|
|
96
|
+
def self.deprecated_column_options(properties)
|
|
97
|
+
names = []
|
|
98
|
+
names << "columns" if properties["columns"].present? && properties["schema"].blank?
|
|
99
|
+
names.concat(%w[allowed_columns required_columns].select { |name| properties[name].present? })
|
|
100
|
+
names << "skip_unknown" if [true, "true"].include?(properties["skip_unknown"])
|
|
101
|
+
names << "header_cleanser" if properties["header_cleanser"].to_s == "none"
|
|
102
|
+
names
|
|
103
|
+
end
|
|
104
|
+
|
|
88
105
|
# Cleanses the header column names when `cleanse_header` is true
|
|
89
106
|
def cleanse_header!
|
|
90
107
|
return unless header_cleanser == :default
|
|
@@ -96,6 +113,8 @@ module RocketJob
|
|
|
96
113
|
end
|
|
97
114
|
|
|
98
115
|
def tabular
|
|
116
|
+
return @tabular ||= schema_tabular if schema.present?
|
|
117
|
+
|
|
99
118
|
@tabular ||= IOStreams::Tabular.new(
|
|
100
119
|
columns: columns,
|
|
101
120
|
format: format == :auto ? nil : format,
|
|
@@ -133,36 +152,143 @@ module RocketJob
|
|
|
133
152
|
@tabular = nil
|
|
134
153
|
end
|
|
135
154
|
|
|
136
|
-
|
|
137
|
-
|
|
155
|
+
if schema.present?
|
|
156
|
+
# IOStreams reads the text of a file with a schema as UTF-8, and raises for text that is not valid UTF-8,
|
|
157
|
+
# unless an encoding is set on the supplied path, which reads it instead. A schema set on the supplied path
|
|
158
|
+
# is kept.
|
|
159
|
+
path.schema(schema) if path.schema.nil?
|
|
160
|
+
elsif tabular?
|
|
161
|
+
# Remove non-printable characters from tabular input formats.
|
|
138
162
|
# Cannot change the length of fixed width lines.
|
|
139
163
|
replace = format == :fixed ? " " : ""
|
|
140
|
-
path.
|
|
164
|
+
path.encoding("UTF-8", cleaner: :printable, replace: replace)
|
|
141
165
|
end
|
|
142
166
|
path
|
|
143
167
|
end
|
|
144
168
|
|
|
145
|
-
#
|
|
169
|
+
# Yields each record read from the supplied path for an upload, see RocketJob::Batch::IO#upload, in the supplied
|
|
170
|
+
# mode, otherwise in :line mode, see `IOStreams::Stream#each`.
|
|
171
|
+
#
|
|
172
|
+
# With a schema it yields the lines of the file in the supplied mode, otherwise in this category's mode, see
|
|
173
|
+
# #mode, and in :hash mode parses each line after the header row by the schema first, so that a value that is not
|
|
174
|
+
# valid raises before any record is processed.
|
|
175
|
+
def each_record(path, mode: nil, **args, &)
|
|
176
|
+
return path.each(mode || :line, **args, &) if schema.blank?
|
|
177
|
+
|
|
178
|
+
mode ||= self.mode
|
|
179
|
+
if mode == :array
|
|
180
|
+
raise(ArgumentError,
|
|
181
|
+
"Input category #{name} has a schema, which cannot be uploaded in :array mode, use :line or :hash")
|
|
182
|
+
end
|
|
183
|
+
return path.each(:line, **args, &) if mode == :line
|
|
184
|
+
|
|
185
|
+
path.each(:line, **args) do |line|
|
|
186
|
+
# The lines before the header row, and the header row itself, are read by #extract_header_callback.
|
|
187
|
+
tabular.record_parse(line) unless tabular.header?
|
|
188
|
+
yield(line)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Returns the lambda that the upload calls with each record before the first that it stores, instead of storing
|
|
193
|
+
# it, until the lambda returns true, see `Sliced::Writer::Input`: it reads the header row, when the file has one,
|
|
194
|
+
# then calls the supplied `on_first` with it. Otherwise it calls `on_first` with the first record. Returns nil
|
|
195
|
+
# when there is neither.
|
|
196
|
+
#
|
|
197
|
+
# A blank line before the header row is skipped, as IOStreams skips it when it reads the header row itself, such
|
|
198
|
+
# as in :hash mode, so that the header row is not stored as a record.
|
|
199
|
+
#
|
|
200
|
+
# The header row is a line, or an Array when the upload parses each line with `stream_mode: :array`, which the
|
|
201
|
+
# format returns as it is.
|
|
146
202
|
def extract_header_callback(on_first)
|
|
147
|
-
return on_first
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
203
|
+
return schema_header_callback(on_first) if schema.present?
|
|
204
|
+
return first_record_callback(on_first) unless tabular? && tabular.header?
|
|
205
|
+
|
|
206
|
+
lambda do |line|
|
|
207
|
+
next false if line.blank?
|
|
208
|
+
|
|
209
|
+
tabular.parse_header(line)
|
|
210
|
+
cleanse_header!
|
|
211
|
+
self.columns = tabular.header.columns
|
|
212
|
+
warn_repeated_columns
|
|
213
|
+
# Call chained on_first if present
|
|
214
|
+
on_first&.call(line)
|
|
215
|
+
true
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
# Reports a deprecation warning, see RocketJob::Deprecation, at the line of the application that uploads a file
|
|
222
|
+
# whose header row has columns that are the same key once cleansed, such as `a` and `A`, since each record holds
|
|
223
|
+
# the value of only the last of them, and IOStreams 3.0, which Rocket Job 8 requires, raises for them.
|
|
224
|
+
def warn_repeated_columns
|
|
225
|
+
repeated = columns.compact.tally.select { |_column, count| count > 1 }.keys
|
|
226
|
+
return if repeated.empty?
|
|
227
|
+
|
|
228
|
+
RocketJob::Deprecation.warn(
|
|
229
|
+
"a header row whose columns are the same key, #{repeated.collect(&:inspect).join(', ')}, is deprecated, " \
|
|
230
|
+
"since each record holds the value of only the last of them, and Rocket Job 8 raises: rename the " \
|
|
231
|
+
"columns of the file, or describe them with the schema: of the input category, see " \
|
|
232
|
+
"#{RocketJob::Deprecation::UPGRADE_GUIDE}"
|
|
233
|
+
)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Returns [IOStreams::Tabular] that reads the records of this category by its schema, see Category::Base#schema,
|
|
237
|
+
# and has read the header row that an upload read from the file, see #columns, so that the workers parse each line
|
|
238
|
+
# by it.
|
|
239
|
+
def schema_tabular
|
|
240
|
+
tabular = super
|
|
241
|
+
tabular.read_header(columns) if columns.present? && tabular.header?
|
|
242
|
+
tabular
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Returns the lambda that reads the header row of a file with a schema, see #extract_header_callback. Its columns
|
|
246
|
+
# are matched to the fields of the schema, which raises IOStreams::Errors::InvalidHeader when they do not match,
|
|
247
|
+
# and are kept as they are in the file, see #columns. A blank row before it, which IOStreams does not read as a
|
|
248
|
+
# header row, such as a line of only commas, is skipped, as IOStreams skips it when it reads the header row itself.
|
|
249
|
+
#
|
|
250
|
+
# With a schema, `columns` hold the header row of the file uploaded, so an upload reads the header row of its own
|
|
251
|
+
# file, rather than keep the columns of an earlier one, such as those that a job scheduled again copies from its
|
|
252
|
+
# last run, see Plugins::Cron.
|
|
253
|
+
def schema_header_callback(on_first)
|
|
254
|
+
self.columns = nil
|
|
255
|
+
@tabular = nil
|
|
256
|
+
return first_record_callback(on_first) unless tabular.header?
|
|
257
|
+
|
|
258
|
+
lambda do |line|
|
|
259
|
+
tabular.read_header(line)
|
|
260
|
+
next false if tabular.header?
|
|
261
|
+
|
|
262
|
+
self.columns = tabular.header.columns
|
|
263
|
+
# Call chained on_first if present
|
|
264
|
+
on_first&.call(line)
|
|
265
|
+
true
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# The schema describes the columns of the file, so the options that describe them without one cannot be set with
|
|
270
|
+
# it, see Category::Base#schema.
|
|
271
|
+
def fields_replaced_by_schema
|
|
272
|
+
replaced = super + %i[allowed_columns required_columns].select { |name| public_send(name).present? }
|
|
273
|
+
replaced << :skip_unknown if skip_unknown
|
|
274
|
+
replaced << :header_cleanser if header_cleanser == :none
|
|
275
|
+
replaced
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# The :array mode cannot be used with a schema, see #mode.
|
|
279
|
+
def schema_replaces_fields
|
|
280
|
+
super
|
|
281
|
+
errors.add(:mode, "cannot be :array with a schema, use :line or :hash") if schema.present? && mode == :array
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Returns the lambda that calls the supplied `on_first` with the first record uploaded, which is not stored, see
|
|
285
|
+
# #extract_header_callback, or nil without one.
|
|
286
|
+
def first_record_callback(on_first)
|
|
287
|
+
return unless on_first
|
|
288
|
+
|
|
289
|
+
lambda do |record|
|
|
290
|
+
on_first.call(record)
|
|
291
|
+
true
|
|
166
292
|
end
|
|
167
293
|
end
|
|
168
294
|
end
|
|
@@ -29,6 +29,13 @@ module RocketJob
|
|
|
29
29
|
slice_class: serializer_class
|
|
30
30
|
)
|
|
31
31
|
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# The schema gives the columns of the file written, see Category::Base#schema.
|
|
36
|
+
def fields_replaced_by_schema
|
|
37
|
+
columns.present? ? super + [:columns] : super
|
|
38
|
+
end
|
|
32
39
|
end
|
|
33
40
|
end
|
|
34
41
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module RocketJob
|
|
2
|
+
# Reports the use of a deprecated option or method as a Ruby deprecation warning, see `Kernel#warn`, as IOStreams
|
|
3
|
+
# does, see `IOStreams::Deprecation`:
|
|
4
|
+
# - Only while deprecation warnings are on, see `Warning[:deprecated]`. They are off by default, so production stays
|
|
5
|
+
# quiet, and on in tests run by Minitest or test-unit, or with `ruby -W:deprecated` or `RUBYOPT=-W:deprecated`.
|
|
6
|
+
# - Written to `$stderr`, unless the application routes them elsewhere, such as to its log, or raises them in its
|
|
7
|
+
# tests, by overriding `Warning.warn`.
|
|
8
|
+
#
|
|
9
|
+
# Each warning names the first line of the call stack outside Rocket Job, such as the line of a job class that
|
|
10
|
+
# defines a category with a deprecated option, rather than a line of Rocket Job.
|
|
11
|
+
module Deprecation
|
|
12
|
+
# The directory of the files of this library, whose lines are skipped when finding the line that used it.
|
|
13
|
+
LIB_DIR = "#{File.expand_path('..', __dir__)}/".freeze
|
|
14
|
+
|
|
15
|
+
# Where the upgrade guide says what replaces each deprecated option.
|
|
16
|
+
UPGRADE_GUIDE = "https://rocketjob.reidmorrison.com/upgrading.html#upgrading-to-v71".freeze
|
|
17
|
+
|
|
18
|
+
# Reports that the application used something deprecated, unless deprecation warnings are off, with the message,
|
|
19
|
+
# which says what is deprecated and what to use instead, after "Rocket Job: ", at the first line of the call stack
|
|
20
|
+
# outside this library and the libraries that call it, or without a line when there is none, such as in a worker
|
|
21
|
+
# that builds the job of a Dirmon entry.
|
|
22
|
+
#
|
|
23
|
+
# Returns nil.
|
|
24
|
+
def self.warn(message)
|
|
25
|
+
return unless Warning[:deprecated]
|
|
26
|
+
|
|
27
|
+
dirs = library_dirs
|
|
28
|
+
uplevel = caller_locations(1).index { |location| !library?(location, dirs) }
|
|
29
|
+
# Kernel#warn counts its own caller, this method, as level 0.
|
|
30
|
+
Kernel.warn("Rocket Job: #{message}", uplevel: uplevel && (uplevel + 1), category: :deprecated)
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns [Array<String>] the directories whose lines are skipped when finding the line of the application: this
|
|
35
|
+
# library, the other gems, which call it back, such as Active Support's callbacks and Mongoid, and Ruby's own
|
|
36
|
+
# library.
|
|
37
|
+
def self.library_dirs
|
|
38
|
+
gems = Gem.loaded_specs.each_value.filter_map { |spec| "#{spec.full_gem_path}/" unless spec.name == "rocketjob" }
|
|
39
|
+
[LIB_DIR, "#{RbConfig::CONFIG['rubylibdir']}/", *gems]
|
|
40
|
+
end
|
|
41
|
+
private_class_method :library_dirs
|
|
42
|
+
|
|
43
|
+
# Returns [true|false] whether the line of the call stack is in a file of a library, see .library_dirs, or of Ruby
|
|
44
|
+
# itself.
|
|
45
|
+
def self.library?(location, dirs)
|
|
46
|
+
path = (location.absolute_path || location.path).to_s
|
|
47
|
+
path.start_with?("<internal:") || dirs.any? { |dir| path.start_with?(dir) }
|
|
48
|
+
end
|
|
49
|
+
private_class_method :library?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -82,8 +82,18 @@ module RocketJob
|
|
|
82
82
|
self.description ||= "Copying to #{target_url}"
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
# Applies the supplied streams to the path, in order. The `none` stream, which `source_streams` and
|
|
86
|
+
# `target_streams` default to, removes the streams before it, so that on its own the file is read or written as
|
|
87
|
+
# it is stored, see `IOStreams::Stream#raw`, rather than with IOStreams' deprecated `stream(:none)`.
|
|
85
88
|
def apply_streams(path, streams)
|
|
86
|
-
streams
|
|
89
|
+
streams = streams.to_a
|
|
90
|
+
none = streams.rindex { |stream, _args| stream.to_s == "none" }
|
|
91
|
+
if none
|
|
92
|
+
streams = streams.drop(none + 1)
|
|
93
|
+
return path.raw if streams.empty?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
streams.each do |stream, args|
|
|
87
97
|
stream_args = args.nil? ? {} : decode_args(args)
|
|
88
98
|
path.stream(stream.to_sym, **stream_args)
|
|
89
99
|
end
|
|
@@ -27,41 +27,42 @@ module RocketJob
|
|
|
27
27
|
# MyCronJob.create!(retry_limit: 0)
|
|
28
28
|
#
|
|
29
29
|
# Below is a table of the delay for each retry attempt, as well as the _total_ duration spent retrying a job for
|
|
30
|
-
# the specified number of retries, excluding actual processing time.
|
|
30
|
+
# the specified number of retries, excluding actual processing time. The delay before retry `n` is
|
|
31
|
+
# `(n - 1)**4 + 5` seconds.
|
|
31
32
|
#
|
|
32
33
|
# |---------|------------|----------------|
|
|
33
34
|
# | Attempt | Delay | Total Duration |
|
|
34
35
|
# |---------|------------|----------------|
|
|
35
|
-
# | 01 |
|
|
36
|
-
# | 02 |
|
|
37
|
-
# | 03 |
|
|
38
|
-
# | 04 |
|
|
39
|
-
# | 05 |
|
|
40
|
-
# | 06 |
|
|
41
|
-
# | 07 |
|
|
42
|
-
# | 08 |
|
|
43
|
-
# | 09 |
|
|
44
|
-
# | 10 |
|
|
45
|
-
# | 11 |
|
|
46
|
-
# | 12 |
|
|
47
|
-
# | 13 |
|
|
48
|
-
# | 14 |
|
|
49
|
-
# | 15 |
|
|
50
|
-
# | 16 |
|
|
51
|
-
# | 17 | 18h 12m |
|
|
52
|
-
# | 18 |
|
|
53
|
-
# | 19 |
|
|
54
|
-
# | 20 | 1d
|
|
55
|
-
# | 21 |
|
|
56
|
-
# | 22 |
|
|
57
|
-
# | 23 |
|
|
58
|
-
# | 24 | 3d
|
|
59
|
-
# | 25 |
|
|
60
|
-
# | 26 |
|
|
61
|
-
# | 27 |
|
|
62
|
-
# | 28 |
|
|
63
|
-
# | 29 |
|
|
64
|
-
# | 30 |
|
|
36
|
+
# | 01 | 5.000s | 5.000s |
|
|
37
|
+
# | 02 | 6.000s | 11.000s |
|
|
38
|
+
# | 03 | 21.000s | 32.000s |
|
|
39
|
+
# | 04 | 1m 26s | 1m 58s |
|
|
40
|
+
# | 05 | 4m 21s | 6m 19s |
|
|
41
|
+
# | 06 | 10m 30s | 16m 49s |
|
|
42
|
+
# | 07 | 21m 41s | 38m 30s |
|
|
43
|
+
# | 08 | 40m 6s | 1h 18m |
|
|
44
|
+
# | 09 | 1h 8m | 2h 26m |
|
|
45
|
+
# | 10 | 1h 49m | 4h 16m |
|
|
46
|
+
# | 11 | 2h 46m | 7h 3m |
|
|
47
|
+
# | 12 | 4h 4m | 11h 7m |
|
|
48
|
+
# | 13 | 5h 45m | 16h 52m |
|
|
49
|
+
# | 14 | 7h 56m | 1d 0h 49m |
|
|
50
|
+
# | 15 | 10h 40m | 1d 11h 29m |
|
|
51
|
+
# | 16 | 14h 3m | 2d 1h 33m |
|
|
52
|
+
# | 17 | 18h 12m | 2d 19h 45m |
|
|
53
|
+
# | 18 | 23h 12m | 3d 18h 57m |
|
|
54
|
+
# | 19 | 1d 5h 9m | 5d 0h 7m |
|
|
55
|
+
# | 20 | 1d 12h 12m | 6d 12h 19m |
|
|
56
|
+
# | 21 | 1d 20h 26m | 8d 8h 46m |
|
|
57
|
+
# | 22 | 2d 6h 1m | 10d 14h 47m |
|
|
58
|
+
# | 23 | 2d 17h 4m | 13d 7h 51m |
|
|
59
|
+
# | 24 | 3d 5h 44m | 16d 13h 36m |
|
|
60
|
+
# | 25 | 3d 20h 9m | 20d 9h 45m |
|
|
61
|
+
# | 26 | 4d 12h 30m | 24d 22h 16m |
|
|
62
|
+
# | 27 | 5d 6h 56m | 30d 5h 12m |
|
|
63
|
+
# | 28 | 6d 3h 37m | 36d 8h 50m |
|
|
64
|
+
# | 29 | 7d 2h 44m | 43d 11h 34m |
|
|
65
|
+
# | 30 | 8d 4h 28m | 51d 16h 2m |
|
|
65
66
|
# |---------|------------|----------------|
|
|
66
67
|
module Retry
|
|
67
68
|
extend ActiveSupport::Concern
|
|
@@ -70,7 +71,7 @@ module RocketJob
|
|
|
70
71
|
after_fail :rocket_job_retry
|
|
71
72
|
|
|
72
73
|
# Maximum number of times to retry this job.
|
|
73
|
-
# The default of 25
|
|
74
|
+
# The default of 25 retries spans about 20 days, see the table above.
|
|
74
75
|
field :retry_limit, type: Integer, default: 25, class_attribute: true, user_editable: true, copy_on_restart: true
|
|
75
76
|
|
|
76
77
|
# List of times when this job failed
|
|
@@ -115,7 +116,7 @@ module RocketJob
|
|
|
115
116
|
#
|
|
116
117
|
# For example, to see the durations for the first 25 retries:
|
|
117
118
|
# count = 25
|
|
118
|
-
# intervals = (
|
|
119
|
+
# intervals = (0...count).map { |failures| (failures**4) + 5 }
|
|
119
120
|
#
|
|
120
121
|
# Display the above intervals as human readable durations:
|
|
121
122
|
# intervals.map { |seconds| RocketJob.seconds_as_duration(seconds) }
|
|
@@ -124,7 +125,7 @@ module RocketJob
|
|
|
124
125
|
# RocketJob.seconds_as_duration(intervals.sum)
|
|
125
126
|
#
|
|
126
127
|
# Or, to see the total durations based on the number of retries:
|
|
127
|
-
# (0
|
|
128
|
+
# (0...count).map { |i| "#{i + 1} ==> #{RocketJob.seconds_as_duration(intervals[0..i].sum)}" }
|
|
128
129
|
def rocket_job_retry_seconds_to_delay
|
|
129
130
|
(rocket_job_failure_count**4) + 5
|
|
130
131
|
end
|
|
@@ -9,8 +9,9 @@ module RocketJob
|
|
|
9
9
|
#
|
|
10
10
|
# Parameters
|
|
11
11
|
# on_first: [Proc]
|
|
12
|
-
#
|
|
13
|
-
# Useful for extracting the header row
|
|
12
|
+
# Called with each line before the first that is stored, instead of storing it, until it returns true.
|
|
13
|
+
# Useful for extracting the header row, after any blank lines before it, see
|
|
14
|
+
# Category::Input#extract_header_callback.
|
|
14
15
|
# Default: nil
|
|
15
16
|
#
|
|
16
17
|
# slice_size: [Integer]
|
|
@@ -42,8 +43,7 @@ module RocketJob
|
|
|
42
43
|
|
|
43
44
|
def <<(line)
|
|
44
45
|
if @on_first
|
|
45
|
-
@on_first.call(line)
|
|
46
|
-
@on_first = nil
|
|
46
|
+
@on_first = nil if @on_first.call(line)
|
|
47
47
|
return self
|
|
48
48
|
end
|
|
49
49
|
@slice << line
|
data/lib/rocket_job/version.rb
CHANGED
data/lib/rocketjob.rb
CHANGED
|
@@ -23,6 +23,7 @@ module RocketJob
|
|
|
23
23
|
autoload :ActiveWorker, "rocket_job/active_worker"
|
|
24
24
|
autoload :Batch, "rocket_job/batch"
|
|
25
25
|
autoload :CLI, "rocket_job/cli"
|
|
26
|
+
autoload :Deprecation, "rocket_job/deprecation"
|
|
26
27
|
autoload :DirmonEntry, "rocket_job/dirmon_entry"
|
|
27
28
|
autoload :Event, "rocket_job/event"
|
|
28
29
|
autoload :Heartbeat, "rocket_job/heartbeat"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rocketjob
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.
|
|
4
|
+
version: 7.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Reid Morrison
|
|
@@ -57,14 +57,14 @@ dependencies:
|
|
|
57
57
|
requirements:
|
|
58
58
|
- - "~>"
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '2.
|
|
60
|
+
version: '2.2'
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '2.
|
|
67
|
+
version: '2.2'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: mongoid
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -145,6 +145,7 @@ files:
|
|
|
145
145
|
- lib/rocket_job/category/output.rb
|
|
146
146
|
- lib/rocket_job/cli.rb
|
|
147
147
|
- lib/rocket_job/config.rb
|
|
148
|
+
- lib/rocket_job/deprecation.rb
|
|
148
149
|
- lib/rocket_job/dirmon_entry.rb
|
|
149
150
|
- lib/rocket_job/event.rb
|
|
150
151
|
- lib/rocket_job/extensions/iostreams/path.rb
|
|
@@ -226,7 +227,7 @@ metadata:
|
|
|
226
227
|
changelog_uri: https://github.com/reidmorrison/rocketjob/blob/main/CHANGELOG.md
|
|
227
228
|
documentation_uri: https://rocketjob.reidmorrison.com
|
|
228
229
|
homepage_uri: https://rocketjob.reidmorrison.com
|
|
229
|
-
source_code_uri: https://github.com/reidmorrison/rocketjob/tree/v7.
|
|
230
|
+
source_code_uri: https://github.com/reidmorrison/rocketjob/tree/v7.1.0
|
|
230
231
|
rubygems_mfa_required: 'true'
|
|
231
232
|
rdoc_options: []
|
|
232
233
|
require_paths:
|