maintenance_tasks 1.7.0 → 1.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6019b1623a8cc72e877154f52e8437339598f0cab121189d78692e0989e86e61
4
- data.tar.gz: 21563dc6dffadd2e58dd551fe2b7d71297b2da36500722f5d9fed91892b1fd6e
3
+ metadata.gz: e66a4f3f2755a31f9dac0125d41d63d5b1b8ca28a4f11642c8648d94dbc2a044
4
+ data.tar.gz: 1cf5d80050866d6e78d4cf392c9ff888953a3d15da8c19f83f511aaaf94640d2
5
5
  SHA512:
6
- metadata.gz: 93228d08b49fab144297cb44a0aa4b9be53144ff13b88c2eba384ef29fdb47b9a8caefaf94aaf1fbfbf4584f4d544396720c17d86f3bad31508fd66dfd7507b8
7
- data.tar.gz: cab4cebb685fddf978eeebd62a5c16867215f849d424b9ba948beba0e93f42a5fe11ee1eccc15174c1494fa16a2fa100d3a7745c5eacef2527d425895805cf29
6
+ metadata.gz: ffc019d73a707936a483349862481e5a227e270be3578f859b7d385fa7d997d327082a6f090881dcb83306feaf496d36f03d75421533ac507d7472adc5cad991
7
+ data.tar.gz: ae2ca92ea5b989e11d6ffaa50bd6ce1e5fcd41f735f81e6261ae496b81de2b90e7da2f58fdc04803dc77d44f6c0a8814a86794413f2e7ce374aa557c7e0073fb
data/README.md CHANGED
@@ -160,6 +160,36 @@ primary keys of the records of the batch first, and then perform an additional
160
160
  query to load the records when calling `each` (or any `Enumerable` method)
161
161
  inside `#process`.
162
162
 
163
+ ### Tasks that don't need a Collection
164
+
165
+ Sometimes, you might want to run a Task that performs a single operation, such
166
+ as enqueuing another background job or hitting an external API. The gem supports
167
+ collection-less tasks.
168
+
169
+ Generate a collection-less Task by running:
170
+
171
+ ```bash
172
+ $ bin/rails generate maintenance_tasks:task no_collection_task --no-collection
173
+ ```
174
+
175
+ The generated task is a subclass of `MaintenanceTasks::Task` that implements:
176
+
177
+ * `process`: do the work of your maintenance task
178
+
179
+ ```ruby
180
+ # app/tasks/maintenance/no_collection_task.rb
181
+
182
+ module Maintenance
183
+ class NoCollectionTask < MaintenanceTasks::Task
184
+ no_collection
185
+
186
+ def process
187
+ SomeAsyncJob.perform_later
188
+ end
189
+ end
190
+ end
191
+ ```
192
+
163
193
  ### Throttling
164
194
 
165
195
  Maintenance Tasks often modify a lot of data and can be taxing on your database.
@@ -36,6 +36,8 @@ module MaintenanceTasks
36
36
  @enumerator = nil
37
37
 
38
38
  collection_enum = case collection
39
+ when :no_collection
40
+ enumerator_builder.build_once_enumerator(cursor: nil)
39
41
  when ActiveRecord::Relation
40
42
  enumerator_builder.active_record_on_records(collection, cursor: cursor)
41
43
  when ActiveRecord::Batches::BatchEnumerator
@@ -89,7 +91,11 @@ module MaintenanceTasks
89
91
  end
90
92
 
91
93
  def task_iteration(input)
92
- @task.process(input)
94
+ if @task.no_collection?
95
+ @task.process
96
+ else
97
+ @task.process(input)
98
+ end
93
99
  rescue => error
94
100
  @errored_element = input
95
101
  raise error
@@ -29,5 +29,10 @@ module MaintenanceTasks
29
29
  def has_csv_content?
30
30
  true
31
31
  end
32
+
33
+ # Returns that the Task processes a collection.
34
+ def no_collection?
35
+ false
36
+ end
32
37
  end
