worker_tools 0.2.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +320 -24
- data/lib/worker_tools/basics.rb +23 -11
- data/lib/worker_tools/benchmark.rb +2 -2
- data/lib/worker_tools/counters.rb +8 -9
- data/lib/worker_tools/csv_input.rb +6 -3
- data/lib/worker_tools/csv_output.rb +19 -33
- data/lib/worker_tools/errors.rb +10 -0
- data/lib/worker_tools/recorder.rb +19 -14
- data/lib/worker_tools/slack_error_notifier.rb +5 -1
- data/lib/worker_tools/utils/hash_with_indifferent_access_type.rb +9 -0
- data/lib/worker_tools/utils/serialized_array_type.rb +19 -0
- data/lib/worker_tools/version.rb +1 -1
- data/lib/worker_tools/xlsx_input.rb +9 -4
- data/lib/worker_tools/xlsx_output.rb +48 -43
- data/lib/worker_tools.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d73219c29b55493f0be76ce047dbe7f72259f4efaab15d1528230bdfc1920b5
|
4
|
+
data.tar.gz: a8ef47af79183aa6e6b465a0cbc6c7396ccccf3cb92574c386b956ebd3415056
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30287518cdce7bb9536d802fffdaa54b893bfcc811b03f1c226ea59b45787c0f5b4090b82796984e5c77741679bd035620cd3e38aecb4ebb0f1dcac9de50cb16
|
7
|
+
data.tar.gz: 18c1c3ff22097f710cea838c6cf4a8058c35343d7f76312e063771ef2dc02c6ad0fbdace866c1b3dc9e21ab3545ea91f041ff16f4d540402be601c44c834d0dc
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [1.0.0] - 2022-05-20
|
4
|
+
|
5
|
+
Compared to 0.2.1
|
6
|
+
|
7
|
+
### New
|
8
|
+
|
9
|
+
- Namespaced errors and non failure logic
|
10
|
+
- Support for status running and complete_with_warnings
|
11
|
+
- Benchmark wrapper
|
12
|
+
- Counters wrapper
|
13
|
+
- Notes instead of information field
|
14
|
+
- Filter for slack errors (`slack_error_notifiable`)
|
15
|
+
- Model attachments convention
|
16
|
+
- Complete specification of csv open arguments
|
17
|
+
|
18
|
+
### BREAKING CHANGES
|
19
|
+
|
20
|
+
Instead of writing the final csv or xlsx to a folder, the gem assumes that the model provides an add_attachment method.
|
21
|
+
|
22
|
+
Both csv and xlsx output modules use entry hashes for content (`csv_output_entries`, `xlsx_output_entries`). The mapper methods `csv_output_row_values` and `xlsx_output_row_values` do not need (in most cases) to be defined, there is a default now. See the complete examples in the README.
|
23
|
+
|
24
|
+
These methods were renamed
|
25
|
+
|
26
|
+
- `xlsx_output_values` => `xlsx_output_row_values`
|
27
|
+
- `xlsx_insert_headers` => `xlsx_output_insert_headers`
|
28
|
+
- `xlsx_insert_rows` => ` xlsx_output_insert_rows`
|
29
|
+
- `xlsx_iterators` => `xlsx_output_iterators`
|
30
|
+
- `xlsx_style_columns` => `xlsx_output_style_columns`
|
31
|
+
- `xlsx_write_sheet` => `xlsx_output_write_sheet`
|
32
|
+
|
33
|
+
These methods were removed
|
34
|
+
|
35
|
+
- `add_info` in favor of `add_note`
|
36
|
+
- `create_model_if_not_available`, a model is always created.
|
37
|
+
- `format_log_message`, `format_info_message` in favor of `format_message`
|
38
|
+
- `csv_output_target`
|
39
|
+
- `cvs_output_target_folder`
|
40
|
+
- `csv_output_target_file_name`
|
41
|
+
- `csv_ouput_ensure_target_folder`
|
42
|
+
- `csv_output_write_target`
|
43
|
+
- `xlsx_output_target`
|
44
|
+
- `xlsx_output_target_folder`
|
45
|
+
- `xlsx_ensure_output_target_folder`
|
46
|
+
- `xlsx_write_output_target`
|
47
|
+
|
48
|
+
[1.0.0]: https://github.com/i22-digitalagentur/worker-tools/compare/0.2.1...1.0.0
|
data/README.md
CHANGED
@@ -1,13 +1,53 @@
|
|
1
1
|
# WorkerTools
|
2
2
|
|
3
|
-
[![Build Status]
|
3
|
+
[![Build Status][build-badge]][build-url]
|
4
|
+
[![MIT License][license-shield]][license-url]
|
5
|
+
[![Release][release-shield]][release-url]
|
6
|
+
![Maintenance][maintained-shield]
|
7
|
+
|
8
|
+
<br>
|
9
|
+
|
10
|
+
<details open="open">
|
11
|
+
<summary>Table of Contents</summary>
|
12
|
+
<ol>
|
13
|
+
<li>
|
14
|
+
<a href="#about-the-project">About The Project</a>
|
15
|
+
</li>
|
16
|
+
<li>
|
17
|
+
<a href="#installation">Installation</a>
|
18
|
+
</li>
|
19
|
+
<li><a href="#conventions">Conventions</a></li>
|
20
|
+
<li><a href="#module-basics">Module 'Basics'</a></li>
|
21
|
+
<li><a href="#module-recorder">Module 'Recorder'</a></li>
|
22
|
+
<li><a href="#module-slackerrornotifier">Module 'SlackErrorNotifier'</a></li>
|
23
|
+
<li><a href="#wrappers">Wrappers</a></li>
|
24
|
+
<li><a href="#module-notes">Module 'Notes'</a></li>
|
25
|
+
<li><a href="#attachments">Attachments</a></li>
|
26
|
+
<li>
|
27
|
+
<a href="#complete-examples">Complete Examples</a>
|
28
|
+
<ul>
|
29
|
+
<li><a href="#xlsx-input-example">XLSX Input Example</a></li>
|
30
|
+
<li><a href="#csv-input-example">CSV Input Example</a></li>
|
31
|
+
<li><a href="#csv-output-example">CSV Output Example</a></li>
|
32
|
+
<li><a href="#xlsx-output-example">XLSX Output Example</a></li>
|
33
|
+
</ul>
|
34
|
+
</li>
|
35
|
+
<li><a href="#changelog">Changelog</a></li>
|
36
|
+
<li><a href="#requirements">Requirements</a></li>
|
37
|
+
<li><a href="#contributing">Contributing</a></li>
|
38
|
+
<li><a href="#license">License</a></li>
|
39
|
+
<li><a href="#acknowledgement">Acknowledgement</a></li>
|
40
|
+
</ol>
|
41
|
+
</details>
|
42
|
+
|
43
|
+
## About The Project
|
4
44
|
|
5
45
|
WorkerTools is a collection of modules meant to speed up how we write background tasks following a few basic patterns. The structure of plain independent modules with limited abstraction allows to define and override a few methods according to your needs without requiring a deep investment in the library.
|
6
46
|
|
7
47
|
These modules provide some features and conventions to address the following points with little configuration on your part:
|
8
48
|
|
9
49
|
- How to save the state the task.
|
10
|
-
- How to save
|
50
|
+
- How to save notes relevant to the admins / customers.
|
11
51
|
- How to log the details
|
12
52
|
- How to handle exceptions and send notifications
|
13
53
|
- How to process CSV files (as input and output)
|
@@ -32,13 +72,20 @@ Or install it yourself as:
|
|
32
72
|
|
33
73
|
## Conventions
|
34
74
|
|
35
|
-
Most of the modules require an ActiveRecord model to keep track of the state,
|
75
|
+
Most of the modules require an ActiveRecord model to keep track of the state, notes, and files related to the job. The class of this model is typically an Import, Export, Report.. or something more generic like a JobEntry.
|
36
76
|
|
37
77
|
An example of this model for an Import using Paperclip would be something like this:
|
38
78
|
|
39
79
|
```ruby
|
40
80
|
class Import < ApplicationRecord
|
41
|
-
enum state:
|
81
|
+
enum state: %w[
|
82
|
+
waiting
|
83
|
+
complete
|
84
|
+
complete_with_warnings
|
85
|
+
failed
|
86
|
+
running
|
87
|
+
].map { |e| [e, e] }.to_h
|
88
|
+
|
42
89
|
enum kind: { foo: 0, bar: 1 }
|
43
90
|
|
44
91
|
has_attached_file :attachment
|
@@ -48,7 +95,9 @@ class Import < ApplicationRecord
|
|
48
95
|
end
|
49
96
|
```
|
50
97
|
|
51
|
-
The state `complete` and `failed` are used by the modules. Both `state` and `kind` could be an enum or just a string field.
|
98
|
+
The state `complete` and `failed` are used by the modules. Both `state` and `kind` could be an enum or just a string field. Whether you have one, none or many attachments, and which library you use to handle it's up to you.
|
99
|
+
|
100
|
+
The state `complete_with_warnings` indicates that the model contains notes that did not lead to a failure but should get some attention. By default those levels are `warning` and `errors` and can be customized.
|
52
101
|
|
53
102
|
In this case the migration would be something like this:
|
54
103
|
|
@@ -56,8 +105,8 @@ In this case the migration would be something like this:
|
|
56
105
|
def change
|
57
106
|
create_table :imports do |t|
|
58
107
|
t.integer :kind, null: false
|
59
|
-
t.
|
60
|
-
t.
|
108
|
+
t.string :state, default: 'waiting', null: false
|
109
|
+
t.json :notes, default: []
|
61
110
|
t.json :options, default: {}
|
62
111
|
t.json :meta, default: {}
|
63
112
|
|
@@ -67,7 +116,7 @@ In this case the migration would be something like this:
|
|
67
116
|
|
68
117
|
t.timestamps
|
69
118
|
end
|
70
|
-
|
119
|
+
```
|
71
120
|
|
72
121
|
## Module 'Basics'
|
73
122
|
|
@@ -96,12 +145,13 @@ end
|
|
96
145
|
|
97
146
|
The basics module contains a `perform` method, which is the usual entry point for ApplicationJob and Sidekiq. It can receive the id of the model, the model instance, or nothing, in which case it will attempt to create this model on its own.
|
98
147
|
|
148
|
+
By default errors subclassed from WorkerTools::Errors::Invalid (such as those related to wrong headers in the input modules) will not raise and mark the model as failed. The method `non_failure_error?` lets you modifiy this behaviour.
|
99
149
|
|
100
150
|
## Module 'Recorder'
|
101
151
|
|
102
|
-
Provides some methods to manage a log and the `
|
152
|
+
Provides some methods to manage a log and the `notes` field of the model. The main methods are `add_info`, `add_log`, and `record` (which both logs and appends the message to the notes field). See all methods in [recorder](/lib/worker_tools/recorder.rb)
|
103
153
|
|
104
|
-
This module has a _recoder_ wrapper that will register the exception details into the log and
|
154
|
+
This module has a _recoder_ wrapper that will register the exception details into the log and notes field in case of error:
|
105
155
|
|
106
156
|
```ruby
|
107
157
|
class MyImporter
|
@@ -127,24 +177,43 @@ If you only want the logger functions, without worrying about persisting a model
|
|
127
177
|
|
128
178
|
## Module SlackErrorNotifier
|
129
179
|
|
130
|
-
|
180
|
+
Provides a Slack error notifier wrapper. To do this, you need to define SLACK_NOTIFIER_WEBHOOK as well as SLACK_NOTIFIER_CHANNEL. Then you need to include the SlackErrorNotifier module in your class and append slack_error_notifier to your wrappers. Below you can see an example.
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
class MyImporter
|
184
|
+
include WorkerTools::SlackErrorNotifier
|
185
|
+
|
186
|
+
wrappers :slack_error_notifier
|
187
|
+
|
188
|
+
def perform
|
189
|
+
with_wrapper_logger do
|
190
|
+
# do stuff
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
See all methods in [slack_error_notifier](/lib/worker_tools/slack_error_notifier.rb)
|
131
197
|
|
132
198
|
## Module CSV Input
|
133
199
|
|
134
|
-
[csv_input](/lib/worker_tools/csv_input.rb)
|
200
|
+
See all methods in [csv_input](/lib/worker_tools/csv_input.rb)
|
135
201
|
|
136
202
|
## Module CSV Output
|
137
203
|
|
138
|
-
[csv_output](/lib/worker_tools/csv_output.rb)
|
204
|
+
See all methods in [csv_output](/lib/worker_tools/csv_output.rb)
|
139
205
|
|
140
206
|
## Module XLSX Input
|
141
207
|
|
142
|
-
[xlsx_input](/lib/worker_tools/xlsx_input.rb)
|
208
|
+
See all methods in [xlsx_input](/lib/worker_tools/xlsx_input.rb)
|
143
209
|
|
210
|
+
## Module XLSX Output
|
211
|
+
|
212
|
+
See all methods in [xlsx_output](/lib/worker_tools/xlsx_output.rb)
|
144
213
|
|
145
214
|
## Wrappers
|
146
215
|
|
147
|
-
In the [basics module](/lib/worker_tools/basics.rb), `perform` calls your custom method `run` to do the actual work of the task, and wraps it around any methods expecting a block that you might have had defined using `wrappers`.
|
216
|
+
In the [basics module](/lib/worker_tools/basics.rb), `perform` calls your custom method `run` to do the actual work of the task, and wraps it around any methods expecting a block that you might have had defined using `wrappers`. That gives us a systematic way to add logic depending on the output of `run` and any exceptions that might arise, such as logging the error and context, sending a chat notification, retrying under some circumstances, etc.
|
148
217
|
|
149
218
|
The following code
|
150
219
|
|
@@ -200,13 +269,14 @@ def perform(model_id)
|
|
200
269
|
end
|
201
270
|
```
|
202
271
|
|
203
|
-
##
|
272
|
+
## Counter
|
204
273
|
|
205
274
|
There is a counter wrapper that you can use to add custom counters to the meta attribute. To do this, you need to complete the following tasks:
|
275
|
+
|
206
276
|
- include WorkerTools::Counters to your class
|
207
277
|
- add :counters to the wrappers method props
|
208
278
|
- call counters method with your custom counters
|
209
|
-
You can see an example below. After that, you can access your custom counters via the meta attribute.
|
279
|
+
You can see an example below. After that, you can access your custom counters via the meta attribute.
|
210
280
|
|
211
281
|
```ruby
|
212
282
|
class MyImporter
|
@@ -219,13 +289,15 @@ class MyImporter
|
|
219
289
|
end
|
220
290
|
|
221
291
|
def example_foo_counter_methods
|
222
|
-
|
223
|
-
|
292
|
+
# you can use the increment helper
|
224
293
|
10.times { increment_foo }
|
225
294
|
|
226
|
-
|
295
|
+
# the counter works like a regular accessor, you can read it and modify it
|
296
|
+
# directly
|
297
|
+
self.bar = 100
|
298
|
+
puts bar # => 100
|
227
299
|
end
|
228
|
-
|
300
|
+
|
229
301
|
# ..
|
230
302
|
end
|
231
303
|
```
|
@@ -236,7 +308,7 @@ There is a benchmark wrapper that you can use to record the benchmark. The only
|
|
236
308
|
|
237
309
|
```ruby
|
238
310
|
class MyImporter
|
239
|
-
include WorkerTools::
|
311
|
+
include WorkerTools::Benchmark
|
240
312
|
wrappers :benchmark
|
241
313
|
|
242
314
|
def run
|
@@ -247,11 +319,235 @@ class MyImporter
|
|
247
319
|
end
|
248
320
|
```
|
249
321
|
|
322
|
+
## Module 'Notes'
|
323
|
+
|
324
|
+
If you use ActiveRecord you may need to modify the serializer as well as deserializer from the note attribute. After that you can easily serialize hashes and array of hashes with indifferent access. For that purpose the gem provides two utility methods. (HashWithIndifferentAccessType, SerializedArrayType). There is an example of how you can use it.
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
class ServiceTask < ApplicationRecord
|
328
|
+
|
329
|
+
attribute :notes, SerializedArrayType.new(type: HashWithIndifferentAccessType.new)
|
330
|
+
end
|
331
|
+
```
|
332
|
+
|
333
|
+
See all methods in [utils](/lib/worker_tools/utils)
|
334
|
+
|
335
|
+
## Attachments
|
336
|
+
|
337
|
+
The modules that generate a file expect the model to provide an `add_attachment` method with following signature:
|
338
|
+
|
339
|
+
```ruby
|
340
|
+
def add_attachment(file, file_name: nil, content_type: nil)
|
341
|
+
# your logic
|
342
|
+
end
|
343
|
+
```
|
344
|
+
|
345
|
+
You can skip this convention by overwriting the module related method, for example after including `CsvOutput`
|
346
|
+
|
347
|
+
```ruby
|
348
|
+
def csv_output_add_attachment
|
349
|
+
# default implementation
|
350
|
+
# model.add_attachment(csv_output_tmp_file, file_name: csv_output_file_name, content_type: 'text/csv')
|
351
|
+
|
352
|
+
# your method
|
353
|
+
ftp_upload(csv_output_tmp_file)
|
354
|
+
end
|
355
|
+
```
|
356
|
+
|
357
|
+
## Complete Examples
|
358
|
+
|
359
|
+
### XLSX Input Example
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
class XlsxInputExample
|
363
|
+
include Sidekiq::Worker
|
364
|
+
include WorkerTools::Basics
|
365
|
+
include WorkerTools::Recorder
|
366
|
+
include WorkerTools::XlsxInput
|
367
|
+
|
368
|
+
wrappers %i[basics recorder]
|
369
|
+
|
370
|
+
def model_class
|
371
|
+
Import
|
372
|
+
end
|
373
|
+
|
374
|
+
def model_kind
|
375
|
+
'xlsx_input_example'
|
376
|
+
end
|
377
|
+
|
378
|
+
def run
|
379
|
+
xlsx_input_foreach.each { |row| SomeModel.create!(row) }
|
380
|
+
end
|
381
|
+
|
382
|
+
def xlsx_input_columns
|
383
|
+
{
|
384
|
+
foo: 'Your Foo',
|
385
|
+
bar: 'Your Bar'
|
386
|
+
}
|
387
|
+
end
|
388
|
+
end
|
389
|
+
```
|
390
|
+
|
391
|
+
### CSV Input Example
|
392
|
+
|
393
|
+
```ruby
|
394
|
+
class CsvInputExample
|
395
|
+
include Sidekiq::Worker
|
396
|
+
include WorkerTools::Basics
|
397
|
+
include WorkerTools::Recorder
|
398
|
+
include WorkerTools::CsvInput
|
399
|
+
|
400
|
+
wrappers %i[basics recorder]
|
401
|
+
|
402
|
+
def model_class
|
403
|
+
Import
|
404
|
+
end
|
405
|
+
|
406
|
+
def model_kind
|
407
|
+
'csv_input_example'
|
408
|
+
end
|
409
|
+
|
410
|
+
def csv_input_columns
|
411
|
+
{
|
412
|
+
flavour: 'Flavour',
|
413
|
+
number: 'Number'
|
414
|
+
}
|
415
|
+
end
|
416
|
+
|
417
|
+
def run
|
418
|
+
csv_input_foreach.map { |row| do_something row_to_attributes(row) }
|
419
|
+
end
|
420
|
+
|
421
|
+
def row_to_attributes(row)
|
422
|
+
{
|
423
|
+
flavour: row['flavour'].downcase,
|
424
|
+
number: row['number'].to_i * 10
|
425
|
+
}
|
426
|
+
end
|
427
|
+
end
|
428
|
+
```
|
429
|
+
|
430
|
+
### CSV Output Example
|
431
|
+
|
432
|
+
```ruby
|
433
|
+
# More complex example with CsvOutput
|
434
|
+
class CsvOutputExample
|
435
|
+
include Sidekiq::Worker
|
436
|
+
include WorkerTools::Basics
|
437
|
+
include WorkerTools::CsvOutput
|
438
|
+
include WorkerTools::Recorder
|
439
|
+
|
440
|
+
wrappers %i[basics recorder]
|
441
|
+
|
442
|
+
def model_class
|
443
|
+
Report
|
444
|
+
end
|
445
|
+
|
446
|
+
def model_kind
|
447
|
+
'csv_out_example'
|
448
|
+
end
|
449
|
+
|
450
|
+
def model_file_name
|
451
|
+
"#{model_kind}-#{Date.current}.csv"
|
452
|
+
end
|
453
|
+
|
454
|
+
def run
|
455
|
+
csv_output_write_file
|
456
|
+
end
|
457
|
+
|
458
|
+
def csv_output_column_headers
|
459
|
+
@csv_output_column_headers ||= {
|
460
|
+
foo: 'Foo',
|
461
|
+
bar: 'Bar'
|
462
|
+
}
|
463
|
+
end
|
464
|
+
|
465
|
+
def csv_output_entries
|
466
|
+
@csv_output_entries ||= User.includes(...).lazy.map do |user|
|
467
|
+
{
|
468
|
+
foo: user.foo,
|
469
|
+
bar: user.bar
|
470
|
+
}
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
end
|
475
|
+
```
|
476
|
+
|
477
|
+
### XLSX Output Example
|
478
|
+
|
479
|
+
```ruby
|
480
|
+
# ExampleXlsxOutput
|
481
|
+
class XlsxOutputExample
|
482
|
+
include Sidekiq::Worker
|
483
|
+
include WorkerTools::Basics
|
484
|
+
include WorkerTools::Recorder
|
485
|
+
include WorkerTools::XlsxOutput
|
486
|
+
|
487
|
+
wrappers %i[basics recorder]
|
488
|
+
|
489
|
+
def model_class
|
490
|
+
Export
|
491
|
+
end
|
492
|
+
|
493
|
+
def model_kind
|
494
|
+
'xlsx_output_example'
|
495
|
+
end
|
496
|
+
|
497
|
+
def run
|
498
|
+
xlsx_output_write_file
|
499
|
+
end
|
500
|
+
|
501
|
+
def xlsx_output_column_headers
|
502
|
+
@xlsx_output_column_headers ||= {
|
503
|
+
foo: 'Foo',
|
504
|
+
bar: 'Bar'
|
505
|
+
}
|
506
|
+
end
|
507
|
+
|
508
|
+
def xlsx_output_entries
|
509
|
+
@xlsx_output_entries ||= SomeArray.map do |entry|
|
510
|
+
{
|
511
|
+
foo: user.foo,
|
512
|
+
bar: user.bar
|
513
|
+
}
|
514
|
+
end
|
515
|
+
end
|
516
|
+
end
|
517
|
+
```
|
518
|
+
|
519
|
+
## Changelog
|
520
|
+
|
521
|
+
See [CHANGELOG](CHANGELOG.md)
|
522
|
+
|
523
|
+
## Requirements
|
524
|
+
|
525
|
+
- ruby > 2.3.1
|
526
|
+
|
250
527
|
## Contributing
|
251
528
|
|
252
|
-
|
529
|
+
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
253
530
|
|
531
|
+
1. Fork the Project
|
532
|
+
2. Create your Feature Branch (`git checkout -b feature/new_feature`)
|
533
|
+
3. Commit your Changes (`git commit -m 'feat: Add new feature'`)
|
534
|
+
4. Push to the Branch (`git push origin feature/new_feature`)
|
535
|
+
5. Open a Pull Request
|
254
536
|
|
255
537
|
## License
|
256
538
|
|
257
|
-
The gem is available
|
539
|
+
The gem is available under the MIT License. See `LICENSE` for more information.
|
540
|
+
|
541
|
+
## Acknowledgement
|
542
|
+
|
543
|
+
- [Img Shields](https://shields.io)
|
544
|
+
|
545
|
+
<!--shield-styles-->
|
546
|
+
|
547
|
+
[build-badge]: https://travis-ci.org/i22-digitalagentur/worker-tools.svg?branch=master
|
548
|
+
[build-url]: https://travis-ci.org/i22-digitalagentur/worker-tools
|
549
|
+
[maintained-shield]: https://img.shields.io/badge/Maintained%3F-yes-green.svg?style=flat
|
550
|
+
[release-shield]: https://img.shields.io/github/release/i22-digitalagentur/coverage-badge-creator.svg?style=flat
|
551
|
+
[release-url]: https://github.com/i22-digitalagentur/worker-tools/releases/
|
552
|
+
[license-shield]: https://img.shields.io/badge/License-MIT-yellow.svg?style=flat
|
553
|
+
[license-url]: https://github.com/i22-digitalagentur/worker-tools/blob/master/LICENSE
|
data/lib/worker_tools/basics.rb
CHANGED
@@ -6,7 +6,6 @@ module WorkerTools
|
|
6
6
|
|
7
7
|
included do
|
8
8
|
attr_writer :model
|
9
|
-
attr_accessor :information
|
10
9
|
|
11
10
|
def self.wrappers(*args)
|
12
11
|
@wrappers ||= args.flatten
|
@@ -49,27 +48,30 @@ module WorkerTools
|
|
49
48
|
end
|
50
49
|
|
51
50
|
def with_wrapper_basics(&block)
|
51
|
+
save_state_without_validate('running')
|
52
52
|
block.yield
|
53
53
|
finalize
|
54
54
|
# this time we do want to catch Exception to attempt to handle some of the
|
55
55
|
# critical errors.
|
56
56
|
# rubocop:disable Lint/RescueException
|
57
|
-
rescue Exception
|
57
|
+
rescue Exception => e
|
58
|
+
return finalize if non_failure_error?(e)
|
59
|
+
|
58
60
|
# rubocop:enable Lint/RescueException
|
59
|
-
|
60
|
-
model.save!(validate: false)
|
61
|
+
save_state_without_validate('failed')
|
61
62
|
raise
|
62
63
|
end
|
63
64
|
|
64
65
|
def finalize
|
65
|
-
model.
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
mark_with_warnings = model.notes.any? do |note|
|
67
|
+
complete_with_warnings_note_levels.include?(note.with_indifferent_access[:level].to_s)
|
68
|
+
end
|
69
|
+
|
70
|
+
model.update!(state: mark_with_warnings ? :complete_with_warnings : :complete)
|
69
71
|
end
|
70
72
|
|
71
|
-
def
|
72
|
-
|
73
|
+
def complete_with_warnings_note_levels
|
74
|
+
%w[error warning]
|
73
75
|
end
|
74
76
|
|
75
77
|
def model
|
@@ -83,13 +85,23 @@ module WorkerTools
|
|
83
85
|
send(current_wrapper_symbol) { with_wrappers(wrapper_symbols, &block) }
|
84
86
|
end
|
85
87
|
|
88
|
+
def non_failure_error?(error)
|
89
|
+
error.is_a?(WorkerTools::Errors::Invalid)
|
90
|
+
# or add your list
|
91
|
+
# [WorkerTools::Errors::Invalid, SomeOtherError].any? { |k| e.is_a?(k) }
|
92
|
+
end
|
93
|
+
|
86
94
|
private
|
87
95
|
|
96
|
+
def save_state_without_validate(state)
|
97
|
+
model.state = state
|
98
|
+
model.save!(validate: false)
|
99
|
+
end
|
100
|
+
|
88
101
|
def find_model
|
89
102
|
@model_id ||= nil
|
90
103
|
return @model_id if @model_id.is_a?(model_class)
|
91
104
|
return model_class.find(@model_id) if @model_id
|
92
|
-
raise 'Model not available' unless create_model_if_not_available
|
93
105
|
|
94
106
|
t = model_class.new
|
95
107
|
t.kind = model_kind if t.respond_to?(:kind=)
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module WorkerTools
|
2
|
-
module
|
2
|
+
module Benchmark
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
6
|
attr_accessor :benchmark
|
7
7
|
|
8
8
|
def with_wrapper_benchmark(&block)
|
9
|
-
@benchmark = Benchmark.measure(&block)
|
9
|
+
@benchmark = ::Benchmark.measure(&block)
|
10
10
|
|
11
11
|
model.meta['duration'] = @benchmark.real.round if model.respond_to?(:meta)
|
12
12
|
end
|
@@ -14,15 +14,14 @@ module WorkerTools
|
|
14
14
|
|
15
15
|
def self.add_counter_methods
|
16
16
|
@counters.each do |name|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
17
|
+
# ex `inserts`
|
18
|
+
define_method(name) { model.meta[name] }
|
19
|
+
|
20
|
+
# ex `inserts=`
|
21
|
+
define_method("#{name}=") { |value| model.meta[name] = value }
|
22
|
+
|
23
|
+
# ex `increment_inserts`
|
24
|
+
define_method("increment_#{name}") { model.meta[name] += 1 }
|
26
25
|
end
|
27
26
|
end
|
28
27
|
|
@@ -63,7 +63,8 @@ module WorkerTools
|
|
63
63
|
actual_columns_length = csv_rows_enum.first.length
|
64
64
|
return if expected_columns_length == actual_columns_length
|
65
65
|
|
66
|
-
|
66
|
+
msg = "The number of columns (#{actual_columns_length}) is not the expected (#{expected_columns_length})"
|
67
|
+
raise Errors::WrongNumberOfColumns, msg
|
67
68
|
end
|
68
69
|
|
69
70
|
def csv_input_columns_hash_check(csv_rows_enum)
|
@@ -75,7 +76,9 @@ module WorkerTools
|
|
75
76
|
|
76
77
|
def csv_input_columns_hash_check_duplicates(names)
|
77
78
|
dups = names.group_by(&:itself).select { |_, v| v.count > 1 }.keys
|
78
|
-
|
79
|
+
return unless dups.present?
|
80
|
+
|
81
|
+
raise Errors::DuplicatedColumns, "The file contains duplicated columns: #{dups}"
|
79
82
|
end
|
80
83
|
|
81
84
|
def csv_input_columns_hash_check_missing(actual_names, expected_names)
|
@@ -83,7 +86,7 @@ module WorkerTools
|
|
83
86
|
matchable = name.is_a?(String) ? csv_input_header_normalized(name) : name
|
84
87
|
actual_names.any? { |n| case n when matchable then true end } # rubocop does not like ===
|
85
88
|
end
|
86
|
-
raise "Some columns are missing: #{missing}" unless missing.empty?
|
89
|
+
raise Errors::MissingColumns, "Some columns are missing: #{missing}" unless missing.empty?
|
87
90
|
end
|
88
91
|
|
89
92
|
def csv_input_csv_options
|
@@ -2,13 +2,6 @@ require 'csv'
|
|
2
2
|
|
3
3
|
module WorkerTools
|
4
4
|
module CsvOutput
|
5
|
-
# if defined, this file will be written to this destination (regardless
|
6
|
-
# of whether the model saves the file as well)
|
7
|
-
def csv_output_target
|
8
|
-
# Ex: Rails.root.join('shared', 'foo', 'bar.csv')
|
9
|
-
false
|
10
|
-
end
|
11
|
-
|
12
5
|
def csv_output_entries
|
13
6
|
raise "csv_output_entries has to be defined in #{self}"
|
14
7
|
end
|
@@ -27,33 +20,18 @@ module WorkerTools
|
|
27
20
|
raise "csv_output_column_headers has to be defined in #{self}"
|
28
21
|
end
|
29
22
|
|
30
|
-
# rubocop:disable Lint/UnusedMethodArgument
|
31
23
|
def csv_output_row_values(entry)
|
32
|
-
|
33
|
-
# {
|
34
|
-
# foo: entry.foo,
|
35
|
-
# bar: entry.bar
|
36
|
-
# }.values_at(*csv_output_column_headers.keys)
|
37
|
-
raise "csv_output_row_values has to be defined in #{self}"
|
38
|
-
end
|
39
|
-
# rubocop:enable Lint/UnusedMethodArgument
|
40
|
-
|
41
|
-
def cvs_output_target_folder
|
42
|
-
File.dirname(csv_output_target)
|
43
|
-
end
|
44
|
-
|
45
|
-
def csv_output_target_file_name
|
46
|
-
File.basename(csv_output_target)
|
47
|
-
end
|
48
|
-
|
49
|
-
def csv_ouput_ensure_target_folder
|
50
|
-
FileUtils.mkdir_p(cvs_output_target_folder) unless File.directory?(cvs_output_target_folder)
|
24
|
+
entry.values_at(*csv_output_column_headers.keys)
|
51
25
|
end
|
52
26
|
|
53
27
|
def csv_output_tmp_file
|
54
28
|
@csv_output_tmp_file ||= Tempfile.new(['output', '.csv'])
|
55
29
|
end
|
56
30
|
|
31
|
+
def csv_output_file_name
|
32
|
+
"#{model_kind}.csv"
|
33
|
+
end
|
34
|
+
|
57
35
|
def csv_output_col_sep
|
58
36
|
';'
|
59
37
|
end
|
@@ -62,21 +40,29 @@ module WorkerTools
|
|
62
40
|
Encoding::UTF_8
|
63
41
|
end
|
64
42
|
|
43
|
+
def csv_output_write_mode
|
44
|
+
'wb'
|
45
|
+
end
|
46
|
+
|
47
|
+
def csv_output_csv_options
|
48
|
+
{ col_sep: csv_output_col_sep, encoding: csv_output_encoding }
|
49
|
+
end
|
50
|
+
|
65
51
|
def csv_output_insert_headers(csv)
|
66
52
|
csv << csv_output_column_headers.values if csv_output_column_headers
|
67
53
|
end
|
68
54
|
|
55
|
+
def csv_output_add_attachment
|
56
|
+
model.add_attachment(csv_output_tmp_file, file_name: csv_output_file_name, content_type: 'text/csv')
|
57
|
+
end
|
58
|
+
|
69
59
|
def csv_output_write_file
|
70
|
-
CSV.open(csv_output_tmp_file,
|
60
|
+
CSV.open(csv_output_tmp_file, csv_output_write_mode, **csv_output_csv_options) do |csv|
|
71
61
|
csv_output_insert_headers(csv)
|
72
62
|
csv_output_entries.each { |entry| csv << csv_output_row_values(entry) }
|
73
63
|
end
|
74
|
-
csv_output_write_target if csv_output_target
|
75
|
-
end
|
76
64
|
|
77
|
-
|
78
|
-
csv_ouput_ensure_target_folder
|
79
|
-
FileUtils.cp(csv_output_tmp_file.path, csv_output_target)
|
65
|
+
csv_output_add_attachment
|
80
66
|
end
|
81
67
|
end
|
82
68
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module WorkerTools
|
2
2
|
module Recorder
|
3
|
-
|
4
3
|
def with_wrapper_recorder(&block)
|
5
4
|
block.yield
|
6
5
|
# this time we do want to catch Exception to attempt to handle some of the
|
@@ -24,38 +23,44 @@ module WorkerTools
|
|
24
23
|
end
|
25
24
|
|
26
25
|
def record_fail(error)
|
27
|
-
record "ID #{model.id} - Error"
|
28
26
|
record(error, :error)
|
29
|
-
model.information = information
|
30
27
|
model.save!(validate: false)
|
31
28
|
end
|
32
29
|
|
33
|
-
def add_log(message, level =
|
34
|
-
|
30
|
+
def add_log(message, level = nil)
|
31
|
+
attrs = default_message_attrs(message, level)
|
32
|
+
logger.public_send(attrs[:level], format_message(attrs[:message]))
|
35
33
|
end
|
36
34
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
35
|
+
def add_note(message, level = nil)
|
36
|
+
attrs = default_message_attrs(message, level)
|
37
|
+
model.notes.push(level: attrs[:level], message: attrs[:message])
|
40
38
|
end
|
41
39
|
|
42
40
|
def record(message, level = :info)
|
43
41
|
add_log(message, level)
|
44
|
-
|
42
|
+
add_note(message, level)
|
45
43
|
end
|
46
44
|
|
47
|
-
def
|
48
|
-
return
|
45
|
+
def level_from_message_type(message)
|
46
|
+
return :error if message.is_a?(Exception)
|
49
47
|
|
50
|
-
|
48
|
+
:info
|
51
49
|
end
|
52
50
|
|
53
|
-
def
|
54
|
-
return error_to_text(message,
|
51
|
+
def format_message(message)
|
52
|
+
return error_to_text(message, log_error_trace_lines) if message.is_a?(Exception)
|
55
53
|
|
56
54
|
message
|
57
55
|
end
|
58
56
|
|
57
|
+
def default_message_attrs(message, level)
|
58
|
+
{
|
59
|
+
message: format_message(message),
|
60
|
+
level: level || level_from_message_type(message)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
59
64
|
def logger
|
60
65
|
@logger ||= Logger.new(File.join(log_directory, log_file_name))
|
61
66
|
end
|
@@ -5,7 +5,7 @@ module WorkerTools
|
|
5
5
|
def with_wrapper_slack_error_notifier(&block)
|
6
6
|
block.yield
|
7
7
|
rescue StandardError => e
|
8
|
-
slack_error_notify(e) if slack_error_notifier_enabled
|
8
|
+
slack_error_notify(e) if slack_error_notifier_enabled && slack_error_notifiable?(e)
|
9
9
|
raise
|
10
10
|
end
|
11
11
|
|
@@ -13,6 +13,10 @@ module WorkerTools
|
|
13
13
|
Rails.env.production?
|
14
14
|
end
|
15
15
|
|
16
|
+
def slack_error_notifiable?(error)
|
17
|
+
error.is_a?(StandardError)
|
18
|
+
end
|
19
|
+
|
16
20
|
def slack_error_notifier_emoji
|
17
21
|
':red_circle:'
|
18
22
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module WorkerTools
|
2
|
+
module Utils
|
3
|
+
class SerializedArrayType < ActiveRecord::Type::Json
|
4
|
+
def initialize(type: nil)
|
5
|
+
@type = type
|
6
|
+
end
|
7
|
+
|
8
|
+
def deserialize(value)
|
9
|
+
super(value)&.map { |d| @type.deserialize(d) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize(value)
|
13
|
+
raise 'not an array' unless value.is_a?(Array)
|
14
|
+
|
15
|
+
super value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/worker_tools/version.rb
CHANGED
@@ -47,7 +47,9 @@ module WorkerTools
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def xlsx_input_header_normalized(name)
|
50
|
-
|
50
|
+
# some elements return obj.to_s => nil
|
51
|
+
# for example [#<Roo::Excelx::Cell::Empty:0x0000000af8d4c8 ... @value=nil>]
|
52
|
+
name&.to_s&.strip&.downcase || ''
|
51
53
|
end
|
52
54
|
|
53
55
|
# Allows for some basic cleanup of the values, such as applying strip to
|
@@ -68,7 +70,8 @@ module WorkerTools
|
|
68
70
|
actual_columns_length = xlsx_rows_enum.first.length
|
69
71
|
return if expected_columns_length == actual_columns_length
|
70
72
|
|
71
|
-
|
73
|
+
msg = "The number of columns (#{actual_columns_length}) is not the expected (#{expected_columns_length})"
|
74
|
+
raise Errors::WrongNumberOfColumns, msg
|
72
75
|
end
|
73
76
|
|
74
77
|
def xlsx_input_columns_hash_check(xlsx_rows_enum)
|
@@ -80,7 +83,9 @@ module WorkerTools
|
|
80
83
|
|
81
84
|
def xlsx_input_columns_hash_check_duplicates(names)
|
82
85
|
dups = names.group_by(&:itself).select { |_, v| v.count > 1 }.keys
|
83
|
-
|
86
|
+
return unless dups.present?
|
87
|
+
|
88
|
+
raise Errors::DuplicatedColumns, "The file contains duplicated columns: #{dups}"
|
84
89
|
end
|
85
90
|
|
86
91
|
def xlsx_input_columns_hash_check_missing(actual_names, expected_names)
|
@@ -88,7 +93,7 @@ module WorkerTools
|
|
88
93
|
matchable = name.is_a?(String) ? xlsx_input_header_normalized(name) : name
|
89
94
|
actual_names.any? { |n| case n when matchable then true end } # rubocop does not like ===
|
90
95
|
end
|
91
|
-
raise "Some columns are missing: #{missing}" unless missing.empty?
|
96
|
+
raise Errors::MissingColumns, "Some columns are missing: #{missing}" unless missing.empty?
|
92
97
|
end
|
93
98
|
|
94
99
|
# Compares the first row (header names) with the xlsx_input_columns hash to find
|
@@ -1,26 +1,8 @@
|
|
1
1
|
require 'rubyXL'
|
2
2
|
module WorkerTools
|
3
3
|
module XlsxOutput
|
4
|
-
|
5
|
-
|
6
|
-
def xlsx_output_target
|
7
|
-
# Ex: Rails.root.join('shared', 'foo', 'bar.xlsx')
|
8
|
-
raise "xlsx_output_target has to be defined in #{self}"
|
9
|
-
end
|
10
|
-
|
11
|
-
def xlsx_output_content
|
12
|
-
{
|
13
|
-
sheet1: {
|
14
|
-
label: 'Sheet 1',
|
15
|
-
headers: xlsx_output_column_headers,
|
16
|
-
rows: xlsx_output_values,
|
17
|
-
column_style: xlsx_output_column_format
|
18
|
-
}
|
19
|
-
}
|
20
|
-
end
|
21
|
-
|
22
|
-
def xlsx_output_values
|
23
|
-
raise "xlsx_output_values has to be defined in #{self}"
|
4
|
+
def xlsx_output_entries
|
5
|
+
raise "xlsx_output_entries has to be defined in #{self}"
|
24
6
|
end
|
25
7
|
|
26
8
|
def xlsx_output_column_headers
|
@@ -37,6 +19,21 @@ module WorkerTools
|
|
37
19
|
raise "xlsx_output_column_headers has to be defined in #{self}"
|
38
20
|
end
|
39
21
|
|
22
|
+
def xlsx_output_content
|
23
|
+
{
|
24
|
+
sheet1: {
|
25
|
+
label: 'Sheet 1',
|
26
|
+
headers: xlsx_output_column_headers,
|
27
|
+
rows: xlsx_output_entries.lazy.map { |entry| xlsx_output_row_values(entry) },
|
28
|
+
column_style: xlsx_output_column_format
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def xlsx_output_row_values(entry)
|
34
|
+
entry.values_at(*xlsx_output_column_headers.keys)
|
35
|
+
end
|
36
|
+
|
40
37
|
def xlsx_output_column_format
|
41
38
|
# These columns are used to set the headers, also
|
42
39
|
# to set the row values depending on your implementation.
|
@@ -51,15 +48,7 @@ module WorkerTools
|
|
51
48
|
{}
|
52
49
|
end
|
53
50
|
|
54
|
-
def
|
55
|
-
@xlsx_output_target_folder ||= File.dirname(xlsx_output_target)
|
56
|
-
end
|
57
|
-
|
58
|
-
def xlsx_ensure_output_target_folder
|
59
|
-
FileUtils.mkdir_p(xlsx_output_target_folder) unless File.directory?(xlsx_output_target_folder)
|
60
|
-
end
|
61
|
-
|
62
|
-
def xlsx_insert_headers(spreadsheet, headers)
|
51
|
+
def xlsx_output_insert_headers(spreadsheet, headers)
|
63
52
|
return unless headers
|
64
53
|
|
65
54
|
iterator =
|
@@ -73,15 +62,15 @@ module WorkerTools
|
|
73
62
|
end
|
74
63
|
end
|
75
64
|
|
76
|
-
def
|
65
|
+
def xlsx_output_insert_rows(spreadsheet, rows, headers)
|
77
66
|
rows.each_with_index do |row, row_index|
|
78
|
-
|
67
|
+
xlsx_output_iterators(row, headers).each_with_index do |value, col_index|
|
79
68
|
spreadsheet.add_cell(row_index + 1, col_index, value.to_s)
|
80
69
|
end
|
81
70
|
end
|
82
71
|
end
|
83
72
|
|
84
|
-
def
|
73
|
+
def xlsx_output_iterators(iterable, compare_hash = nil)
|
85
74
|
if iterable.is_a? Hash
|
86
75
|
raise 'parameter compare_hash should be a hash, too.' if compare_hash.nil? || !compare_hash.is_a?(Hash)
|
87
76
|
|
@@ -91,10 +80,10 @@ module WorkerTools
|
|
91
80
|
end
|
92
81
|
end
|
93
82
|
|
94
|
-
def
|
83
|
+
def xlsx_output_style_columns(spreadsheet, styles, headers)
|
95
84
|
return false unless headers
|
96
85
|
|
97
|
-
|
86
|
+
xlsx_output_iterators(styles, headers).each_with_index do |format, index|
|
98
87
|
next unless format
|
99
88
|
|
100
89
|
spreadsheet.change_column_width(index, format[:width])
|
@@ -103,25 +92,41 @@ module WorkerTools
|
|
103
92
|
true
|
104
93
|
end
|
105
94
|
|
106
|
-
def
|
95
|
+
def xlsx_output_tmp_file
|
96
|
+
@xlsx_output_tmp_file ||= Tempfile.new(['output', '.xlsx'])
|
97
|
+
end
|
98
|
+
|
99
|
+
def xlsx_output_file_name
|
100
|
+
"#{model_kind}.xlsx"
|
101
|
+
end
|
102
|
+
|
103
|
+
def xlsx_output_add_attachment
|
104
|
+
model.add_attachment(
|
105
|
+
xlsx_output_tmp_file,
|
106
|
+
file_name: xlsx_output_file_name,
|
107
|
+
content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
def xlsx_output_write_sheet(workbook, sheet_content, index)
|
107
112
|
sheet = workbook.worksheets[index]
|
108
113
|
sheet = workbook.add_worksheet(sheet_content[:label]) if sheet.nil?
|
109
114
|
|
110
115
|
sheet.sheet_name = sheet_content[:label]
|
111
|
-
|
112
|
-
|
113
|
-
|
116
|
+
xlsx_output_style_columns(sheet, sheet_content[:column_style], sheet_content[:headers])
|
117
|
+
xlsx_output_insert_headers(sheet, sheet_content[:headers])
|
118
|
+
xlsx_output_insert_rows(sheet, sheet_content[:rows], sheet_content[:headers])
|
114
119
|
end
|
115
120
|
|
116
|
-
def
|
117
|
-
xlsx_ensure_output_target_folder
|
118
|
-
|
121
|
+
def xlsx_output_write_file
|
119
122
|
book = RubyXL::Workbook.new
|
120
123
|
xlsx_output_content.each_with_index do |(_, object), index|
|
121
|
-
|
124
|
+
xlsx_output_write_sheet(book, object, index)
|
122
125
|
end
|
123
126
|
|
124
|
-
book.write
|
127
|
+
book.write xlsx_output_tmp_file
|
128
|
+
|
129
|
+
xlsx_output_add_attachment
|
125
130
|
end
|
126
131
|
end
|
127
132
|
end
|
data/lib/worker_tools.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: worker_tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fsainz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -230,6 +230,7 @@ files:
|
|
230
230
|
- ".gitignore"
|
231
231
|
- ".rubocop.yml"
|
232
232
|
- ".travis.yml"
|
233
|
+
- CHANGELOG.md
|
233
234
|
- Gemfile
|
234
235
|
- LICENSE
|
235
236
|
- README.md
|
@@ -242,8 +243,11 @@ files:
|
|
242
243
|
- lib/worker_tools/counters.rb
|
243
244
|
- lib/worker_tools/csv_input.rb
|
244
245
|
- lib/worker_tools/csv_output.rb
|
246
|
+
- lib/worker_tools/errors.rb
|
245
247
|
- lib/worker_tools/recorder.rb
|
246
248
|
- lib/worker_tools/slack_error_notifier.rb
|
249
|
+
- lib/worker_tools/utils/hash_with_indifferent_access_type.rb
|
250
|
+
- lib/worker_tools/utils/serialized_array_type.rb
|
247
251
|
- lib/worker_tools/version.rb
|
248
252
|
- lib/worker_tools/xlsx_input.rb
|
249
253
|
- lib/worker_tools/xlsx_output.rb
|
@@ -268,7 +272,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
268
272
|
- !ruby/object:Gem::Version
|
269
273
|
version: '0'
|
270
274
|
requirements: []
|
271
|
-
rubygems_version: 3.1.
|
275
|
+
rubygems_version: 3.1.2
|
272
276
|
signing_key:
|
273
277
|
specification_version: 4
|
274
278
|
summary: A collection of modules to help writing common worker tasks)
|