orchestrated 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +41 -25
- data/lib/orchestrated/base.rb +1 -5
- data/lib/orchestrated/completion.rb +6 -6
- data/lib/orchestrated/dependency.rb +47 -4
- data/lib/orchestrated/orchestration.rb +16 -19
- data/lib/orchestrated/version.rb +1 -1
- data/orchestrated.gemspec +8 -8
- metadata +26 -26
data/README.markdown
CHANGED
@@ -1,17 +1,32 @@
|
|
1
1
|
Orchestrated
|
2
2
|
============
|
3
3
|
|
4
|
-
The [delayed_job](https://github.com/collectiveidea/delayed_job) Ruby Gem provides a
|
4
|
+
The [delayed_job](https://github.com/collectiveidea/delayed_job) Ruby Gem provides a restartable queuing system for Ruby. It implements an elegant API for delaying execution of any Ruby object method invocation. Not only is the message delivery delayed in time, it is potentially shifted in space too. By shifting in space, i.e. running in a different virtual machine, possibly on a separate computer, multiple CPUs can be brought to bear on a computing problem.
|
5
5
|
|
6
6
|
By breaking up otherwise serial execution into multiple queued jobs, a program can be made more scalable. This sort of distributed queue-processing architecture has a long and successful history in data processing.
|
7
7
|
|
8
|
-
Queuing works well for simple tasks. By simple we mean the task can be done all at once, in one piece
|
8
|
+
Queuing works well for simple, independent tasks. By simple we mean the task can be done all at once, in one piece, with no inter-task dependencies. This works well for performing a file upload task in the background (to avoid tying up a Ruby virtual machine process/thread). More complex (compound) multi-part tasks, however, do not fit this model. Examples of complex (compound) tasks include:
|
9
9
|
|
10
|
-
1. pipelined (multi-step) generation of complex PDF
|
11
|
-
2. extract/transfer/load (ETL)
|
10
|
+
1. pipelined (multi-step) generation of a complex PDF document
|
11
|
+
2. an extract/transfer/load (ETL) job that must acquire data from source systems, transform it and load it into the target system
|
12
12
|
|
13
13
|
If we would like to scale these compound operations, breaking them into smaller parts, and managing the execution of those parts across many computers, we need an "orchestrator". This project implements just such a framework, called "Orchestrated".
|
14
14
|
|
15
|
+
Orchestrated introduces the ```acts_as_orchestrated``` Object class method. When invoked on your class, this will define the ```orchestrated``` instance method. You use ```orchestrated``` in a mannner similar to [delayed_job](https://github.com/collectiveidea/delayed_job)'s ```delay```—the difference being that ```orchestrated``` takes a parameter that lets you specify dependencies between your jobs.
|
16
|
+
|
17
|
+
The reason we refer to [delayed_job](https://github.com/collectiveidea/delayed_job) as a restartable queueing system is because, even if computers (database host, worker hosts) in the cluster crash, the work on the queues progresses. If no worker is servicing a particular queue, then work accumulates there. Once workers are available, they consume the jobs. This is a resilient architecture.
|
18
|
+
|
19
|
+
With Orchestrated you can create restartable workflows, a workflow consisting of one or more dependent, queueable, tasks. This means that your workflows will continue to make progress even in the face of database and (queue) worker crashes.
|
20
|
+
|
21
|
+
In summary, orchestrated workflows running atop [active_record](https://github.com/rails/rails/tree/master/activerecord) and [delayed_job](https://github.com/collectiveidea/delayed_job) have these characteristics:
|
22
|
+
|
23
|
+
1. restartable—the workflows make progress even though (queue worker, and database) hosts are not always available
|
24
|
+
2. scalable—compound workflows are broken into steps which can be executed on separate computers. Results can be accumulated from the disparate steps as needed.
|
25
|
+
3. forgiving of external system failures—workflow steps that communicate with an external system can simply throw an exception when the system is unavailable, assured that the step will be automatically retried again later.
|
26
|
+
4. composable—compound tasks can be defined in terms of simpler ones
|
27
|
+
|
28
|
+
Read on to get started.
|
29
|
+
|
15
30
|
Installation
|
16
31
|
------------
|
17
32
|
|
@@ -37,7 +52,13 @@ If you do not already have [delayed_job](https://github.com/collectiveidea/delay
|
|
37
52
|
The API
|
38
53
|
-------
|
39
54
|
|
40
|
-
To orchestrate (methods) on your own classes you simply call ```acts_as_orchestrated``` in the class definition
|
55
|
+
To orchestrate (methods) on your own classes you simply call ```acts_as_orchestrated``` in the class definition. Declaring ```acts_as_orchestrated``` on your class defines the ```orchestrated``` method:
|
56
|
+
|
57
|
+
* ```orchestrated```—call this to specify your workflow prerequisite, and designate a workflow step
|
58
|
+
|
59
|
+
Use ```orchestrated``` to orchestrate any method on your class.
|
60
|
+
|
61
|
+
Let's say for example you needed to download a couple files from remote systems (a slow process), merge their content and then load the results into your system. Imagine you have a ```Downloader``` class that knows how to download and an ```Xform``` class that knows how to merge the content and load the results into your system. Your ```Xform``` class might look something like this:
|
41
62
|
|
42
63
|
```ruby
|
43
64
|
class Xform
|
@@ -55,12 +76,7 @@ class Xform
|
|
55
76
|
end
|
56
77
|
```
|
57
78
|
|
58
|
-
|
59
|
-
|
60
|
-
* ```orchestrated```—call this to specify your workflow prerequisite, and designate a workflow step
|
61
|
-
* ```orchestration```—call this in the context of a workflow step (execution) to access orchestration (and prerequisite) context
|
62
|
-
|
63
|
-
After that you can orchestrate any method on such a class. Let's say you needed to download a couple files from remote systems (a slow process), merge their content and then load the results into your system. Imagine you had had a Downloader class that knew how to download and an Xform class that knew how to merge the content and load the results into your system. You might write an orchestration like this:
|
79
|
+
You might write an orchestration like this:
|
64
80
|
|
65
81
|
```ruby
|
66
82
|
xform = Xform.new
|
@@ -78,15 +94,16 @@ xform.orchestrated(
|
|
78
94
|
).load('combined_records')
|
79
95
|
```
|
80
96
|
|
81
|
-
The next time you process delayed jobs, the
|
97
|
+
The next time you process delayed jobs, the ```download``` messages will be delivered to a couple Downloaders. After the last download completes, the next time a delayed job is processed, the ```merge``` message will be delivered to an Xform object. And on it goes…
|
82
98
|
|
83
99
|
What happened there? The pattern is:
|
84
100
|
|
85
101
|
1. create an orchestrated object (instantiate it)
|
86
|
-
2. call orchestrated on it: this returns
|
87
|
-
3. send
|
102
|
+
2. call ```orchestrated``` on it: this returns a *magic proxy* object that can respond to any of the messages your object can respond to
|
103
|
+
3. send any message to the *magic proxy* (returned in the second step) and the framework will delay delivery of that message and immediately return a "completion expression" you can use as a prerequisite for other orchestrations
|
104
|
+
4. (optionally) use the "completion expression" returned in (3) as a prerequisite for other orchestrations
|
88
105
|
|
89
|
-
Now the messages you can send in (3)
|
106
|
+
Now the messages you can send in (3) can be anything that your object can respond to. The message will be remembered by the framework and "replayed" (on a new instance of your object) somewhere on the network (later).
|
90
107
|
|
91
108
|
Not accidentally, this is similar to the way [delayed_job](https://github.com/collectiveidea/delayed_job)'s delay method works. Under the covers, orchestrated is conspiring with [delayed_job](https://github.com/collectiveidea/delayed_job) when it comes time to actually execute a workflow step. Before that time though, orchestrated keeps track of everything.
|
92
109
|
|
@@ -95,16 +112,15 @@ Key Concept: Prerequisites (Completion Expressions)
|
|
95
112
|
|
96
113
|
Unlike [delayed_job](https://github.com/collectiveidea/delayed_job) ```delay```, the orchestrated ```orchestrated``` method takes an optional parameter: the prerequisite. The prerequisite determines when your workflow step is ready to run.
|
97
114
|
|
98
|
-
The return value from
|
115
|
+
The return value from messaging the *magic proxy* is itself a ready-to-use prerequisite. You saw this in the statement generation example above. The result of the first call to ```orchestrated``` calls (to "download") were sent as an argument to the third ("merge"). In this way, the "merge" workflow step was suspended until after the "download"s finished. You may have also noticed from that example that if you specify no prerequisite then the step will be ready to run immediately, as was the case for the "download" calls).
|
99
116
|
|
100
|
-
|
117
|
+
Users of the framework deal directly with three kinds of prerequisite or "completion expression":
|
101
118
|
|
102
|
-
1. ```OrchestrationCompletion```—returned
|
103
|
-
2. ```
|
104
|
-
3. ```
|
105
|
-
4. ```LastCompletion```—aggregates other completions: complete after all of them are complete
|
119
|
+
1. ```OrchestrationCompletion```—returned messages to the *magic proxy*, complete when its associated orchestration is complete
|
120
|
+
2. ```FirstCompletion```—aggregates other completions: complete after the first one completes
|
121
|
+
3. ```LastCompletion```—aggregates other completions: complete after all of them are complete
|
106
122
|
|
107
|
-
See the completion_spec for examples of how to combine these different prerequisite types into completion expressions.
|
123
|
+
There are other kinds of completion expression used internally by the framework but these three are the important ones for users to understand. See the completion_spec for examples of how to combine these different prerequisite types into completion expressions.
|
108
124
|
|
109
125
|
Key Concept: Orchestration State
|
110
126
|
--------------------------------
|
@@ -115,15 +131,15 @@ An orchestration can be in one of a few states:
|
|
115
131
|
|
116
132
|
When you create a new orchestration that is waiting on a prerequisite that is not complete yet, the orchestration will be in the "waiting" state. Some time later, if that prerequisite completes, then your orchestration will become "ready". A "ready" orchestration is automatically queued to run by the framework (via [delayed_job](https://github.com/collectiveidea/delayed_job)).
|
117
133
|
|
118
|
-
A "ready" orchestration will use [delayed_job](https://github.com/collectiveidea/delayed_job) to
|
134
|
+
A "ready" orchestration will use [delayed_job](https://github.com/collectiveidea/delayed_job) to deliver its (delayed) message. In the context of such a message delivery (inside your object method e.g. ```Xform#merge``` or ```Xform#load``` in our example) you can rely on the ability to access the current Orchestration (context) object via the ```orchestration``` accessor. Be careful with that one though. You really shouldn't need it very often, and to use it, you have to understand framework internals.
|
119
135
|
|
120
136
|
After your workflow step executes, the orchestration moves into either the "succeeded" or "failed" state.
|
121
137
|
|
122
|
-
When an orchestration is "ready" or "waiting" it may be canceled by sending it the ```cancel!``` message. This moves it to the "canceled" state and prevents delivery of the orchestrated message (in the future).
|
138
|
+
When an orchestration is "ready" or "waiting" it may be canceled by sending it the ```cancel!``` message (i.e. a ```cancel!``` message to the ```OrchestrationCompletion```. This moves it to the orchestration to the "canceled" state and prevents delivery of the orchestrated message (in the future).
|
123
139
|
|
124
140
|
It is important to understand that both of the states: "succeeded" and "failed" are part of a "super-state": "complete". When an orchestration is in either of those two states, it will return ```true``` in response to the ```complete?``` message.
|
125
141
|
|
126
|
-
It is not just successful completion of orchestrated methods that causes dependent ones to run—a "failed" orchestration is complete too! If you have an orchestration that actually requires successful completion of its prerequisite then
|
142
|
+
It is not just successful completion of orchestrated methods that causes dependent ones to run—a "failed" orchestration is complete too! If you have an orchestration that actually requires successful completion of its prerequisite then your method can inspect the prerequisite as needed, by accessing it via ```self.orchestration.prerequisite.prerequisite```.
|
127
143
|
|
128
144
|
Failure (An Option)
|
129
145
|
-------------------
|
data/lib/orchestrated/base.rb
CHANGED
@@ -7,11 +7,7 @@ module Orchestrated
|
|
7
7
|
end
|
8
8
|
def method_missing(sym, *args)
|
9
9
|
raise 'cannot orchestrate with blocks because they are not portable across processes' if block_given?
|
10
|
-
|
11
|
-
completion.orchestration = Orchestration.create( @target, sym, args, @prerequisite)
|
12
|
-
end.tap do |completion|
|
13
|
-
completion.save!
|
14
|
-
end
|
10
|
+
Orchestration.create( @target, sym, args, @prerequisite)
|
15
11
|
end
|
16
12
|
end
|
17
13
|
|
@@ -44,10 +44,10 @@ module Orchestrated
|
|
44
44
|
def +(c); self << c; end # synonymc
|
45
45
|
end
|
46
46
|
class LastCompletion < CompositeCompletion
|
47
|
-
def complete?;
|
48
|
-
def always_complete?;
|
47
|
+
def complete?; prerequisite_associations.all?(&:complete?); end
|
48
|
+
def always_complete?; prerequisite_associations.empty?; end
|
49
49
|
def never_complete?; prerequisites.any?(&:never_complete?); end
|
50
|
-
def canceled?;
|
50
|
+
def canceled?; prerequisite_associations.any?(&:canceled?); end
|
51
51
|
def <<(c)
|
52
52
|
prerequisites << c unless c.always_complete?
|
53
53
|
self
|
@@ -60,10 +60,10 @@ module Orchestrated
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
class FirstCompletion < CompositeCompletion
|
63
|
-
def complete?;
|
63
|
+
def complete?; prerequisite_associations.any?(&:complete?); end
|
64
64
|
def always_complete?; prerequisites.any?(&:always_complete?); end
|
65
|
-
def never_complete?;
|
66
|
-
def canceled?;
|
65
|
+
def never_complete?; prerequisite_associations.empty?; end
|
66
|
+
def canceled?; prerequisite_associations.all?(&:canceled?); end
|
67
67
|
def <<(c)
|
68
68
|
prerequisites << c unless c.never_complete?
|
69
69
|
self
|
@@ -4,9 +4,12 @@ module Orchestrated
|
|
4
4
|
class OrchestrationDependency < ActiveRecord::Base
|
5
5
|
# TODO: figure out why Rails thinks I'm mass-assigning this over in Orchestration when I'm not really!
|
6
6
|
attr_accessible :prerequisite_id
|
7
|
-
belongs_to :dependent, :class_name => 'CompletionExpression'
|
8
7
|
belongs_to :prerequisite, :class_name => 'CompletionExpression'
|
9
|
-
|
8
|
+
belongs_to :dependent, :class_name => 'CompletionExpression'
|
9
|
+
|
10
|
+
before_validation :constrain
|
11
|
+
|
12
|
+
state_machine :initial => :incomplete, :action => :save_avoiding_recursion do
|
10
13
|
state :incomplete
|
11
14
|
state :complete
|
12
15
|
state :canceled
|
@@ -17,10 +20,50 @@ module Orchestrated
|
|
17
20
|
transition :incomplete => :canceled
|
18
21
|
end
|
19
22
|
after_transition any => :complete do |dependency, transition|
|
20
|
-
dependency.
|
23
|
+
dependency.call_dependent{|d| d.prerequisite_complete}
|
21
24
|
end
|
22
25
|
after_transition any => :canceled do |dependency, transition|
|
23
|
-
dependency.
|
26
|
+
dependency.call_dependent{|d| d.prerequisite_canceled}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
def call_dependent(&block)
|
30
|
+
yield dependent unless dependent.nil?
|
31
|
+
end
|
32
|
+
def constrain
|
33
|
+
@saving = true
|
34
|
+
_constrain.tap{@saving = false}
|
35
|
+
end
|
36
|
+
def _constrain
|
37
|
+
if prerequisite.present?
|
38
|
+
if prerequisite_id_changed? || new_record?
|
39
|
+
# this may be our first prerequisite, or our prerequisite may
|
40
|
+
# have changed—either way we must initialize our state
|
41
|
+
|
42
|
+
# This method can be called more than once in general since it is called
|
43
|
+
# as part of validation. Rather than loosening the state machine (to allow
|
44
|
+
# e.g. complete=>complete) we explicitly avoid re-submitting events here.
|
45
|
+
prerequisite_completed if prerequisite.complete? && can_prerequisite_completed?
|
46
|
+
prerequisite_canceled if prerequisite.canceled? && can_prerequisite_canceled?
|
47
|
+
else
|
48
|
+
# prerequisite has not changed so our state is already correct
|
49
|
+
if dependent_id_changed?
|
50
|
+
# dependent has been set for the first time—propigate state
|
51
|
+
call_dependent{|d| d.prerequisite_complete} if prerequisite.complete?
|
52
|
+
call_dependent{|d| d.prerequisite_canceled} if prerequisite.canceled?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
true
|
57
|
+
end
|
58
|
+
def save_avoiding_recursion
|
59
|
+
# Default action of state_machine is "save", however that is
|
60
|
+
# a problem when we need to transition state during validation
|
61
|
+
# (see constrain method above). If were are validating then we
|
62
|
+
# dursnt call save again.
|
63
|
+
if @saving
|
64
|
+
true # allow state transition but don't save ActiveRecord
|
65
|
+
else
|
66
|
+
save # save ActiveRecord as usual and return true/false
|
24
67
|
end
|
25
68
|
end
|
26
69
|
end
|
@@ -73,26 +73,23 @@ module Orchestrated
|
|
73
73
|
|
74
74
|
end
|
75
75
|
|
76
|
+
# Actually creates a completion (wrapper). Not _exactly_ an orchestration—ssh…
|
76
77
|
def self.create( value, sym, args, prerequisite)
|
77
|
-
#
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
# prime the pump for a constant prerequisite
|
94
|
-
orchestration.prerequisite_complete! if prerequisite.complete?
|
95
|
-
end
|
78
|
+
# wee! static analysis FTW!
|
79
|
+
raise 'prerequisite can never be complete' if prerequisite.never_complete?
|
80
|
+
prerequisite.save!
|
81
|
+
OrchestrationCompletion.new.tap do |completion|
|
82
|
+
completion.orchestration = new.tap do |orchestration|
|
83
|
+
orchestration.handler = Handler.new( value, sym, args)
|
84
|
+
orchestration.save!
|
85
|
+
interest = OrchestrationInterest.new.tap do |interest|
|
86
|
+
interest.prerequisite = prerequisite
|
87
|
+
interest.orchestration = orchestration
|
88
|
+
interest.save!
|
89
|
+
end # interest
|
90
|
+
end # orchestration
|
91
|
+
completion.save!
|
92
|
+
end # completion
|
96
93
|
end
|
97
94
|
|
98
95
|
def enqueue
|
data/lib/orchestrated/version.rb
CHANGED
data/orchestrated.gemspec
CHANGED
@@ -18,19 +18,19 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
|
-
gem.add_runtime_dependency 'delayed_job_active_record', '
|
22
|
-
gem.add_runtime_dependency 'activerecord', ['~> 3']
|
23
|
-
gem.add_runtime_dependency 'state_machine', ['
|
21
|
+
gem.add_runtime_dependency 'delayed_job_active_record', '~> 0.3.3'
|
22
|
+
gem.add_runtime_dependency 'activerecord', ['~> 3.2']
|
23
|
+
gem.add_runtime_dependency 'state_machine', ['~> 1.1.2']
|
24
24
|
|
25
25
|
gem.add_development_dependency 'rake', ['>= 10']
|
26
|
-
gem.add_development_dependency 'rails', ['~> 3'] # for rspec-rails
|
27
|
-
gem.add_development_dependency 'rspec-rails', ['
|
26
|
+
gem.add_development_dependency 'rails', ['~> 3.2'] # for rspec-rails
|
27
|
+
gem.add_development_dependency 'rspec-rails', ['~> 2.12']
|
28
28
|
# I couldn't get rspecs transactional fixtures setting to do savepoints
|
29
29
|
# in this project (which is not _really_ a Rails app). database_cleaner
|
30
30
|
# claims it'll help us clean up the database so let's try it!
|
31
|
-
gem.add_development_dependency 'database_cleaner', ['
|
32
|
-
gem.add_development_dependency 'sqlite3', ['
|
31
|
+
gem.add_development_dependency 'database_cleaner', ['~> 0.9']
|
32
|
+
gem.add_development_dependency 'sqlite3', ['~> 1.3']
|
33
33
|
gem.add_development_dependency 'debugger'
|
34
34
|
# The state_machine:draw rake task needs this
|
35
|
-
gem.add_development_dependency 'ruby-graphviz', ['
|
35
|
+
gem.add_development_dependency 'ruby-graphviz', ['~> 0.9']
|
36
36
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: orchestrated
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,24 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: delayed_job_active_record
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: 0.3.3
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
29
|
+
version: 0.3.3
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: activerecord
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
requirements:
|
35
35
|
- - ~>
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: '3'
|
37
|
+
version: '3.2'
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,23 +42,23 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: '3'
|
45
|
+
version: '3.2'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: state_machine
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
49
49
|
none: false
|
50
50
|
requirements:
|
51
|
-
- -
|
51
|
+
- - ~>
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
53
|
+
version: 1.1.2
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
none: false
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 1.1.2
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
name: rake
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,7 +82,7 @@ dependencies:
|
|
82
82
|
requirements:
|
83
83
|
- - ~>
|
84
84
|
- !ruby/object:Gem::Version
|
85
|
-
version: '3'
|
85
|
+
version: '3.2'
|
86
86
|
type: :development
|
87
87
|
prerelease: false
|
88
88
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -90,13 +90,13 @@ dependencies:
|
|
90
90
|
requirements:
|
91
91
|
- - ~>
|
92
92
|
- !ruby/object:Gem::Version
|
93
|
-
version: '3'
|
93
|
+
version: '3.2'
|
94
94
|
- !ruby/object:Gem::Dependency
|
95
95
|
name: rspec-rails
|
96
96
|
requirement: !ruby/object:Gem::Requirement
|
97
97
|
none: false
|
98
98
|
requirements:
|
99
|
-
- -
|
99
|
+
- - ~>
|
100
100
|
- !ruby/object:Gem::Version
|
101
101
|
version: '2.12'
|
102
102
|
type: :development
|
@@ -104,7 +104,7 @@ dependencies:
|
|
104
104
|
version_requirements: !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
|
-
- -
|
107
|
+
- - ~>
|
108
108
|
- !ruby/object:Gem::Version
|
109
109
|
version: '2.12'
|
110
110
|
- !ruby/object:Gem::Dependency
|
@@ -112,7 +112,7 @@ dependencies:
|
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
113
113
|
none: false
|
114
114
|
requirements:
|
115
|
-
- -
|
115
|
+
- - ~>
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: '0.9'
|
118
118
|
type: :development
|
@@ -120,7 +120,7 @@ dependencies:
|
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
none: false
|
122
122
|
requirements:
|
123
|
-
- -
|
123
|
+
- - ~>
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '0.9'
|
126
126
|
- !ruby/object:Gem::Dependency
|
@@ -128,17 +128,17 @@ dependencies:
|
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
129
129
|
none: false
|
130
130
|
requirements:
|
131
|
-
- -
|
131
|
+
- - ~>
|
132
132
|
- !ruby/object:Gem::Version
|
133
|
-
version: 1.3
|
133
|
+
version: '1.3'
|
134
134
|
type: :development
|
135
135
|
prerelease: false
|
136
136
|
version_requirements: !ruby/object:Gem::Requirement
|
137
137
|
none: false
|
138
138
|
requirements:
|
139
|
-
- -
|
139
|
+
- - ~>
|
140
140
|
- !ruby/object:Gem::Version
|
141
|
-
version: 1.3
|
141
|
+
version: '1.3'
|
142
142
|
- !ruby/object:Gem::Dependency
|
143
143
|
name: debugger
|
144
144
|
requirement: !ruby/object:Gem::Requirement
|
@@ -160,17 +160,17 @@ dependencies:
|
|
160
160
|
requirement: !ruby/object:Gem::Requirement
|
161
161
|
none: false
|
162
162
|
requirements:
|
163
|
-
- -
|
163
|
+
- - ~>
|
164
164
|
- !ruby/object:Gem::Version
|
165
|
-
version: 0.9
|
165
|
+
version: '0.9'
|
166
166
|
type: :development
|
167
167
|
prerelease: false
|
168
168
|
version_requirements: !ruby/object:Gem::Requirement
|
169
169
|
none: false
|
170
170
|
requirements:
|
171
|
-
- -
|
171
|
+
- - ~>
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: 0.9
|
173
|
+
version: '0.9'
|
174
174
|
description: a workflow orchestration framework running on delayed_job and active_record
|
175
175
|
email:
|
176
176
|
- bill@paydici.com
|