33
38
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaintenanceTasks
4
+ # Strategy for building a Task that has no collection. These Tasks
5
+ # consist of a single iteration.
6
+ #
7
+ # @api private
8
+ class NoCollectionBuilder
9
+ # Specifies that this task does not process a collection.
10
+ def collection(_task)
11
+ :no_collection
12
+ end
13
+
14
+ # The number of rows to be processed. Always returns 1.
15
+ def count(_task)
16
+ 1
17
+ end
18
+
19
+ # Return that the Task does not process CSV content.
20
+ def has_csv_content?
21
+ false
22
+ end
23
+
24
+ # Returns that the Task is collection-less.
25
+ def no_collection?
26
+ true
27
+ end
28
+ end
29
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module MaintenanceTasks
4
4
  # Base strategy for building a collection-based Task to be performed.
5
+ #
6
+ # @api private
5
7
  class NullCollectionBuilder
6
8
  # Placeholder method to raise in case a subclass fails to implement the
7
9
  # expected instance method.
@@ -27,5 +29,10 @@ module MaintenanceTasks
27
29
  def has_csv_content?
28
30
  false
29
31
  end
32
+
33
+ # Returns that the Task processes a collection.
34
+ def no_collection?
35
+ false
36
+ end
30
37
  end
31
38
  end
@@ -131,8 +131,8 @@ module MaintenanceTasks
131
131
  self.started_at ||= Time.now
132
132
  update!(
133
133
  status: :errored,
134
- error_class: error.class.to_s,
135
- error_message: error.message,
134
+ error_class: truncate(:error_class, error.class.name),
135
+ error_message: truncate(:error_message, error.message),
136
136
  backtrace: MaintenanceTasks.backtrace_cleaner.clean(error.backtrace),
137
137
  ended_at: Time.now,
138
138
  )
@@ -422,5 +422,11 @@ module MaintenanceTasks
422
422
  errors.add(:base, error_message)
423
423
  end
424
424
  end
425
+
426
+ def truncate(attribute_name, value)
427
+ limit = MaintenanceTasks::Run.column_for_attribute(attribute_name).limit
428
+ return value unless limit
429
+ value&.first(limit)
430
+ end
425
431
  end
426
432
  end
@@ -18,6 +18,7 @@ module MaintenanceTasks
18
18
  # @api private
19
19
  class_attribute :throttle_conditions, default: []
20
20
 
21
+ # @api private
21
22
  class_attribute :collection_builder_strategy,
22
23
  default: NullCollectionBuilder.new
23
24
 
@@ -63,20 +64,23 @@ module MaintenanceTasks
63
64
  MaintenanceTasks::CsvCollectionBuilder.new
64
65
  end
65
66
 
66
- # Returns whether the Task handles CSV.
67
- #
68
- # @return [Boolean] whether the Task handles CSV.
69
- def has_csv_content?
70
- collection_builder_strategy.has_csv_content?
67
+ # Make this a Task that calls #process once, instead of iterating over
68
+ # a collection.
69
+ def no_collection
70
+ self.collection_builder_strategy =
71
+ MaintenanceTasks::NoCollectionBuilder.new
71
72
  end
72
73
 
74
+ delegate :has_csv_content?, :no_collection?,
75
+ to: :collection_builder_strategy
76
+
73
77
  # Processes one item.
74
78
  #
75
79
  # Especially useful for tests.
76
80
  #
77
- # @param item the item to process.
78
- def process(item)
79
- new.process(item)
81
+ # @param args [Object, nil] the item to process
82
+ def process(*args)
83
+ new.process(*args)
80
84
  end
81
85
 
82
86
  # Returns the collection for this Task.
@@ -197,6 +201,13 @@ module MaintenanceTasks
197
201
  self.class.has_csv_content?
198
202
  end
199
203
 
204
+ # Returns whether the Task is collection-less.
205
+ #
206
+ # @return [Boolean] whether the Task is collection-less.
207
+ def no_collection?
208
+ self.class.no_collection?
209
+ end
210
+
200
211
  # The collection to be processed, delegated to the strategy.
201
212
  #
202
213
  # @return the collection.
@@ -12,10 +12,17 @@ module MaintenanceTasks
12
12
  class_option :csv, type: :boolean, default: false,
13
13
  desc: "Generate a CSV Task."
