task_rb 0.4.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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +7 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +154 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/task.rb +7 -0
- data/lib/task/data_interface.rb +3 -0
- data/lib/task/data_interface/cassandra_adapter.rb +68 -0
- data/lib/task/data_interface/interface.rb +53 -0
- data/lib/task/generator.rb +23 -0
- data/lib/task/generators.rb +2 -0
- data/lib/task/generators/existing_tasks.rb +12 -0
- data/lib/task/task.rb +162 -0
- data/lib/task/tasks.rb +1 -0
- data/lib/task/tasks/composite_task.rb +19 -0
- data/lib/task/version.rb +3 -0
- data/task_rb.gemspec +31 -0
- metadata +191 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 194d17f1d017541392f992a1c347ec22c04e76f1
|
4
|
+
data.tar.gz: 713adbbc091faad2991b1a18f0571968890e110a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2f4ba9823762f74c376c8b78883f627dab721f67c18edeb1c56f7c89793553f651c364659a12650d5fbf23b886b1ae16659c7de0201dbe148e10a2832ee5e7dd
|
7
|
+
data.tar.gz: b94c11cca50d493cb14fae48802c98467c673da301ca52d510ed2b291d26507a5e83e713bc8f4c083189ed74a0cc4719336cd1b0aad55b1d6e1f2099d1555573
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in task.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'cassava_rb', :git => 'git@github.com:backupify/cassava.git'
|
7
|
+
gem 'pyper_rb', :git => 'git@github.com:backupify/pyper.git'
|
8
|
+
gem 'values', :git => 'git@github.com:backupify/values.git'
|
9
|
+
|
10
|
+
group :development, :test do
|
11
|
+
gem "pry"
|
12
|
+
gem "awesome_print"
|
13
|
+
gem 'm', :git => 'git@github.com:ANorwell/m.git', :branch => 'minitest_5'
|
14
|
+
end
|
15
|
+
|
16
|
+
group :test do
|
17
|
+
gem 'minitest_should', :git => 'git@github.com:citrus/minitest_should.git'
|
18
|
+
gem "mocha"
|
19
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Datto
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# Task
|
2
|
+
|
3
|
+
[](https://travis-ci.org/backupify/task)
|
4
|
+
|
5
|
+
Task provides a toolbox for generating, tracking and serializing tasks to be performed.
|
6
|
+
It is NOT a job queuing framework, a la resque or qless, and does not provide ways to execute tasks
|
7
|
+
in a distributed fashion. Rather, Task could be used with such a framework to provide "at-least-once"
|
8
|
+
task execution guarantees, or to perform multiple, lightweight tasks within a single job.
|
9
|
+
|
10
|
+
An example task lifecycle could be follows:
|
11
|
+
|
12
|
+
1. A task is generated by a task generator process
|
13
|
+
2. The task is saved to a backing data store, recording that it has been generated and should be completed.
|
14
|
+
3. The task is passed, in serialized format, directly from the generating process to another worker process
|
15
|
+
or host to complete.
|
16
|
+
4. The worker process begins working on the task, and:
|
17
|
+
* The worker process completes the task, marking it as completed in the backing data store.
|
18
|
+
|
19
|
+
OR
|
20
|
+
|
21
|
+
* The worker process fails to complete the task.
|
22
|
+
* A subsequent worker can fetch the task from the backing data store and attempts to complete it.
|
23
|
+
|
24
|
+
Task provides mechanisms creating tasks, saving and loading tasks from the backend datastore, and serializing tasks
|
25
|
+
for transfer between processes or hosts. However, Task is not a framework, and each step in the lifecycle
|
26
|
+
above would be implemented in application code.
|
27
|
+
|
28
|
+
Out of the box, Task uses Cassandra as the backing data store, but other backends could be implemented.
|
29
|
+
Cassandra provides stronger durability guarantees than some other data stores (for example, Redis). Additionally,
|
30
|
+
Cassandra prefers write-heavy workloads. In the workload described above, A task need only be read from the
|
31
|
+
datastore if it fails to complete initially. In situations where most tasks complete on the first attempt,
|
32
|
+
the majority of datastore operations will be writes. However, because Task does not enforce a usage pattern,
|
33
|
+
this could be usage-dependent.
|
34
|
+
|
35
|
+
## Installation
|
36
|
+
|
37
|
+
Add this line to your application's Gemfile:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
gem 'task_rb', github: 'backupify/task'
|
41
|
+
```
|
42
|
+
|
43
|
+
And then execute:
|
44
|
+
|
45
|
+
$ bundle
|
46
|
+
|
47
|
+
## Usage
|
48
|
+
|
49
|
+
### Defining and Creating a Task
|
50
|
+
|
51
|
+
To define a task, mix in the Task module:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class FetchFile
|
55
|
+
include Task::Task
|
56
|
+
|
57
|
+
data_attr_reader :external_host
|
58
|
+
data_attr_reader :filename
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
A task contains a set of data key-value pairs; The `data_attr_reader` helper provides accessors for various expected
|
63
|
+
data fields, but is not required.
|
64
|
+
|
65
|
+
To build a task, we invoke the build method:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
file_task = FetchFile.build(
|
69
|
+
id: 'file1',
|
70
|
+
task_list: 'datto.com',
|
71
|
+
external_host: 'datto.com',
|
72
|
+
filename: 'file1.txt')
|
73
|
+
```
|
74
|
+
|
75
|
+
Here, the task belongs to the 'datto.com' task list. The ID of the task should be unique across the task list (if one
|
76
|
+
is not specified, a UUID will be used). Other provided fields just become part of the data of task; this is just syntactic
|
77
|
+
sugar for:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
file_task = FetchFile.new(
|
81
|
+
id: 'file1',
|
82
|
+
task_list: 'datto.com',
|
83
|
+
data: { external_host: 'datto.com', filename: 'file1.txt' })
|
84
|
+
```
|
85
|
+
|
86
|
+
On the task object, the `data_attr_readers` allow access to data fields, so `file_task.data[:external_host]` and
|
87
|
+
`file_task.external_host` are equivalent. This is just a convenience, and does not enforce any restrictions on which
|
88
|
+
fields can and cannot be placed in the task data.
|
89
|
+
|
90
|
+
### Saving and Loading Tasks from the Datastore
|
91
|
+
|
92
|
+
To save to the datastore:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
file_task.save
|
96
|
+
```
|
97
|
+
|
98
|
+
If an existing task with the same task list and ID already exists within the backend store, it will be overwritten.
|
99
|
+
|
100
|
+
To fetch a particular task (by id) from the datastore:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
file_task = Task::Task.find(task_list, id)
|
104
|
+
```
|
105
|
+
|
106
|
+
To fetch all tasks for a task list from the datastore:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
tasks_enumerator = Task::Task.all(task_list)
|
110
|
+
```
|
111
|
+
|
112
|
+
### Serializing and Deserializing Tasks
|
113
|
+
|
114
|
+
To serialize as a hash:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
task_hash = my_task.as_hash
|
118
|
+
```
|
119
|
+
|
120
|
+
To deserialize a task hash to the task that was originally serialized:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
my_task = Task::Task.from_hash(task_hash)
|
124
|
+
```
|
125
|
+
|
126
|
+
Task does not enforce a particular over the wire serialization format.
|
127
|
+
|
128
|
+
### Configuring the Datastore
|
129
|
+
|
130
|
+
The datastore interface is backed by an adapter. Which adapter is used, and how it is
|
131
|
+
constructed, is configurable, by specifying the adapter builder. The specified lambda function
|
132
|
+
should take the options passed to the `Interface#new` method, and return the adapter instance:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
Task::DataInterface::Interface.adapter_builder = ->(_options) do
|
136
|
+
session = Cassandra.cluster(port: 1234, hosts: ['my_host']).connect('my_tasks')
|
137
|
+
CassandraAdapter.new(client: Cassava::Client.new(session))
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
This example configures the CassandraAdapter to connect to Cassandra on port 1234 and host 'my_host', and to
|
142
|
+
use the keyspace 'my_tasks'. Similarly, a completely separate adapter could be used.
|
143
|
+
|
144
|
+
Additionally, data interface instances can be constructed by passing in an adapter, allowing different
|
145
|
+
adapters (or adapter configurations) to be used at once.
|
146
|
+
|
147
|
+
## Contributing
|
148
|
+
|
149
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/backupify/task.
|
150
|
+
|
151
|
+
|
152
|
+
## License
|
153
|
+
|
154
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "task"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/task.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'pyper/all'
|
2
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
|
5
|
+
module Task::DataInterface
|
6
|
+
class CassandraAdapter
|
7
|
+
attr_reader :client, :tasks_table_name
|
8
|
+
|
9
|
+
# @option opts [Cassava::Client] :client The Cassandra Client to use
|
10
|
+
# @option opts [Symbol] :tasks_table_name The table name of the cassandra table containing the tasks
|
11
|
+
# Defaults to :tasks
|
12
|
+
def initialize(opts = {})
|
13
|
+
@client = opts[:client]
|
14
|
+
@tasks_table_name = opts[:tasks_table_name] || :tasks
|
15
|
+
end
|
16
|
+
|
17
|
+
# (see Interface)
|
18
|
+
def store(task)
|
19
|
+
pipeline = Pyper::Pipeline.new
|
20
|
+
|
21
|
+
# Serialize the attributes to be stored
|
22
|
+
pipeline << Pyper::Pipes::Model::AttributeSerializer.new
|
23
|
+
|
24
|
+
# Store the serialized attributes in the tasks table
|
25
|
+
pipeline << Pyper::Pipes::Cassandra::Writer.new(tasks_table_name, client)
|
26
|
+
pipeline.push(task.as_hash)
|
27
|
+
end
|
28
|
+
|
29
|
+
# (see Interface)
|
30
|
+
def delete(task_list, task_id)
|
31
|
+
client.delete(:tasks).where(:task_list => task_list, :id => task_id).execute
|
32
|
+
end
|
33
|
+
|
34
|
+
# (see Interface)
|
35
|
+
def all(task_list)
|
36
|
+
read_pipe.push(:task_list => task_list).value
|
37
|
+
end
|
38
|
+
|
39
|
+
def find(task_list, task_id)
|
40
|
+
read_pipe.push(:task_list => task_list, :id => task_id).value.first
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def read_pipe
|
46
|
+
pipeline = Pyper::Pipeline.new
|
47
|
+
|
48
|
+
# Read items from cassandra, as determined by the args pushed into the pipeline
|
49
|
+
pipeline << Pyper::Pipes::Cassandra::Reader.new(tasks_table_name, client)
|
50
|
+
|
51
|
+
# Deserialize the data field into a hash
|
52
|
+
pipeline << Pyper::Pipes::Model::AttributeDeserializer.new('data' => Hash)
|
53
|
+
|
54
|
+
# Deserialize items into Task objects
|
55
|
+
pipeline << TaskDeserializer
|
56
|
+
|
57
|
+
pipeline
|
58
|
+
end
|
59
|
+
|
60
|
+
class TaskDeserializer
|
61
|
+
def self.pipe(items, status)
|
62
|
+
items.map do |item|
|
63
|
+
Task::Task.from_hash(item.with_indifferent_access)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'cassava'
|
2
|
+
require 'active_support/core_ext/class/attribute'
|
3
|
+
require_relative 'cassandra_adapter'
|
4
|
+
|
5
|
+
module Task::DataInterface
|
6
|
+
class Interface
|
7
|
+
attr_reader :adapter
|
8
|
+
|
9
|
+
# The adapter builder function used to initialize the adapter based on the options provided to new.
|
10
|
+
# By default, a CassandraAdapter will be used. For this default adapter builder, the following
|
11
|
+
# arguments are supported:
|
12
|
+
# * :keyspace - the keyspace to use; default is 'tasks'
|
13
|
+
# * any arguments accepted by the #Cassandra.cluster method, including :port and :hosts
|
14
|
+
class_attribute :adapter_builder
|
15
|
+
self.adapter_builder = ->(options) do
|
16
|
+
session = Cassandra.cluster(options).connect(options[:keyspace] || 'tasks')
|
17
|
+
CassandraAdapter.new(client: Cassava::Client.new(session))
|
18
|
+
end
|
19
|
+
|
20
|
+
# @option options [#store,#all,#find,#delete] :adapter adapter to use.
|
21
|
+
# Otherwise, the configured adapter builder will be used
|
22
|
+
def initialize(options = {})
|
23
|
+
@adapter = options[:adapter] || self.class.adapter_builder.call(options)
|
24
|
+
end
|
25
|
+
# Stores a task in the data store
|
26
|
+
# @param [Task::Task] task
|
27
|
+
def store(task)
|
28
|
+
adapter.store(task)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns all tasks for the provided task list
|
32
|
+
# @param [String] task_list
|
33
|
+
# @return [Enumerator::Lazy<Task::Task>]
|
34
|
+
def all(task_list)
|
35
|
+
adapter.all(task_list)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the task with the given id
|
39
|
+
# @param [String] task_list
|
40
|
+
# @param [String] task_id
|
41
|
+
# @return [Task::Task|NilClass]
|
42
|
+
def find(task_list, task_id)
|
43
|
+
adapter.find(task_list, task_id)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Deletes the task with the given id.
|
47
|
+
# @param [String] task_list
|
48
|
+
# @param [String] task_id
|
49
|
+
def delete(task_list, task_id)
|
50
|
+
adapter.delete(task_list, task_id)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'values'
|
2
|
+
|
3
|
+
module Task
|
4
|
+
module Generator
|
5
|
+
include ::Enumerable
|
6
|
+
|
7
|
+
def append(*generators)
|
8
|
+
Generators::CombinedGenerator.new([self] + generators)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Generators
|
13
|
+
class CombinedGenerator < Value.new(:generators)
|
14
|
+
include Generator
|
15
|
+
|
16
|
+
def each
|
17
|
+
generators.each do |generator|
|
18
|
+
generator.each { |t| yield t }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/task/task.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
require 'active_support/concern'
|
3
|
+
require_relative 'data_interface/interface'
|
4
|
+
|
5
|
+
module Task
|
6
|
+
# A Task represents a task to be performed. The task can be serialized as JSON to be passed to
|
7
|
+
# other processes to complete, and can be stored in Cassandra as a means of ensuring "at-least-once"
|
8
|
+
# task completion for this task. So, an example lifecycle of a task would be:
|
9
|
+
# 1) A task is generated in a "master" job
|
10
|
+
# 2) The master job saves the task, providing a record that the task was generated.
|
11
|
+
# 3) The master job passes the task to another job to complete the task.
|
12
|
+
# 4) The worker job completes task, removing the record for that task.
|
13
|
+
# 5) If the worker job fails, the task is not completed and so the record for it persists.
|
14
|
+
# 6) A subsequent job can fetch the list of tasks, returning the tasks that failed to complete.
|
15
|
+
# This job could either serialize them to be completed by other jobs, or complete them directly.
|
16
|
+
#
|
17
|
+
# A task has a unique id, belongs to a task list that group similar tasks together, and has data
|
18
|
+
# associated with it.
|
19
|
+
#
|
20
|
+
# @example Defining a type of Task
|
21
|
+
# class MyDeleteTask
|
22
|
+
# include Task::Task
|
23
|
+
# # This task has the item_id data field, representing the item to delete
|
24
|
+
# data_attr_reader :item_id
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @example Creating a task
|
28
|
+
# my_task = MyDeleteTask.build(:id => 'my_id', :task_list => "#{service.id}-delete", :item_id => '123')
|
29
|
+
#
|
30
|
+
# @example Serializing and deserializing a task
|
31
|
+
# # In the process that generated the task
|
32
|
+
# serialized = my_task.as_hash
|
33
|
+
#
|
34
|
+
# # In the process that acts on the hash
|
35
|
+
# my_task_copy = Task::Task.from_hash(serialized)
|
36
|
+
#
|
37
|
+
# @example Saving a task
|
38
|
+
# my_task.save
|
39
|
+
#
|
40
|
+
# @example Fetching a single task that has been saved
|
41
|
+
# Task::DataInterface::Interface.new.find(task_list, task_id)
|
42
|
+
#
|
43
|
+
# @example Fetching all tasks for a task list
|
44
|
+
# Task::DataInterface::Interface.new.all(task_list)
|
45
|
+
#
|
46
|
+
# @example Completing a task, so that it is not longer fetchable
|
47
|
+
# my_task.complete
|
48
|
+
#
|
49
|
+
module Task
|
50
|
+
extend ActiveSupport::Concern
|
51
|
+
include Virtus.module
|
52
|
+
|
53
|
+
attribute :task_list, String
|
54
|
+
attribute :id, String
|
55
|
+
attribute :data, Hash[Symbol => Object]
|
56
|
+
|
57
|
+
module ClassMethods
|
58
|
+
# Instantiate an instance of this Task subclass.
|
59
|
+
# @param options [Hash] Options to instantiate this Task. :task_list and :id are required;
|
60
|
+
# other arguments will be passed as data to the task.
|
61
|
+
# @option options [String] :task_list
|
62
|
+
# @option options [String] :id
|
63
|
+
def build(options)
|
64
|
+
task_list = options.delete(:task_list)
|
65
|
+
id = options.delete(:id) || SecureRandom.hex
|
66
|
+
new(task_list: task_list, id: id, data: options)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Instantiate an instance of this Task subclass and save it to the datastore.
|
70
|
+
# @param options [Hash] Options to instantiate this Task. :task_list and :id are required;
|
71
|
+
# other arguments will be passed as data to the task.
|
72
|
+
# @option options [String] :task_list
|
73
|
+
# @option options [String] :id
|
74
|
+
def create(options)
|
75
|
+
task = build(options)
|
76
|
+
task.save
|
77
|
+
task
|
78
|
+
end
|
79
|
+
|
80
|
+
# Defines an attr reader instance method for a field in the data hash.
|
81
|
+
#
|
82
|
+
# @example
|
83
|
+
# class MyTask
|
84
|
+
# include Task::Task
|
85
|
+
# data_attr_reader :my_data_field
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# @param attr_name [Symbol] The attr name of the data field which will be used.
|
89
|
+
def data_attr_reader(attr_name)
|
90
|
+
define_method(attr_name) { data[attr_name] }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Creates a Task object from the provided hash. Generally, this task object should NOT be
|
95
|
+
# constructed manually using this method. Rather, this provides a way to reconstitute a task
|
96
|
+
# that was previously serialized using the #as_hash method.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# class MyTask
|
100
|
+
# include Task::Task
|
101
|
+
# data_attr_reader :my_data_field
|
102
|
+
# end
|
103
|
+
# my_task = MyTask.new(:task_list => 'my_task_list', )
|
104
|
+
|
105
|
+
#
|
106
|
+
# @param task_hash [Hash] Should contain the :task_list, :id, :type, and :data fields
|
107
|
+
# @return [Task::Task] The task object.
|
108
|
+
def self.from_hash(task_hash)
|
109
|
+
task_hash = task_hash.dup
|
110
|
+
type = task_hash.delete(:type)
|
111
|
+
type.constantize.new(task_hash)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns the task with the given id
|
115
|
+
# @param [String] task_list
|
116
|
+
# @param [String] task_id
|
117
|
+
# @return [Task::Task|NilClass]
|
118
|
+
def self.find(task_list, id)
|
119
|
+
interface.find(task_list, id)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns all tasks for the provided task list
|
123
|
+
# @param [String] task_list
|
124
|
+
# @return [Enumerator::Lazy<Task::Task>]
|
125
|
+
def self.all(task_list)
|
126
|
+
interface.all(task_list)
|
127
|
+
end
|
128
|
+
|
129
|
+
# The Data Interface used
|
130
|
+
# @return [Task::DataInterface::Interface]
|
131
|
+
def self.interface
|
132
|
+
DataInterface::Interface.new
|
133
|
+
end
|
134
|
+
|
135
|
+
# Executes this task
|
136
|
+
# @param options [Hash] Options specific to the execution of this task
|
137
|
+
def execute(options = {})
|
138
|
+
raise NotImplementedError.new('execute method not implemented')
|
139
|
+
end
|
140
|
+
|
141
|
+
# Serialized this Task as a hash
|
142
|
+
# @return [Hash]
|
143
|
+
def as_hash
|
144
|
+
attributes.merge(type: self.class.to_s)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Saves this task to the data store.
|
148
|
+
# @return [NilClass]
|
149
|
+
def save
|
150
|
+
Task.interface.store(self)
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
# Marks this task as complete, removing it from the datastore
|
156
|
+
# @return [NilClass]
|
157
|
+
def complete
|
158
|
+
Task.interface.delete(task_list, id)
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
data/lib/task/tasks.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'tasks/composite_task.rb'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative '../task'
|
2
|
+
|
3
|
+
module Task::Tasks
|
4
|
+
class CompositeTask
|
5
|
+
include Task::Task
|
6
|
+
|
7
|
+
data_attr_reader :child_task_list
|
8
|
+
|
9
|
+
# @param opts [Hash] Options to pass to the execute method of each child task
|
10
|
+
# @return [Array] The sequence of return values from each task execution
|
11
|
+
def execute(opts = {})
|
12
|
+
(Task::Task.all(child_task_list).map do |task|
|
13
|
+
task_result = task.execute(opts)
|
14
|
+
task.complete
|
15
|
+
task_result
|
16
|
+
end).force
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/task/version.rb
ADDED
data/task_rb.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'task/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "task_rb"
|
8
|
+
spec.version = Task::VERSION
|
9
|
+
spec.authors = ["Arron Norwell"]
|
10
|
+
spec.email = ["anorwell@datto.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Task provides a toolbox for generating, tracking and serializing tasks to be performed.}
|
13
|
+
spec.homepage = "http://datto.com"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest"
|
24
|
+
spec.add_development_dependency "minitest-reporters"
|
25
|
+
|
26
|
+
spec.add_dependency 'pyper_rb', '~> 1.0'
|
27
|
+
spec.add_dependency 'cassava_rb'
|
28
|
+
spec.add_dependency 'virtus'
|
29
|
+
spec.add_dependency 'values'
|
30
|
+
spec.add_dependency "activesupport"
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: task_rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Arron Norwell
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-reporters
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pyper_rb
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: cassava_rb
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: virtus
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: values
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: activesupport
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description:
|
140
|
+
email:
|
141
|
+
- anorwell@datto.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- ".gitignore"
|
147
|
+
- ".travis.yml"
|
148
|
+
- Gemfile
|
149
|
+
- LICENSE.txt
|
150
|
+
- README.md
|
151
|
+
- Rakefile
|
152
|
+
- bin/console
|
153
|
+
- bin/setup
|
154
|
+
- lib/task.rb
|
155
|
+
- lib/task/data_interface.rb
|
156
|
+
- lib/task/data_interface/cassandra_adapter.rb
|
157
|
+
- lib/task/data_interface/interface.rb
|
158
|
+
- lib/task/generator.rb
|
159
|
+
- lib/task/generators.rb
|
160
|
+
- lib/task/generators/existing_tasks.rb
|
161
|
+
- lib/task/task.rb
|
162
|
+
- lib/task/tasks.rb
|
163
|
+
- lib/task/tasks/composite_task.rb
|
164
|
+
- lib/task/version.rb
|
165
|
+
- task_rb.gemspec
|
166
|
+
homepage: http://datto.com
|
167
|
+
licenses:
|
168
|
+
- MIT
|
169
|
+
metadata: {}
|
170
|
+
post_install_message:
|
171
|
+
rdoc_options: []
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
requirements: []
|
185
|
+
rubyforge_project:
|
186
|
+
rubygems_version: 2.4.8
|
187
|
+
signing_key:
|
188
|
+
specification_version: 4
|
189
|
+
summary: Task provides a toolbox for generating, tracking and serializing tasks to
|
190
|
+
be performed.
|
191
|
+
test_files: []
|