maintenance_tasks 1.2.2 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -0
- data/app/jobs/concerns/maintenance_tasks/task_job_concern.rb +150 -0
- data/app/jobs/maintenance_tasks/task_job.rb +1 -134
- data/app/tasks/maintenance_tasks/task.rb +20 -1
- data/lib/generators/maintenance_tasks/templates/task_spec.rb.tt +7 -5
- data/lib/maintenance_tasks.rb +38 -20
- data/lib/maintenance_tasks/engine.rb +1 -0
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 649f66cc11a303666134c75aad8575b81eb376e358f69312180e21a14528ce50
|
4
|
+
data.tar.gz: b564b46e647c467c5d93a6198ab112729623059cfe194d1115d095c8ab8d9953
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f313350c5a80fa8840b23976eb7cde8776f825bc25a9b6a8fb7ce5d6f1abe8148d547ae7b2c1368a72caf980a2a12377e33b7c325e1447de3c0a0b0686d87a8d
|
7
|
+
data.tar.gz: '084950e7be06503e12d437294c784d02e2f0449c76db6297c5f95fcfd40845e4ce7e3354e16fbe12dd99b82554946f16fbc3415c047c17109aab1303c6b81b9b'
|
data/README.md
CHANGED
@@ -112,6 +112,46 @@ title,content
|
|
112
112
|
My Title,Hello World!
|
113
113
|
```
|
114
114
|
|
115
|
+
### Throttling
|
116
|
+
|
117
|
+
Maintenance Tasks often modify a lot of data and can be taxing on your database.
|
118
|
+
The gem provides a throttling mechanism that can be used to throttle a Task when
|
119
|
+
a given condition is met. If a Task is throttled, it will be interrupted and
|
120
|
+
retried after a backoff period has passed. The default backoff is 30 seconds.
|
121
|
+
Specify the throttle condition as a block:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
# app/tasks/maintenance/update_posts_throttled_task.rb
|
125
|
+
module Maintenance
|
126
|
+
class UpdatePostsThrottledTask < MaintenanceTasks::Task
|
127
|
+
throttle_on(backoff: 1.minute) do
|
128
|
+
DatabaseStatus.unhealthy?
|
129
|
+
end
|
130
|
+
|
131
|
+
def collection
|
132
|
+
Post.all
|
133
|
+
end
|
134
|
+
|
135
|
+
def count
|
136
|
+
collection.count
|
137
|
+
end
|
138
|
+
|
139
|
+
def process(post)
|
140
|
+
post.update!(content: "New content added on #{Time.now.utc}")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
Note that it's up to you to define a throttling condition that makes sense for
|
147
|
+
your app. Shopify implements `DatabaseStatus.healthy?` to check various MySQL
|
148
|
+
metrics such as replication lag, DB threads, whether DB writes are available,
|
149
|
+
etc.
|
150
|
+
|
151
|
+
Tasks can define multiple throttle conditions. Throttle conditions are inherited
|
152
|
+
by descendants, and new conditions will be appended without impacting existing
|
153
|
+
conditions.
|
154
|
+
|
115
155
|
### Considerations when writing Tasks
|
116
156
|
|
117
157
|
MaintenanceTasks relies on the queue adapter configured for your application to
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module MaintenanceTasks
|
3
|
+
# Concern that holds the behaviour of the job that runs the tasks. It is
|
4
|
+
# included in {TaskJob} and if MaintenanceTasks.job is overridden, it must be
|
5
|
+
# included in the job.
|
6
|
+
module TaskJobConcern
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include JobIteration::Iteration
|
9
|
+
|
10
|
+
included do
|
11
|
+
before_perform(:before_perform)
|
12
|
+
|
13
|
+
on_start(:on_start)
|
14
|
+
on_complete(:on_complete)
|
15
|
+
on_shutdown(:on_shutdown)
|
16
|
+
|
17
|
+
after_perform(:after_perform)
|
18
|
+
|
19
|
+
rescue_from StandardError, with: :on_error
|
20
|
+
end
|
21
|
+
|
22
|
+
class_methods do
|
23
|
+
# Overrides ActiveJob::Exceptions.retry_on to declare it unsupported.
|
24
|
+
# The use of rescue_from prevents retry_on from being usable.
|
25
|
+
def retry_on(*, **)
|
26
|
+
raise NotImplementedError, "retry_on is not supported"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_enumerator(_run, cursor:)
|
33
|
+
cursor ||= @run.cursor
|
34
|
+
collection = @task.collection
|
35
|
+
|
36
|
+
collection_enum = case collection
|
37
|
+
when ActiveRecord::Relation
|
38
|
+
enumerator_builder.active_record_on_records(collection, cursor: cursor)
|
39
|
+
when Array
|
40
|
+
enumerator_builder.build_array_enumerator(collection, cursor: cursor)
|
41
|
+
when CSV
|
42
|
+
JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor)
|
43
|
+
else
|
44
|
+
raise ArgumentError, "#{@task.class.name}#collection must be either "\
|
45
|
+
"an Active Record Relation, Array, or CSV."
|
46
|
+
end
|
47
|
+
|
48
|
+
@task.throttle_conditions.reduce(collection_enum) do |enum, condition|
|
49
|
+
enumerator_builder.build_throttle_enumerator(enum, **condition)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Performs task iteration logic for the current input returned by the
|
54
|
+
# enumerator.
|
55
|
+
#
|
56
|
+
# @param input [Object] the current element from the enumerator.
|
57
|
+
# @param _run [Run] the current Run, passed as an argument by Job Iteration.
|
58
|
+
def each_iteration(input, _run)
|
59
|
+
throw(:abort, :skip_complete_callbacks) if @run.stopping?
|
60
|
+
task_iteration(input)
|
61
|
+
@ticker.tick
|
62
|
+
@run.reload_status
|
63
|
+
end
|
64
|
+
|
65
|
+
def task_iteration(input)
|
66
|
+
@task.process(input)
|
67
|
+
rescue => error
|
68
|
+
@errored_element = input
|
69
|
+
raise error
|
70
|
+
end
|
71
|
+
|
72
|
+
def before_perform
|
73
|
+
@run = arguments.first
|
74
|
+
@task = Task.named(@run.task_name).new
|
75
|
+
if @task.respond_to?(:csv_content=)
|
76
|
+
@task.csv_content = @run.csv_file.download
|
77
|
+
end
|
78
|
+
@run.job_id = job_id
|
79
|
+
|
80
|
+
@run.running! unless @run.stopping?
|
81
|
+
|
82
|
+
@ticker = Ticker.new(MaintenanceTasks.ticker_delay) do |ticks, duration|
|
83
|
+
@run.persist_progress(ticks, duration)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def on_start
|
88
|
+
@run.update!(started_at: Time.now, tick_total: @task.count)
|
89
|
+
end
|
90
|
+
|
91
|
+
def on_complete
|
92
|
+
@run.status = :succeeded
|
93
|
+
@run.ended_at = Time.now
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_shutdown
|
97
|
+
if @run.cancelling?
|
98
|
+
@run.status = :cancelled
|
99
|
+
@run.ended_at = Time.now
|
100
|
+
else
|
101
|
+
@run.status = @run.pausing? ? :paused : :interrupted
|
102
|
+
@run.cursor = cursor_position
|
103
|
+
end
|
104
|
+
|
105
|
+
@ticker.persist
|
106
|
+
end
|
107
|
+
|
108
|
+
# We are reopening a private part of Job Iteration's API here, so we should
|
109
|
+
# ensure the method is still defined upstream. This way, in the case where
|
110
|
+
# the method changes upstream, we catch it at load time instead of at
|
111
|
+
# runtime while calling `super`.
|
112
|
+
unless JobIteration::Iteration
|
113
|
+
.private_method_defined?(:reenqueue_iteration_job)
|
114
|
+
error_message = <<~HEREDOC
|
115
|
+
JobIteration::Iteration#reenqueue_iteration_job is expected to be
|
116
|
+
defined. Upgrading the maintenance_tasks gem should solve this problem.
|
117
|
+
HEREDOC
|
118
|
+
raise error_message
|
119
|
+
end
|
120
|
+
def reenqueue_iteration_job(should_ignore: true)
|
121
|
+
super() unless should_ignore
|
122
|
+
@reenqueue_iteration_job = true
|
123
|
+
end
|
124
|
+
|
125
|
+
def after_perform
|
126
|
+
@run.save!
|
127
|
+
if defined?(@reenqueue_iteration_job) && @reenqueue_iteration_job
|
128
|
+
reenqueue_iteration_job(should_ignore: false)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def on_error(error)
|
133
|
+
@ticker.persist if defined?(@ticker)
|
134
|
+
|
135
|
+
if defined?(@run)
|
136
|
+
@run.persist_error(error)
|
137
|
+
|
138
|
+
task_context = {
|
139
|
+
task_name: @run.task_name,
|
140
|
+
started_at: @run.started_at,
|
141
|
+
ended_at: @run.ended_at,
|
142
|
+
}
|
143
|
+
else
|
144
|
+
task_context = {}
|
145
|
+
end
|
146
|
+
errored_element = @errored_element if defined?(@errored_element)
|
147
|
+
MaintenanceTasks.error_handler.call(error, task_context, errored_element)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -3,139 +3,6 @@
|
|
3
3
|
module MaintenanceTasks
|
4
4
|
# Base class that is inherited by the host application's task classes.
|
5
5
|
class TaskJob < ActiveJob::Base
|
6
|
-
include
|
7
|
-
|
8
|
-
before_perform(:before_perform)
|
9
|
-
|
10
|
-
on_start(:on_start)
|
11
|
-
on_complete(:on_complete)
|
12
|
-
on_shutdown(:on_shutdown)
|
13
|
-
|
14
|
-
after_perform(:after_perform)
|
15
|
-
|
16
|
-
rescue_from StandardError, with: :on_error
|
17
|
-
|
18
|
-
class << self
|
19
|
-
# Overrides ActiveJob::Exceptions.retry_on to declare it unsupported.
|
20
|
-
# The use of rescue_from prevents retry_on from being usable.
|
21
|
-
def retry_on(*, **)
|
22
|
-
raise NotImplementedError, "retry_on is not supported"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def build_enumerator(_run, cursor:)
|
29
|
-
cursor ||= @run.cursor
|
30
|
-
collection = @task.collection
|
31
|
-
|
32
|
-
case collection
|
33
|
-
when ActiveRecord::Relation
|
34
|
-
enumerator_builder.active_record_on_records(collection, cursor: cursor)
|
35
|
-
when Array
|
36
|
-
enumerator_builder.build_array_enumerator(collection, cursor: cursor)
|
37
|
-
when CSV
|
38
|
-
JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor)
|
39
|
-
else
|
40
|
-
raise ArgumentError, "#{@task.class.name}#collection must be either "\
|
41
|
-
"an Active Record Relation, Array, or CSV."
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Performs task iteration logic for the current input returned by the
|
46
|
-
# enumerator.
|
47
|
-
#
|
48
|
-
# @param input [Object] the current element from the enumerator.
|
49
|
-
# @param _run [Run] the current Run, passed as an argument by Job Iteration.
|
50
|
-
def each_iteration(input, _run)
|
51
|
-
throw(:abort, :skip_complete_callbacks) if @run.stopping?
|
52
|
-
task_iteration(input)
|
53
|
-
@ticker.tick
|
54
|
-
@run.reload_status
|
55
|
-
end
|
56
|
-
|
57
|
-
def task_iteration(input)
|
58
|
-
@task.process(input)
|
59
|
-
rescue => error
|
60
|
-
@errored_element = input
|
61
|
-
raise error
|
62
|
-
end
|
63
|
-
|
64
|
-
def before_perform
|
65
|
-
@run = arguments.first
|
66
|
-
@task = Task.named(@run.task_name).new
|
67
|
-
if @task.respond_to?(:csv_content=)
|
68
|
-
@task.csv_content = @run.csv_file.download
|
69
|
-
end
|
70
|
-
@run.job_id = job_id
|
71
|
-
|
72
|
-
@run.running! unless @run.stopping?
|
73
|
-
|
74
|
-
@ticker = Ticker.new(MaintenanceTasks.ticker_delay) do |ticks, duration|
|
75
|
-
@run.persist_progress(ticks, duration)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def on_start
|
80
|
-
@run.update!(started_at: Time.now, tick_total: @task.count)
|
81
|
-
end
|
82
|
-
|
83
|
-
def on_complete
|
84
|
-
@run.status = :succeeded
|
85
|
-
@run.ended_at = Time.now
|
86
|
-
end
|
87
|
-
|
88
|
-
def on_shutdown
|
89
|
-
if @run.cancelling?
|
90
|
-
@run.status = :cancelled
|
91
|
-
@run.ended_at = Time.now
|
92
|
-
else
|
93
|
-
@run.status = @run.pausing? ? :paused : :interrupted
|
94
|
-
@run.cursor = cursor_position
|
95
|
-
end
|
96
|
-
|
97
|
-
@ticker.persist
|
98
|
-
end
|
99
|
-
|
100
|
-
# We are reopening a private part of Job Iteration's API here, so we should
|
101
|
-
# ensure the method is still defined upstream. This way, in the case where
|
102
|
-
# the method changes upstream, we catch it at load time instead of at
|
103
|
-
# runtime while calling `super`.
|
104
|
-
unless private_method_defined?(:reenqueue_iteration_job)
|
105
|
-
error_message = <<~HEREDOC
|
106
|
-
JobIteration::Iteration#reenqueue_iteration_job is expected to be
|
107
|
-
defined. Upgrading the maintenance_tasks gem should solve this problem.
|
108
|
-
HEREDOC
|
109
|
-
raise error_message
|
110
|
-
end
|
111
|
-
def reenqueue_iteration_job(should_ignore: true)
|
112
|
-
super() unless should_ignore
|
113
|
-
@reenqueue_iteration_job = true
|
114
|
-
end
|
115
|
-
|
116
|
-
def after_perform
|
117
|
-
@run.save!
|
118
|
-
if defined?(@reenqueue_iteration_job) && @reenqueue_iteration_job
|
119
|
-
reenqueue_iteration_job(should_ignore: false)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def on_error(error)
|
124
|
-
@ticker.persist if defined?(@ticker)
|
125
|
-
|
126
|
-
if defined?(@run)
|
127
|
-
@run.persist_error(error)
|
128
|
-
|
129
|
-
task_context = {
|
130
|
-
task_name: @run.task_name,
|
131
|
-
started_at: @run.started_at,
|
132
|
-
ended_at: @run.ended_at,
|
133
|
-
}
|
134
|
-
else
|
135
|
-
task_context = {}
|
136
|
-
end
|
137
|
-
errored_element = @errored_element if defined?(@errored_element)
|
138
|
-
MaintenanceTasks.error_handler.call(error, task_context, errored_element)
|
139
|
-
end
|
6
|
+
include TaskJobConcern
|
140
7
|
end
|
141
8
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
2
|
module MaintenanceTasks
|
4
3
|
# Base class that is inherited by the host application's task classes.
|
5
4
|
class Task
|
@@ -7,6 +6,13 @@ module MaintenanceTasks
|
|
7
6
|
|
8
7
|
class NotFoundError < NameError; end
|
9
8
|
|
9
|
+
# The throttle conditions for a given Task. This is provided as an array of
|
10
|
+
# hashes, with each hash specifying two keys: throttle_condition and
|
11
|
+
# backoff. Note that Tasks inherit conditions from their superclasses.
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
class_attribute :throttle_conditions, default: []
|
15
|
+
|
10
16
|
class << self
|
11
17
|
# Finds a Task with the given name.
|
12
18
|
#
|
@@ -72,6 +78,19 @@ module MaintenanceTasks
|
|
72
78
|
new.count
|
73
79
|
end
|
74
80
|
|
81
|
+
# Add a condition under which this Task will be throttled.
|
82
|
+
#
|
83
|
+
# @param backoff [ActiveSupport::Duration] optionally, a custom backoff
|
84
|
+
# can be specified. This is the time to wait before retrying the Task.
|
85
|
+
# If no value is specified, it defaults to 30 seconds.
|
86
|
+
# @yieldreturn [Boolean] where the throttle condition is being met,
|
87
|
+
# indicating that the Task should throttle.
|
88
|
+
def throttle_on(backoff: 30.seconds, &condition)
|
89
|
+
self.throttle_conditions += [
|
90
|
+
{ throttle_on: condition, backoff: backoff },
|
91
|
+
]
|
92
|
+
end
|
93
|
+
|
75
94
|
private
|
76
95
|
|
77
96
|
def load_constants
|
@@ -4,11 +4,13 @@ require 'rails_helper'
|
|
4
4
|
module <%= tasks_module %>
|
5
5
|
<% module_namespacing do -%>
|
6
6
|
RSpec.describe <%= class_name %>Task do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
describe "#process" do
|
8
|
+
subject(:process) { described_class.process(element) }
|
9
|
+
let(:element) {
|
10
|
+
# Object to be processed in a single iteration of this task
|
11
|
+
}
|
12
|
+
pending "add some examples to (or delete) #{__FILE__}"
|
13
|
+
end
|
12
14
|
end
|
13
15
|
<% end -%>
|
14
16
|
end
|
data/lib/maintenance_tasks.rb
CHANGED
@@ -7,46 +7,56 @@ require "active_record"
|
|
7
7
|
require "job-iteration"
|
8
8
|
require "maintenance_tasks/engine"
|
9
9
|
|
10
|
-
# Force the TaskJob class to load so we can verify upstream compatibility with
|
11
|
-
# the JobIteration gem
|
12
|
-
require_relative "../app/jobs/maintenance_tasks/task_job"
|
13
|
-
|
14
10
|
# The engine's namespace module. It provides isolation between the host
|
15
11
|
# application's code and the engine-specific code. Top-level engine constants
|
16
12
|
# and variables are defined under this module.
|
17
13
|
module MaintenanceTasks
|
18
|
-
#
|
19
|
-
#
|
14
|
+
# @!attribute tasks_module
|
15
|
+
# @scope class
|
16
|
+
#
|
17
|
+
# The module to namespace Tasks in, as a String. Defaults to 'Maintenance'.
|
18
|
+
# @return [String] the name of the module.
|
20
19
|
mattr_accessor :tasks_module, default: "Maintenance"
|
21
20
|
|
22
|
-
#
|
23
|
-
#
|
21
|
+
# @!attribute job
|
22
|
+
# @scope class
|
24
23
|
#
|
25
|
-
#
|
24
|
+
# The name of the job to be used to perform Tasks. Defaults to
|
25
|
+
# `"MaintenanceTasks::TaskJob"`. This job must be either a class that
|
26
|
+
# inherits from {TaskJob} or a class that includes {TaskJobConcern}.
|
27
|
+
#
|
28
|
+
# @return [String] the name of the job class.
|
26
29
|
mattr_accessor :job, default: "MaintenanceTasks::TaskJob"
|
27
30
|
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
31
|
+
# @!attribute ticker_delay
|
32
|
+
# @scope class
|
33
|
+
#
|
34
|
+
# The delay between updates to the tick count. After each iteration, the
|
35
|
+
# progress of the Task may be updated. This duration in seconds limits
|
36
|
+
# these updates, skipping if the duration since the last update is lower
|
37
|
+
# than this value, except if the job is interrupted, in which case the
|
38
|
+
# progress will always be recorded.
|
32
39
|
#
|
33
|
-
#
|
34
|
-
#
|
40
|
+
# @return [ActiveSupport::Duration, Numeric] duration of the delay between
|
41
|
+
# updates to the tick count during Task iterations.
|
35
42
|
mattr_accessor :ticker_delay, default: 1.second
|
36
43
|
|
37
|
-
#
|
44
|
+
# @!attribute active_storage_service
|
45
|
+
# @scope class
|
38
46
|
#
|
39
|
-
#
|
40
|
-
#
|
47
|
+
# The Active Storage service to use for uploading CSV file blobs.
|
48
|
+
#
|
49
|
+
# @return [Symbol] the key for the storage service, as specified in the
|
50
|
+
# app's config/storage.yml.
|
41
51
|
mattr_accessor :active_storage_service
|
42
52
|
|
43
|
-
#
|
53
|
+
# @private
|
44
54
|
def self.error_handler
|
45
55
|
return @error_handler if defined?(@error_handler)
|
46
56
|
@error_handler = ->(_error, _task_context, _errored_element) {}
|
47
57
|
end
|
48
58
|
|
49
|
-
#
|
59
|
+
# @private
|
50
60
|
def self.error_handler=(error_handler)
|
51
61
|
unless error_handler.arity == 3
|
52
62
|
ActiveSupport::Deprecation.warn(
|
@@ -59,4 +69,12 @@ module MaintenanceTasks
|
|
59
69
|
end
|
60
70
|
@error_handler = error_handler
|
61
71
|
end
|
72
|
+
|
73
|
+
# @!attribute error_handler
|
74
|
+
# @scope class
|
75
|
+
#
|
76
|
+
# The callback to perform when an error occurs in the Task. See the
|
77
|
+
# {file:README#label-Customizing+the+error+handler} for details.
|
78
|
+
#
|
79
|
+
# @return [Proc] the callback to perform when an error occurs in the Task.
|
62
80
|
end
|
@@ -12,6 +12,7 @@ module MaintenanceTasks
|
|
12
12
|
end
|
13
13
|
|
14
14
|
config.to_prepare do
|
15
|
+
_ = TaskJobConcern # load this for JobIteration compatibility check
|
15
16
|
unless Rails.autoloaders.zeitwerk_enabled?
|
16
17
|
tasks_module = MaintenanceTasks.tasks_module.underscore
|
17
18
|
Dir["#{Rails.root}/app/tasks/#{tasks_module}/*.rb"].each do |file|
|
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.
|
4
|
+
version: 1.3.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: 2021-
|
11
|
+
date: 2021-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -94,6 +94,7 @@ files:
|
|
94
94
|
- app/controllers/maintenance_tasks/tasks_controller.rb
|
95
95
|
- app/helpers/maintenance_tasks/application_helper.rb
|
96
96
|
- app/helpers/maintenance_tasks/tasks_helper.rb
|
97
|
+
- app/jobs/concerns/maintenance_tasks/task_job_concern.rb
|
97
98
|
- app/jobs/maintenance_tasks/task_job.rb
|
98
99
|
- app/models/maintenance_tasks/application_record.rb
|
99
100
|
- app/models/maintenance_tasks/csv_collection.rb
|
@@ -139,10 +140,10 @@ homepage: https://github.com/Shopify/maintenance_tasks
|
|
139
140
|
licenses:
|
140
141
|
- MIT
|
141
142
|
metadata:
|
142
|
-
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v1.
|
143
|
+
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v1.3.0
|
143
144
|
allowed_push_host: https://rubygems.org
|
144
145
|
post_install_message: |-
|
145
|
-
Thank you for installing Maintenance Tasks 1.
|
146
|
+
Thank you for installing Maintenance Tasks 1.3.0. To complete, please run:
|
146
147
|
|
147
148
|
rails generate maintenance_tasks:install
|
148
149
|
rdoc_options: []
|
@@ -159,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
160
|
- !ruby/object:Gem::Version
|
160
161
|
version: '0'
|
161
162
|
requirements: []
|
162
|
-
rubygems_version: 3.
|
163
|
+
rubygems_version: 3.2.17
|
163
164
|
signing_key:
|
164
165
|
specification_version: 4
|
165
166
|
summary: A Rails engine for queuing and managing maintenance tasks
|