cloudtasker 0.2.0 → 0.7.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/.gitignore +4 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +10 -1
- data/Appraisals +25 -0
- data/CHANGELOG.md +29 -0
- data/Gemfile.lock +27 -4
- data/README.md +571 -6
- data/Rakefile +6 -0
- data/app/controllers/cloudtasker/application_controller.rb +2 -0
- data/app/controllers/cloudtasker/worker_controller.rb +24 -2
- data/cloudtasker.gemspec +5 -3
- data/docs/BATCH_JOBS.md +66 -0
- data/docs/CRON_JOBS.md +65 -0
- data/docs/UNIQUE_JOBS.md +127 -0
- data/exe/cloudtasker +15 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/google_cloud_tasks_1.0.gemfile +9 -0
- data/gemfiles/google_cloud_tasks_1.0.gemfile.lock +263 -0
- data/gemfiles/google_cloud_tasks_1.1.gemfile +9 -0
- data/gemfiles/google_cloud_tasks_1.1.gemfile.lock +263 -0
- data/gemfiles/google_cloud_tasks_1.2.gemfile +9 -0
- data/gemfiles/google_cloud_tasks_1.2.gemfile.lock +263 -0
- data/gemfiles/google_cloud_tasks_1.3.gemfile +9 -0
- data/gemfiles/google_cloud_tasks_1.3.gemfile.lock +264 -0
- data/gemfiles/rails_4.0.gemfile +10 -0
- data/gemfiles/rails_4.1.gemfile +9 -0
- data/gemfiles/rails_4.2.gemfile +9 -0
- data/gemfiles/rails_5.0.gemfile +9 -0
- data/gemfiles/rails_5.1.gemfile +9 -0
- data/gemfiles/rails_5.2.gemfile +9 -0
- data/gemfiles/rails_5.2.gemfile.lock +247 -0
- data/gemfiles/rails_6.0.gemfile +9 -0
- data/gemfiles/rails_6.0.gemfile.lock +263 -0
- data/lib/cloudtasker.rb +19 -1
- data/lib/cloudtasker/backend/google_cloud_task.rb +139 -0
- data/lib/cloudtasker/backend/memory_task.rb +190 -0
- data/lib/cloudtasker/backend/redis_task.rb +249 -0
- data/lib/cloudtasker/batch/batch_progress.rb +19 -1
- data/lib/cloudtasker/batch/job.rb +85 -23
- data/lib/cloudtasker/cli.rb +194 -0
- data/lib/cloudtasker/cloud_task.rb +91 -0
- data/lib/cloudtasker/config.rb +64 -2
- data/lib/cloudtasker/cron/job.rb +2 -2
- data/lib/cloudtasker/cron/schedule.rb +25 -11
- data/lib/cloudtasker/dead_worker_error.rb +6 -0
- data/lib/cloudtasker/local_server.rb +74 -0
- data/lib/cloudtasker/railtie.rb +10 -0
- data/lib/cloudtasker/redis_client.rb +2 -2
- data/lib/cloudtasker/testing.rb +133 -0
- data/lib/cloudtasker/unique_job/job.rb +1 -1
- data/lib/cloudtasker/unique_job/lock/base_lock.rb +1 -1
- data/lib/cloudtasker/unique_job/lock/until_executed.rb +3 -1
- data/lib/cloudtasker/unique_job/lock/while_executing.rb +3 -1
- data/lib/cloudtasker/version.rb +1 -1
- data/lib/cloudtasker/worker.rb +61 -17
- data/lib/cloudtasker/{task.rb → worker_handler.rb} +10 -77
- data/lib/cloudtasker/worker_logger.rb +155 -0
- data/lib/tasks/setup_queue.rake +10 -0
- metadata +70 -6
data/Rakefile
CHANGED
@@ -2,7 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
4
|
require 'rspec/core/rake_task'
|
5
|
+
require 'github_changelog_generator/task'
|
5
6
|
|
6
7
|
RSpec::Core::RakeTask.new(:spec)
|
7
8
|
|
8
9
|
task default: :spec
|
10
|
+
|
11
|
+
GitHubChangelogGenerator::RakeTask.new :changelog do |config|
|
12
|
+
config.user = 'keypup-io'
|
13
|
+
config.project = 'cloudtasker'
|
14
|
+
end
|
@@ -16,16 +16,38 @@ module Cloudtasker
|
|
16
16
|
# Run a worker from a Cloud Task payload
|
17
17
|
#
|
18
18
|
def run
|
19
|
-
|
19
|
+
# Build payload
|
20
|
+
payload = request.params
|
21
|
+
.slice(:worker, :job_id, :job_args, :job_meta)
|
22
|
+
.merge(job_retries: job_retries)
|
23
|
+
|
24
|
+
# Process payload
|
25
|
+
WorkerHandler.execute_from_payload!(payload)
|
20
26
|
head :no_content
|
27
|
+
rescue DeadWorkerError
|
28
|
+
# 205: job will NOT be retried
|
29
|
+
head :reset_content
|
21
30
|
rescue InvalidWorkerError
|
31
|
+
# 404: Job will be retried
|
22
32
|
head :not_found
|
23
|
-
rescue StandardError
|
33
|
+
rescue StandardError => e
|
34
|
+
# 404: Job will be retried
|
35
|
+
Cloudtasker.logger.error(e)
|
36
|
+
Cloudtasker.logger.error(e.backtrace.join("\n"))
|
24
37
|
head :unprocessable_entity
|
25
38
|
end
|
26
39
|
|
27
40
|
private
|
28
41
|
|
42
|
+
#
|
43
|
+
# Extract the number of times this task failed at runtime.
|
44
|
+
#
|
45
|
+
# @return [Integer] The number of failures
|
46
|
+
#
|
47
|
+
def job_retries
|
48
|
+
request.headers[Cloudtasker::Config::RETRY_HEADER].to_i
|
49
|
+
end
|
50
|
+
|
29
51
|
#
|
30
52
|
# Authenticate incoming requests using a bearer token
|
31
53
|
#
|
data/cloudtasker.gemspec
CHANGED
@@ -10,8 +10,8 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.authors = ['Arnaud Lachaume']
|
11
11
|
spec.email = ['arnaud.lachaume@keypup.io']
|
12
12
|
|
13
|
-
spec.summary = '
|
14
|
-
spec.description = '
|
13
|
+
spec.summary = 'Background jobs for Ruby using Google Cloud Tasks (alpha)'
|
14
|
+
spec.description = 'Background jobs for Ruby using Google Cloud Tasks (alpha)'
|
15
15
|
spec.homepage = 'https://github.com/keypup-io/cloudtasker'
|
16
16
|
spec.license = 'MIT'
|
17
17
|
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
# Specify which files should be added to the gem when it is released.
|
25
25
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
26
26
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
27
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(examples|test|spec|features)/}) }
|
28
28
|
end
|
29
29
|
spec.bindir = 'exe'
|
30
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
@@ -36,7 +36,9 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_dependency 'jwt'
|
37
37
|
spec.add_dependency 'redis'
|
38
38
|
|
39
|
+
spec.add_development_dependency 'appraisal'
|
39
40
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
41
|
+
spec.add_development_dependency 'github_changelog_generator'
|
40
42
|
spec.add_development_dependency 'rake', '~> 10.0'
|
41
43
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
42
44
|
spec.add_development_dependency 'rubocop', '0.76.0'
|
data/docs/BATCH_JOBS.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Cloudtasker Unique Jobs
|
2
|
+
|
3
|
+
**Note**: this extension requires redis
|
4
|
+
|
5
|
+
The Cloudtasker batch job extension allows to add sub-jobs to regular jobs. This adds the ability to enqueue a list of jobs and track their overall progression as a groupd of jobs (the batch). This extension allows jobs to define callbacks in their worker to track completion of the batch and take action based on that.
|
6
|
+
|
7
|
+
## Configuration
|
8
|
+
|
9
|
+
You can enable batch jobs by adding the following to your cloudtasker initializer:
|
10
|
+
```ruby
|
11
|
+
# The batch job extension is optional and must be explicitly required
|
12
|
+
require 'cloudtasker/batch'
|
13
|
+
|
14
|
+
Cloudtasker.configure do |config|
|
15
|
+
# Specify your redis url.
|
16
|
+
# Defaults to `redis://localhost:6379/0` if unspecified
|
17
|
+
config.redis = { url: 'redis://some-host:6379/0' }
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
## Example
|
22
|
+
|
23
|
+
The following example defines a worker that adds itself to the batch with different arguments then monitors the success of the batch.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class BatchWorker
|
27
|
+
include Cloudtasker::Worker
|
28
|
+
|
29
|
+
def perform(level, instance)
|
30
|
+
3.times { |n| batch.add(self.class, level + 1, n) } if level < 2
|
31
|
+
end
|
32
|
+
|
33
|
+
# Invoked when any descendant (e.g. sub-sub job) is complete
|
34
|
+
def on_batch_node_complete(child)
|
35
|
+
logger.info("Direct or Indirect child complete: #{child.job_id}")
|
36
|
+
end
|
37
|
+
|
38
|
+
# Invoked when a direct descendant is complete
|
39
|
+
def on_child_complete(child)
|
40
|
+
logger.info("Direct child complete: #{child.job_id}")
|
41
|
+
end
|
42
|
+
|
43
|
+
# Invoked when all chidren have finished
|
44
|
+
def on_batch_complete
|
45
|
+
Rails.logger.info("Batch complete")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
## Available callbacks
|
51
|
+
|
52
|
+
The following callbacks are available on your workers to track the progress of the batch:
|
53
|
+
|
54
|
+
| Callback | Argument | Description |
|
55
|
+
|------|-------------|-----------|
|
56
|
+
| `on_batch_node_complete` | `The child job` | Invoked when any descendant (e.g. sub-sub job) successfully completess |
|
57
|
+
| `on_child_complete` | `The child job` | Invoked when a direct descendant successfully completes |
|
58
|
+
| `on_child_error` | `The child job` | Invoked when a child fails |
|
59
|
+
| `on_child_dead` | `The child job` | Invoked when a child has exhausted all of its retries |s
|
60
|
+
| `on_batch_complete` | none | Invoked when all chidren have finished or died |
|
61
|
+
|
62
|
+
## Batch completion
|
63
|
+
|
64
|
+
Batches complete when all children have successfully completed or died (all retries exhausted).
|
65
|
+
|
66
|
+
Jobs that fail in a batch will be retried based on the `max_retries` setting configured globally or on the worker itself. The batch will be considered `pending` while workers retry. Therefore it may be a good idea to reduce the number of retries on your workers using `cloudtasker_options max_retries: 5` to ensure your batches don't hang for too long.
|
data/docs/CRON_JOBS.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Cloudtasker Cron Jobs
|
2
|
+
|
3
|
+
**Note**: this extension requires redis
|
4
|
+
|
5
|
+
The Cloudtasker cron job extension allows you to register workers to run at fixed intervals, using a cron expression. You can validate your cron expressions using [crontab.guru](https://crontab.guru).
|
6
|
+
|
7
|
+
## Configuration
|
8
|
+
|
9
|
+
You can schedule cron jobs by adding the following to your cloudtasker initializer:
|
10
|
+
```ruby
|
11
|
+
# The cron job extension is optional and must be explicitly required
|
12
|
+
require 'cloudtasker/cron'
|
13
|
+
|
14
|
+
Cloudtasker.configure do |config|
|
15
|
+
# Specify your redis url.
|
16
|
+
# Defaults to `redis://localhost:6379/0` if unspecified
|
17
|
+
config.redis = { url: 'redis://some-host:6379/0' }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Specify all your cron jobs below. This will synchronize your list of cron jobs (cron jobs previously created and not listed below will be removed).
|
21
|
+
unless Rails.env.test?
|
22
|
+
Cloudtasker::Cron::Schedule.load_from_hash!(
|
23
|
+
# Run job every minute
|
24
|
+
some_schedule_name: {
|
25
|
+
worker: 'SomeCronWorker',
|
26
|
+
cron: '* * * * *'
|
27
|
+
},
|
28
|
+
# Run job every hour on the fifteenth minute
|
29
|
+
other_cron_schedule: {
|
30
|
+
worker: 'OtherCronWorker',
|
31
|
+
cron: '15 * * * *'
|
32
|
+
}
|
33
|
+
)
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## Using a configuration file
|
38
|
+
|
39
|
+
You can maintain the list of cron jobs in a YAML file inside your config folder if you prefer:
|
40
|
+
```yml
|
41
|
+
# config/cloudtasker_cron.yml
|
42
|
+
|
43
|
+
# Run job every minute
|
44
|
+
some_schedule_name:
|
45
|
+
worker: 'SomeCronWorker'
|
46
|
+
cron: => '* * * * *'
|
47
|
+
|
48
|
+
# Run job every hour on the fifteenth minute
|
49
|
+
other_cron_schedule:
|
50
|
+
worker: 'OtherCronWorker'
|
51
|
+
cron: => '15 * * * *'
|
52
|
+
```
|
53
|
+
|
54
|
+
Then register the jobs inside your Cloudtasker initializer this way:
|
55
|
+
```ruby
|
56
|
+
# config/initializers/cloudtasker.rb
|
57
|
+
|
58
|
+
# ... Cloudtasker configuration ...
|
59
|
+
|
60
|
+
schedule_file = 'config/cloudtasker_cron.yml'
|
61
|
+
if File.exist?(schedule_file) && !Rails.env.test?
|
62
|
+
Cloudtasker::Cron::Schedule.load_from_hash!(YAML.load_file(schedule_file))
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
data/docs/UNIQUE_JOBS.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# Cloudtasker Unique Jobs
|
2
|
+
|
3
|
+
**Note**: this extension requires redis
|
4
|
+
|
5
|
+
The Cloudtasker unique job extension allows you to define uniqueness rules for jobs you schedule or process based on job arguments.
|
6
|
+
|
7
|
+
## Configuration
|
8
|
+
|
9
|
+
You can enable unique jobs by adding the following to your cloudtasker initializer:
|
10
|
+
```ruby
|
11
|
+
# The unique job extension is optional and must be explicitly required
|
12
|
+
require 'cloudtasker/unique_job'
|
13
|
+
|
14
|
+
Cloudtasker.configure do |config|
|
15
|
+
# Specify your redis url.
|
16
|
+
# Defaults to `redis://localhost:6379/0` if unspecified
|
17
|
+
config.redis = { url: 'redis://some-host:6379/0' }
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
## Example
|
22
|
+
|
23
|
+
The following example defines a worker that prevents more than one instance to run at the same time for the set of provided arguments. Any identical job scheduled after the first one will be re-enqueued until the first job has finished running.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class UniqAtRuntimeWorker
|
27
|
+
include Cloudtasker::Worker
|
28
|
+
|
29
|
+
#
|
30
|
+
# lock: specify the phase during which a worker must be unique based on class and arguments.
|
31
|
+
# In this case the worker will be unique while it is processed.
|
32
|
+
# Other types of locks are available - see below the rest of the documentation.
|
33
|
+
#
|
34
|
+
# on_conflict: specify what to do if another identical instance enter the lock phase.
|
35
|
+
# In this case the worker will be rescheduled until the lock becomes available.
|
36
|
+
#
|
37
|
+
cloudtasker_options lock: :while_executing, on_conflict: :reschedule
|
38
|
+
|
39
|
+
def perform(arg1, arg2)
|
40
|
+
sleep(10)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Considering the worker and the code below:
|
46
|
+
```ruby
|
47
|
+
# Enqueue two jobs successively
|
48
|
+
UniqAtRuntimeWorker.perform_async # Job 1
|
49
|
+
UniqAtRuntimeWorker.perform_async # Job 2
|
50
|
+
```
|
51
|
+
|
52
|
+
The following will happen
|
53
|
+
1) Cloud Tasks sends job 1 and job 2 for processing to Rails
|
54
|
+
2) Job 1 acquires a `while_executing` lock
|
55
|
+
3) Job 2 does not acquire the lock and moves to `on_conflict` which is `reschedule`
|
56
|
+
4) Job 2 gets rescheduled in 5 seconds
|
57
|
+
5) Job 1 keeps processing for 5 seconds
|
58
|
+
6) Job 2 is re-sent by Cloud Tasks and cannot acquire the lock, therefore is rescheduled.
|
59
|
+
7) Job 1 processes for another 5 seconds and finishes (total = 10 seconds of processing)
|
60
|
+
8) Job 2 is re-sent by Cloud Tasks, can acquire the lock this time and starts processing
|
61
|
+
|
62
|
+
## Available locks
|
63
|
+
|
64
|
+
Below is the list of available locks that can be specified through the `cloudtasker_options lock: ...` configuration option.
|
65
|
+
|
66
|
+
For each lock strategy the table specifies the lock period (start/end) and which `on_conflict` strategies are available.
|
67
|
+
|
68
|
+
| Lock | Starts when | Ends when | On Conflict strategies |
|
69
|
+
|------|-------------|-----------|------------------------|
|
70
|
+
| `until_executing` | The job is scheduled | The job starts processing | `reject` (default) or `raise` |
|
71
|
+
| `while_executing` | The job starts processing | The job ends processing | `reject` (default), `reschedule` or `raise` |
|
72
|
+
| `until_executed` | The job is scheduled | The job ends processing | `reject` (default) or `raise` |
|
73
|
+
|
74
|
+
## Available conflict strategies
|
75
|
+
|
76
|
+
Below is the list of available conflict strategies can be specified through the `cloudtasker_options on_conflict: ...` configuration option.
|
77
|
+
|
78
|
+
| Strategy | Available with | Description |
|
79
|
+
|----------|----------------|----------------|
|
80
|
+
| `reject` | All locks | This is the default strategy. The job will be discarded when a conflict occurs |
|
81
|
+
| `raise` | All locks | A `Cloudtasker::UniqueJob::LockError` will be raised when a conflict occurs |
|
82
|
+
| `reschedule` | `while_executing` | The job will be rescheduled 5 seconds later when a conflict occurs |
|
83
|
+
|
84
|
+
## Configuring unique arguments
|
85
|
+
|
86
|
+
By default Cloudtasker considers all job arguments to evaluate the uniqueness of a job. This behaviour is configurable per worker by defining a `unique_args` method on the worker itself returning the list of args defining uniqueness.
|
87
|
+
|
88
|
+
Example 1: Uniqueness based on a subset of arguments
|
89
|
+
```ruby
|
90
|
+
class UniqBasedOnTwoArgsWorker
|
91
|
+
include Cloudtasker::Worker
|
92
|
+
|
93
|
+
cloudtasker_options lock: :until_executed
|
94
|
+
|
95
|
+
# Only consider the first two args when evaluating uniqueness
|
96
|
+
def unique_args(args)
|
97
|
+
[arg[0], arg[1]]
|
98
|
+
end
|
99
|
+
|
100
|
+
def perform(arg1, arg2, arg3)
|
101
|
+
# ...
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
Example 2: Uniqueness based on modified arguments
|
107
|
+
```ruby
|
108
|
+
class ModuloArgsWorker
|
109
|
+
include Cloudtasker::Worker
|
110
|
+
|
111
|
+
cloudtasker_options lock: :until_executed
|
112
|
+
|
113
|
+
# The remainder of `some_int` modulo 5 will be considered for
|
114
|
+
# uniqueness instead of the full value of `some_int`
|
115
|
+
def unique_args(args)
|
116
|
+
[arg[0], arg[1], arg[2] % 5]
|
117
|
+
end
|
118
|
+
|
119
|
+
def perform(arg1, arg2, some_int)
|
120
|
+
# ...
|
121
|
+
end
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
## Beware of default method arguments
|
126
|
+
|
127
|
+
Default method arguments are ignored when evaluating worker uniqueness. See [this section](../../../#be-careful-with-default-arguments) for more details.
|
data/exe/cloudtasker
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'cloudtasker/cli'
|
6
|
+
|
7
|
+
begin
|
8
|
+
Cloudtasker::CLI.run
|
9
|
+
rescue StandardError => e
|
10
|
+
raise e if $DEBUG
|
11
|
+
|
12
|
+
warn e.message
|
13
|
+
warn e.backtrace.join("\n")
|
14
|
+
exit 1
|
15
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
cloudtasker (0.2.0)
|
5
|
+
activesupport
|
6
|
+
fugit
|
7
|
+
google-cloud-tasks
|
8
|
+
jwt
|
9
|
+
redis
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
actioncable (6.0.1)
|
15
|
+
actionpack (= 6.0.1)
|
16
|
+
nio4r (~> 2.0)
|
17
|
+
websocket-driver (>= 0.6.1)
|
18
|
+
actionmailbox (6.0.1)
|
19
|
+
actionpack (= 6.0.1)
|
20
|
+
activejob (= 6.0.1)
|
21
|
+
activerecord (= 6.0.1)
|
22
|
+
activestorage (= 6.0.1)
|
23
|
+
activesupport (= 6.0.1)
|
24
|
+
mail (>= 2.7.1)
|
25
|
+
actionmailer (6.0.1)
|
26
|
+
actionpack (= 6.0.1)
|
27
|
+
actionview (= 6.0.1)
|
28
|
+
activejob (= 6.0.1)
|
29
|
+
mail (~> 2.5, >= 2.5.4)
|
30
|
+
rails-dom-testing (~> 2.0)
|
31
|
+
actionpack (6.0.1)
|
32
|
+
actionview (= 6.0.1)
|
33
|
+
activesupport (= 6.0.1)
|
34
|
+
rack (~> 2.0)
|
35
|
+
rack-test (>= 0.6.3)
|
36
|
+
rails-dom-testing (~> 2.0)
|
37
|
+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
38
|
+
actiontext (6.0.1)
|
39
|
+
actionpack (= 6.0.1)
|
40
|
+
activerecord (= 6.0.1)
|
41
|
+
activestorage (= 6.0.1)
|
42
|
+
activesupport (= 6.0.1)
|
43
|
+
nokogiri (>= 1.8.5)
|
44
|
+
actionview (6.0.1)
|
45
|
+
activesupport (= 6.0.1)
|
46
|
+
builder (~> 3.1)
|
47
|
+
erubi (~> 1.4)
|
48
|
+
rails-dom-testing (~> 2.0)
|
49
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
50
|
+
activejob (6.0.1)
|
51
|
+
activesupport (= 6.0.1)
|
52
|
+
globalid (>= 0.3.6)
|
53
|
+
activemodel (6.0.1)
|
54
|
+
activesupport (= 6.0.1)
|
55
|
+
activerecord (6.0.1)
|
56
|
+
activemodel (= 6.0.1)
|
57
|
+
activesupport (= 6.0.1)
|
58
|
+
activestorage (6.0.1)
|
59
|
+
actionpack (= 6.0.1)
|
60
|
+
activejob (= 6.0.1)
|
61
|
+
activerecord (= 6.0.1)
|
62
|
+
marcel (~> 0.3.1)
|
63
|
+
activesupport (6.0.1)
|
64
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
65
|
+
i18n (>= 0.7, < 2)
|
66
|
+
minitest (~> 5.1)
|
67
|
+
tzinfo (~> 1.1)
|
68
|
+
zeitwerk (~> 2.2)
|
69
|
+
addressable (2.7.0)
|
70
|
+
public_suffix (>= 2.0.2, < 5.0)
|
71
|
+
appraisal (2.2.0)
|
72
|
+
bundler
|
73
|
+
rake
|
74
|
+
thor (>= 0.14.0)
|
75
|
+
ast (2.4.0)
|
76
|
+
builder (3.2.3)
|
77
|
+
concurrent-ruby (1.1.5)
|
78
|
+
crack (0.4.3)
|
79
|
+
safe_yaml (~> 1.0.0)
|
80
|
+
crass (1.0.5)
|
81
|
+
diff-lcs (1.3)
|
82
|
+
erubi (1.9.0)
|
83
|
+
et-orbi (1.2.2)
|
84
|
+
tzinfo
|
85
|
+
faraday (0.17.0)
|
86
|
+
multipart-post (>= 1.2, < 3)
|
87
|
+
fugit (1.3.3)
|
88
|
+
et-orbi (~> 1.1, >= 1.1.8)
|
89
|
+
raabro (~> 1.1)
|
90
|
+
globalid (0.4.2)
|
91
|
+
activesupport (>= 4.2.0)
|
92
|
+
google-cloud-tasks (1.0.0)
|
93
|
+
google-gax (~> 1.3)
|
94
|
+
googleapis-common-protos (>= 1.3.9, < 2.0)
|
95
|
+
grpc-google-iam-v1 (~> 0.6.9)
|
96
|
+
google-gax (1.8.1)
|
97
|
+
google-protobuf (~> 3.9)
|
98
|
+
googleapis-common-protos (>= 1.3.9, < 2.0)
|
99
|
+
googleauth (~> 0.9)
|
100
|
+
grpc (~> 1.24)
|
101
|
+
rly (~> 0.2.3)
|
102
|
+
google-protobuf (3.10.1)
|
103
|
+
googleapis-common-protos (1.3.9)
|
104
|
+
google-protobuf (~> 3.0)
|
105
|
+
googleapis-common-protos-types (~> 1.0)
|
106
|
+
grpc (~> 1.0)
|
107
|
+
googleapis-common-protos-types (1.0.4)
|
108
|
+
google-protobuf (~> 3.0)
|
109
|
+
googleauth (0.10.0)
|
110
|
+
faraday (~> 0.12)
|
111
|
+
jwt (>= 1.4, < 3.0)
|
112
|
+
memoist (~> 0.16)
|
113
|
+
multi_json (~> 1.11)
|
114
|
+
os (>= 0.9, < 2.0)
|
115
|
+
signet (~> 0.12)
|
116
|
+
grpc (1.25.0)
|
117
|
+
google-protobuf (~> 3.8)
|
118
|
+
googleapis-common-protos-types (~> 1.0)
|
119
|
+
grpc-google-iam-v1 (0.6.9)
|
120
|
+
googleapis-common-protos (>= 1.3.1, < 2.0)
|
121
|
+
grpc (~> 1.0)
|
122
|
+
hashdiff (1.0.0)
|
123
|
+
i18n (1.7.0)
|
124
|
+
concurrent-ruby (~> 1.0)
|
125
|
+
jaro_winkler (1.5.4)
|
126
|
+
jwt (2.2.1)
|
127
|
+
loofah (2.3.1)
|
128
|
+
crass (~> 1.0.2)
|
129
|
+
nokogiri (>= 1.5.9)
|
130
|
+
mail (2.7.1)
|
131
|
+
mini_mime (>= 0.1.1)
|
132
|
+
marcel (0.3.3)
|
133
|
+
mimemagic (~> 0.3.2)
|
134
|
+
memoist (0.16.1)
|
135
|
+
method_source (0.9.2)
|
136
|
+
mimemagic (0.3.3)
|
137
|
+
mini_mime (1.0.2)
|
138
|
+
mini_portile2 (2.4.0)
|
139
|
+
minitest (5.13.0)
|
140
|
+
multi_json (1.14.1)
|
141
|
+
multipart-post (2.1.1)
|
142
|
+
nio4r (2.5.2)
|
143
|
+
nokogiri (1.10.5)
|
144
|
+
mini_portile2 (~> 2.4.0)
|
145
|
+
os (1.0.1)
|
146
|
+
parallel (1.19.0)
|
147
|
+
parser (2.6.5.0)
|
148
|
+
ast (~> 2.4.0)
|
149
|
+
public_suffix (4.0.1)
|
150
|
+
raabro (1.1.6)
|
151
|
+
rack (2.0.7)
|
152
|
+
rack-test (1.1.0)
|
153
|
+
rack (>= 1.0, < 3)
|
154
|
+
rails (6.0.1)
|
155
|
+
actioncable (= 6.0.1)
|
156
|
+
actionmailbox (= 6.0.1)
|
157
|
+
actionmailer (= 6.0.1)
|
158
|
+
actionpack (= 6.0.1)
|
159
|
+
actiontext (= 6.0.1)
|
160
|
+
actionview (= 6.0.1)
|
161
|
+
activejob (= 6.0.1)
|
162
|
+
activemodel (= 6.0.1)
|
163
|
+
activerecord (= 6.0.1)
|
164
|
+
activestorage (= 6.0.1)
|
165
|
+
activesupport (= 6.0.1)
|
166
|
+
bundler (>= 1.3.0)
|
167
|
+
railties (= 6.0.1)
|
168
|
+
sprockets-rails (>= 2.0.0)
|
169
|
+
rails-dom-testing (2.0.3)
|
170
|
+
activesupport (>= 4.2.0)
|
171
|
+
nokogiri (>= 1.6)
|
172
|
+
rails-html-sanitizer (1.3.0)
|
173
|
+
loofah (~> 2.3)
|
174
|
+
railties (6.0.1)
|
175
|
+
actionpack (= 6.0.1)
|
176
|
+
activesupport (= 6.0.1)
|
177
|
+
method_source
|
178
|
+
rake (>= 0.8.7)
|
179
|
+
thor (>= 0.20.3, < 2.0)
|
180
|
+
rainbow (3.0.0)
|
181
|
+
rake (10.5.0)
|
182
|
+
redis (4.1.3)
|
183
|
+
rly (0.2.3)
|
184
|
+
rspec (3.9.0)
|
185
|
+
rspec-core (~> 3.9.0)
|
186
|
+
rspec-expectations (~> 3.9.0)
|
187
|
+
rspec-mocks (~> 3.9.0)
|
188
|
+
rspec-core (3.9.0)
|
189
|
+
rspec-support (~> 3.9.0)
|
190
|
+
rspec-expectations (3.9.0)
|
191
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
192
|
+
rspec-support (~> 3.9.0)
|
193
|
+
rspec-mocks (3.9.0)
|
194
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
195
|
+
rspec-support (~> 3.9.0)
|
196
|
+
rspec-rails (3.9.0)
|
197
|
+
actionpack (>= 3.0)
|
198
|
+
activesupport (>= 3.0)
|
199
|
+
railties (>= 3.0)
|
200
|
+
rspec-core (~> 3.9.0)
|
201
|
+
rspec-expectations (~> 3.9.0)
|
202
|
+
rspec-mocks (~> 3.9.0)
|
203
|
+
rspec-support (~> 3.9.0)
|
204
|
+
rspec-support (3.9.0)
|
205
|
+
rubocop (0.76.0)
|
206
|
+
jaro_winkler (~> 1.5.1)
|
207
|
+
parallel (~> 1.10)
|
208
|
+
parser (>= 2.6)
|
209
|
+
rainbow (>= 2.2.2, < 4.0)
|
210
|
+
ruby-progressbar (~> 1.7)
|
211
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
212
|
+
rubocop-rspec (1.36.0)
|
213
|
+
rubocop (>= 0.68.1)
|
214
|
+
ruby-progressbar (1.10.1)
|
215
|
+
safe_yaml (1.0.5)
|
216
|
+
signet (0.12.0)
|
217
|
+
addressable (~> 2.3)
|
218
|
+
faraday (~> 0.9)
|
219
|
+
jwt (>= 1.5, < 3.0)
|
220
|
+
multi_json (~> 1.10)
|
221
|
+
sprockets (4.0.0)
|
222
|
+
concurrent-ruby (~> 1.0)
|
223
|
+
rack (> 1, < 3)
|
224
|
+
sprockets-rails (3.2.1)
|
225
|
+
actionpack (>= 4.0)
|
226
|
+
activesupport (>= 4.0)
|
227
|
+
sprockets (>= 3.0.0)
|
228
|
+
sqlite3 (1.4.1)
|
229
|
+
thor (0.20.3)
|
230
|
+
thread_safe (0.3.6)
|
231
|
+
timecop (0.9.1)
|
232
|
+
tzinfo (1.2.5)
|
233
|
+
thread_safe (~> 0.1)
|
234
|
+
unicode-display_width (1.6.0)
|
235
|
+
webmock (3.7.6)
|
236
|
+
addressable (>= 2.3.6)
|
237
|
+
crack (>= 0.3.2)
|
238
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
239
|
+
websocket-driver (0.7.1)
|
240
|
+
websocket-extensions (>= 0.1.0)
|
241
|
+
websocket-extensions (0.1.4)
|
242
|
+
zeitwerk (2.2.1)
|
243
|
+
|
244
|
+
PLATFORMS
|
245
|
+
ruby
|
246
|
+
|
247
|
+
DEPENDENCIES
|
248
|
+
appraisal
|
249
|
+
bundler (~> 2.0)
|
250
|
+
cloudtasker!
|
251
|
+
google-cloud-tasks (= 1.0)
|
252
|
+
rails
|
253
|
+
rake (~> 10.0)
|
254
|
+
rspec (~> 3.0)
|
255
|
+
rspec-rails
|
256
|
+
rubocop (= 0.76.0)
|
257
|
+
rubocop-rspec
|
258
|
+
sqlite3
|
259
|
+
timecop
|
260
|
+
webmock
|
261
|
+
|
262
|
+
BUNDLED WITH
|
263
|
+
2.0.2
|