14
14
 
15
+ class_option :no_collection, type: :boolean, default: false,
16
+ desc: "Generate a collection-less Task."
17
+
15
18
  check_class_collision suffix: "Task"
16
19
 
17
20
  # Creates the Task file.
18
21
  def create_task_file
22
+ if options[:csv] && options[:no_collection]
23
+ raise "Multiple Task type options provided. Please use either "\
24
+ "--csv or --no-collection."
25
+ end
19
26
  template_file = File.join(
20
27
  "app/tasks/#{tasks_module_file_path}",
21
28
  class_path,
@@ -23,6 +30,8 @@ module MaintenanceTasks
23
30
  )
24
31
  if options[:csv]
25
32
  template("csv_task.rb", template_file)
33
+ elsif no_collection?
34
+ template("no_collection_task.rb", template_file)
26
35
  else
27
36
  template("task.rb", template_file)
28
37
  end
@@ -76,5 +85,9 @@ module MaintenanceTasks
76
85
  def test_framework
77
86
  Rails.application.config.generators.options[:rails][:test_framework]
78
87
  end
88
+
89
+ def no_collection?
90
+ options[:no_collection]
91
+ end
79
92
  end
80
93
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= tasks_module %>
4
+ <% module_namespacing do -%>
5
+ class <%= class_name %>Task < MaintenanceTasks::Task
6
+ no_collection
7
+
8
+ def process
9
+ # The work to be done
10
+ end
11
+ end
12
+ <% end -%>
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ require 'test_helper'
3
+
4
+ module <%= tasks_module %>
5
+ <% module_namespacing do -%>
6
+ class <%= class_name %>TaskTest < ActiveSupport::TestCase
7
+ # test "#process performs a task iteration" do
8
+ # <%= tasks_module %>::<%= class_name %>Task.process
9
+ # end
10
+ end
11
+ <% end -%>
12
+ end
@@ -5,7 +5,11 @@ module <%= tasks_module %>
5
5
  <% module_namespacing do -%>
6
6
  class <%= class_name %>TaskTest < ActiveSupport::TestCase
7
7
  # test "#process performs a task iteration" do
8
+ <%- if no_collection? -%>
9
+ # <%= tasks_module %>::<%= class_name %>Task.process
10
+ <%- else -%>
8
11
  # <%= tasks_module %>::<%= class_name %>Task.process(element)
12
+ <%- end -%>
9
13
  # end
10
14
  end
11
15
  <% end -%>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maintenance_tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-28 00:00:00.000000000 Z
11
+ date: 2022-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -98,6 +98,7 @@ files:
98
98
  - app/jobs/maintenance_tasks/task_job.rb
99
99
  - app/models/maintenance_tasks/application_record.rb
100
100
  - app/models/maintenance_tasks/csv_collection_builder.rb
101
+ - app/models/maintenance_tasks/no_collection_builder.rb
101
102
  - app/models/maintenance_tasks/null_collection_builder.rb
102
103
  - app/models/maintenance_tasks/progress.rb
103
104
  - app/models/maintenance_tasks/run.rb
@@ -135,6 +136,8 @@ files:
135
136
  - lib/generators/maintenance_tasks/install_generator.rb
136
137
  - lib/generators/maintenance_tasks/task_generator.rb
137
138
  - lib/generators/maintenance_tasks/templates/csv_task.rb.tt
139
+ - lib/generators/maintenance_tasks/templates/no_collection_task.rb.tt
140
+ - lib/generators/maintenance_tasks/templates/no_collection_task_test.rb.tt
138
141
  - lib/generators/maintenance_tasks/templates/task.rb.tt
139
142
  - lib/generators/maintenance_tasks/templates/task_spec.rb.tt
140
143
  - lib/generators/maintenance_tasks/templates/task_test.rb.tt
@@ -147,7 +150,7 @@ homepage: https://github.com/Shopify/maintenance_tasks
147
150
  licenses:
148
151
  - MIT
149
152
  metadata:
150
- source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v1.7.0
153
+ source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v1.8.0
151
154
  allowed_push_host: https://rubygems.org
152
155
  post_install_message:
153
156
  rdoc_options: []