composable_operations 0.1.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 +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/composable_operations.gemspec +26 -0
- data/lib/composable_operations.rb +11 -0
- data/lib/composable_operations/composed_operation.rb +78 -0
- data/lib/composable_operations/matcher.rb +4 -0
- data/lib/composable_operations/matcher/fail_to_perform.rb +83 -0
- data/lib/composable_operations/matcher/succeed_to_perform.rb +70 -0
- data/lib/composable_operations/matcher/utilize_operation.rb +92 -0
- data/lib/composable_operations/operation.rb +165 -0
- data/lib/composable_operations/operation_error.rb +4 -0
- data/lib/composable_operations/version.rb +3 -0
- data/spec/composable_operations/composed_operation_spec.rb +135 -0
- data/spec/composable_operations/control_flow_spec.rb +187 -0
- data/spec/composable_operations/operation_spec.rb +229 -0
- data/spec/spec_helper.rb +11 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2815a0e9caf243a6e502b0f277cb98c36b44b9d5
|
4
|
+
data.tar.gz: 668f9e8de07e9287f7498909fca06f2d61ab4904
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 962cef4189be9cabea7c2df388d1f6be24929a211eafe43f9fadddec9ff4375fd553a977a01641ee812fb1e887fc2d62853bdb3eb29e2636b336f10899d3473f
|
7
|
+
data.tar.gz: e3f3d3df80f08542fa285e27fbe7d4ca9a7c217a41f0d74dfa97e737dc95e9d6742877ec08d2f5f41ccc9f28ac75bffc6b419a33ca1983d6047ea76dfe36db8f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Konstantin Tennhard
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# ComposableOperations
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'composable_operations'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install composable_operations
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'composable_operations/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "composable_operations"
|
8
|
+
spec.version = ComposableOperations::VERSION
|
9
|
+
spec.authors = ["Konstantin Tennhard"]
|
10
|
+
spec.email = ["me@t6d.de"]
|
11
|
+
spec.summary = %q{Tool set for operation pipelines.}
|
12
|
+
spec.description = %q{Composable Operations is a tool set for creating easy-to-use operation pipelines.}
|
13
|
+
spec.homepage = "http://github.com/t6d/composable_operations"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "smart_properties", "~> 1.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec", "~> 2.11"
|
26
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'smart_properties'
|
2
|
+
|
3
|
+
module ComposableOperations
|
4
|
+
# Your code goes here...
|
5
|
+
end
|
6
|
+
|
7
|
+
require_relative "composable_operations/version"
|
8
|
+
require_relative "composable_operations/operation_error"
|
9
|
+
require_relative "composable_operations/operation"
|
10
|
+
require_relative "composable_operations/composed_operation"
|
11
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ComposableOperations
|
2
|
+
class ComposedOperation < Operation
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def operations
|
6
|
+
[] + Array((super if defined? super)) + Array(@operations)
|
7
|
+
end
|
8
|
+
|
9
|
+
def use(operation)
|
10
|
+
(@operations ||= []) << operation
|
11
|
+
end
|
12
|
+
|
13
|
+
def compose(*operations, &block)
|
14
|
+
raise ArgumentError, "Expects either an array of operations or a block with configuration instructions" unless !!block ^ !operations.empty?
|
15
|
+
|
16
|
+
if block
|
17
|
+
Class.new(self, &block)
|
18
|
+
else
|
19
|
+
Class.new(self) do
|
20
|
+
operations.each do |operation|
|
21
|
+
use operation
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def transitions
|
28
|
+
transitions = []
|
29
|
+
klass = self
|
30
|
+
while klass != Operation
|
31
|
+
klass = klass.superclass
|
32
|
+
transitions += Array(klass.instance_variable_get(:@transitions))
|
33
|
+
end
|
34
|
+
transitions += Array(@transitions)
|
35
|
+
transitions
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def between(&callback)
|
42
|
+
(@transitions ||= []) << callback
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def operations
|
48
|
+
self.class.operations
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
def execute
|
54
|
+
[nil, *operations, nil].each_cons(2).inject(input) do |data, operations|
|
55
|
+
if operation = operations.last
|
56
|
+
operation = operation.new(data)
|
57
|
+
operation.perform
|
58
|
+
|
59
|
+
if operation.failed?
|
60
|
+
fail operation.message, operation.result, operation.backtrace
|
61
|
+
elsif operation.halted?
|
62
|
+
halt operation.message, operation.result
|
63
|
+
end
|
64
|
+
|
65
|
+
transition(*operations, data) if operations.first && operations.last
|
66
|
+
operation.result
|
67
|
+
else
|
68
|
+
data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def transition(a, b, payload)
|
74
|
+
self.class.transitions.each { |transition| instance_exec(a, b, payload, &transition) }
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module ComposableOperations
|
2
|
+
module Matcher
|
3
|
+
module FailToPerform
|
4
|
+
class Matcher
|
5
|
+
|
6
|
+
def matches?(operation)
|
7
|
+
self.operation = operation
|
8
|
+
failed? && result_as_expected? && message_as_expected?
|
9
|
+
end
|
10
|
+
|
11
|
+
def because(message)
|
12
|
+
@message = message
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def and_return(result)
|
17
|
+
@result = result
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def description
|
22
|
+
description = "fail to perform"
|
23
|
+
description += " because #{message}" if message
|
24
|
+
description += " and return the expected result" if result
|
25
|
+
description
|
26
|
+
end
|
27
|
+
|
28
|
+
def failure_message
|
29
|
+
"the operation did not fail to perform for the following reason(s):\n#{failure_reasons}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def negative_failure_message
|
33
|
+
"the operation failed unexpectedly"
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
attr_reader :operation
|
39
|
+
attr_reader :message
|
40
|
+
attr_reader :result
|
41
|
+
|
42
|
+
def operation=(operation)
|
43
|
+
operation.perform
|
44
|
+
@operation = operation
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def failed?
|
50
|
+
operation.failed?
|
51
|
+
end
|
52
|
+
|
53
|
+
def message_as_expected?
|
54
|
+
return true unless message
|
55
|
+
operation.message == message
|
56
|
+
end
|
57
|
+
|
58
|
+
def result_as_expected?
|
59
|
+
return true unless result
|
60
|
+
operation.result == result
|
61
|
+
end
|
62
|
+
|
63
|
+
def failure_reasons
|
64
|
+
reasons = []
|
65
|
+
reasons << "it did not fail at all" unless failed?
|
66
|
+
reasons << "its message was not as expected" unless message_as_expected?
|
67
|
+
reasons << "it did not return the expected result" unless result_as_expected?
|
68
|
+
reasons.map { |r| "\t- #{r}" }.join("\n")
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def fail_to_perform
|
74
|
+
Matcher.new
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
RSpec.configure do |config|
|
82
|
+
config.include ComposableOperations::Matcher::FailToPerform
|
83
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module ComposableOperations
|
2
|
+
module Matcher
|
3
|
+
module SucceedToPerform
|
4
|
+
class Matcher
|
5
|
+
|
6
|
+
def matches?(operation)
|
7
|
+
self.operation = operation
|
8
|
+
succeeded? && result_as_expected?
|
9
|
+
end
|
10
|
+
|
11
|
+
def and_return(result)
|
12
|
+
@result = result
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
description = "succeed to perform"
|
18
|
+
description += " and return the expected result" if result
|
19
|
+
description
|
20
|
+
end
|
21
|
+
|
22
|
+
def failure_message
|
23
|
+
"the operation failed to perform for the following reason(s):\n#{failure_reasons}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def negative_failure_message
|
27
|
+
"the operation succeeded unexpectedly"
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
attr_reader :operation
|
33
|
+
attr_reader :result
|
34
|
+
|
35
|
+
def operation=(operation)
|
36
|
+
operation.perform
|
37
|
+
@operation = operation
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def succeeded?
|
43
|
+
operation.succeeded?
|
44
|
+
end
|
45
|
+
|
46
|
+
def result_as_expected?
|
47
|
+
return true unless result
|
48
|
+
operation.result == result
|
49
|
+
end
|
50
|
+
|
51
|
+
def failure_reasons
|
52
|
+
reasons = []
|
53
|
+
reasons << "it did not succeed at all" unless succeeded?
|
54
|
+
reasons << "it did not return the expected result" unless result_as_expected?
|
55
|
+
reasons.map { |r| "\t- #{r}" }.join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def succeed_to_perform
|
61
|
+
Matcher.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
RSpec.configure do |config|
|
68
|
+
config.include ComposableOperations::Matcher::SucceedToPerform
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module ComposableOperations
|
2
|
+
module Matcher
|
3
|
+
module UtilizeOperation
|
4
|
+
|
5
|
+
class DummyOperation
|
6
|
+
def initialize(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def failed?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def halted?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def succeeded?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def result
|
22
|
+
Object.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def perform
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Matcher
|
31
|
+
|
32
|
+
attr_reader :composite_operations
|
33
|
+
attr_reader :tested_operation
|
34
|
+
attr_reader :tested_instance
|
35
|
+
|
36
|
+
def initialize(*composite_operations)
|
37
|
+
@composite_operations = composite_operations.flatten
|
38
|
+
end
|
39
|
+
|
40
|
+
def matches?(class_or_instance)
|
41
|
+
if class_or_instance.is_a?(Class)
|
42
|
+
@tested_operation = class_or_instance
|
43
|
+
@tested_instance = class_or_instance.new
|
44
|
+
else
|
45
|
+
@tested_operation = class_or_instance.class
|
46
|
+
@tested_instance = class_or_instance
|
47
|
+
end
|
48
|
+
|
49
|
+
Operation.stub(:new => DummyOperation.new)
|
50
|
+
composite_operations.each do |composite_operation|
|
51
|
+
dummy_operation = DummyOperation.new
|
52
|
+
dummy_operation.should_receive(:perform).and_call_original
|
53
|
+
composite_operation.should_receive(:new).and_return(dummy_operation)
|
54
|
+
end
|
55
|
+
tested_instance.stub(:prepare => true, :finalize => true)
|
56
|
+
tested_instance.perform
|
57
|
+
|
58
|
+
tested_operation.operations == composite_operations
|
59
|
+
end
|
60
|
+
|
61
|
+
def description
|
62
|
+
"utilize the following operations: #{composite_operations.map(&:to_s).join(', ')}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def failure_message
|
66
|
+
expected_but_not_used = composite_operations - tested_operation.operations
|
67
|
+
used_but_not_exptected = tested_operation.operations - composite_operations
|
68
|
+
message = ["Unexpected operation utilization:"]
|
69
|
+
message << "Expected: #{expected_but_not_used.join(', ')}" unless expected_but_not_used.empty?
|
70
|
+
message << "Not expected: #{used_but_not_exptected.join(', ')}" unless used_but_not_exptected.empty?
|
71
|
+
message.join("\n\t")
|
72
|
+
end
|
73
|
+
|
74
|
+
def negative_failure_message
|
75
|
+
"Unexpected operation utilization"
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
def utilize_operation(*args)
|
81
|
+
Matcher.new(*args)
|
82
|
+
end
|
83
|
+
alias utilize_operations utilize_operation
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
RSpec.configure do |config|
|
90
|
+
config.include ComposableOperations::Matcher::UtilizeOperation
|
91
|
+
end
|
92
|
+
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module ComposableOperations
|
2
|
+
class Operation
|
3
|
+
|
4
|
+
include SmartProperties
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def perform(*args)
|
9
|
+
operation = new(*args)
|
10
|
+
operation.perform
|
11
|
+
|
12
|
+
raise exception, operation.message, operation.backtrace if operation.failed?
|
13
|
+
|
14
|
+
operation.result
|
15
|
+
end
|
16
|
+
|
17
|
+
def preparators
|
18
|
+
preparators = []
|
19
|
+
klass = self
|
20
|
+
while klass != Operation
|
21
|
+
klass = klass.superclass
|
22
|
+
preparators += Array(klass.instance_variable_get(:@preparators))
|
23
|
+
end
|
24
|
+
preparators += Array(@preparators)
|
25
|
+
preparators
|
26
|
+
end
|
27
|
+
|
28
|
+
def finalizers
|
29
|
+
finalizers = []
|
30
|
+
klass = self
|
31
|
+
while klass != Operation
|
32
|
+
klass = klass.superclass
|
33
|
+
finalizers += Array(klass.instance_variable_get(:@finalizers))
|
34
|
+
end
|
35
|
+
finalizers += Array(@finalizers)
|
36
|
+
finalizers
|
37
|
+
end
|
38
|
+
|
39
|
+
def exception
|
40
|
+
@exception or defined?(super) ? super : OperationError
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def before(&callback)
|
46
|
+
(@preparators ||= []) << callback
|
47
|
+
end
|
48
|
+
|
49
|
+
def after(&callback)
|
50
|
+
(@finalizers ||= []) << callback
|
51
|
+
end
|
52
|
+
|
53
|
+
def processes(*names)
|
54
|
+
case names.length
|
55
|
+
when 0
|
56
|
+
raise ArgumentError, "#{self}.#{__callee__} expects at least one argument"
|
57
|
+
when 1
|
58
|
+
alias_method names[0].to_sym, :input
|
59
|
+
else
|
60
|
+
names.each_with_index do |name, index|
|
61
|
+
define_method(name) { input[index] }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def raises(exception)
|
67
|
+
@exception = exception
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def method_added(method)
|
73
|
+
super
|
74
|
+
protected method if method == :execute
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_reader :input
|
80
|
+
attr_reader :result
|
81
|
+
attr_reader :message
|
82
|
+
attr_reader :backtrace
|
83
|
+
|
84
|
+
def initialize(input = nil, options = {})
|
85
|
+
super(options)
|
86
|
+
@input = input
|
87
|
+
end
|
88
|
+
|
89
|
+
def failed?
|
90
|
+
state == :failed
|
91
|
+
end
|
92
|
+
|
93
|
+
def halted?
|
94
|
+
state == :halted
|
95
|
+
end
|
96
|
+
|
97
|
+
def succeeded?
|
98
|
+
state == :succeeded
|
99
|
+
end
|
100
|
+
|
101
|
+
def message?
|
102
|
+
message.present?
|
103
|
+
end
|
104
|
+
|
105
|
+
def name
|
106
|
+
self.class.name
|
107
|
+
end
|
108
|
+
|
109
|
+
def perform
|
110
|
+
self.result = catch(:halt) do
|
111
|
+
prepare
|
112
|
+
result = execute
|
113
|
+
self.state = :succeeded
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
finalize
|
118
|
+
|
119
|
+
self.result
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
attr_accessor :state
|
125
|
+
|
126
|
+
attr_writer :message
|
127
|
+
attr_writer :result
|
128
|
+
attr_writer :backtrace
|
129
|
+
|
130
|
+
def execute
|
131
|
+
raise NotImplementedError, "#{name}#execute not implemented"
|
132
|
+
end
|
133
|
+
|
134
|
+
def fail(message = nil, return_value = nil, backtrace = caller)
|
135
|
+
raise "Operation execution has already been aborted" if halted? or failed?
|
136
|
+
|
137
|
+
self.state = :failed
|
138
|
+
self.backtrace = backtrace
|
139
|
+
self.message = message
|
140
|
+
throw :halt, return_value
|
141
|
+
end
|
142
|
+
|
143
|
+
def halt(message = nil, return_value = input)
|
144
|
+
raise "Operation execution has already been aborted" if halted? or failed?
|
145
|
+
|
146
|
+
self.state = :halted
|
147
|
+
self.message = message
|
148
|
+
throw :halt, return_value
|
149
|
+
end
|
150
|
+
|
151
|
+
def prepare
|
152
|
+
self.class.preparators.each { |preparator| instance_eval(&preparator) }
|
153
|
+
end
|
154
|
+
|
155
|
+
def finalize
|
156
|
+
self.class.finalizers.each do |finalizer|
|
157
|
+
self.result = catch(:halt) do
|
158
|
+
instance_eval(&finalizer)
|
159
|
+
self.result
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ComposableOperations::ComposedOperation do
|
4
|
+
|
5
|
+
let(:string_generator) do
|
6
|
+
Class.new(ComposableOperations::Operation) do
|
7
|
+
def self.name
|
8
|
+
"StringGenerator"
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
"chunky bacon"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:string_capitalizer) do
|
18
|
+
Class.new(ComposableOperations::Operation) do
|
19
|
+
def self.name
|
20
|
+
"StringCapitalizer"
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute
|
24
|
+
input.upcase
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:halting_operation) do
|
30
|
+
Class.new(ComposableOperations::Operation) do
|
31
|
+
def execute
|
32
|
+
halt
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when composed of one operation that generates a string no matter the input" do
|
38
|
+
|
39
|
+
subject(:composed_operation) do
|
40
|
+
operation = string_generator
|
41
|
+
|
42
|
+
Class.new(described_class) do
|
43
|
+
use operation
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should return this string as result" do
|
48
|
+
composed_operation.perform(nil).should be == "chunky bacon"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when composed of two operations using the factory method '#chain'" do
|
54
|
+
|
55
|
+
subject(:composed_operation) do
|
56
|
+
described_class.compose(string_generator, string_capitalizer).new
|
57
|
+
end
|
58
|
+
|
59
|
+
it { should succeed_to_perform.and_return("CHUNKY BACON") }
|
60
|
+
|
61
|
+
it { should utilize_operations(string_generator, string_capitalizer) }
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when composed of two operations, one that generates a string and one that capitalizes strings, " do
|
66
|
+
|
67
|
+
subject(:composed_operation) do
|
68
|
+
operations = [string_generator, string_capitalizer]
|
69
|
+
|
70
|
+
Class.new(described_class) do
|
71
|
+
use operations.first
|
72
|
+
use operations.last
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should return a capitalized version of the generated string" do
|
77
|
+
composed_operation.perform(nil).should be == "CHUNKY BACON"
|
78
|
+
end
|
79
|
+
|
80
|
+
it { should utilize_operations(string_generator, string_capitalizer) }
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when composed of three operations, one that generates a string, one that halts and one that capatalizes strings" do
|
85
|
+
|
86
|
+
subject(:composed_operation) do
|
87
|
+
described_class.compose(string_generator, halting_operation, string_capitalizer)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should return a capitalized version of the generated string" do
|
91
|
+
composed_operation.perform.should be == "chunky bacon"
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should only execute the first two operations" do
|
95
|
+
string_generator.any_instance.should_receive(:perform).and_call_original
|
96
|
+
halting_operation.any_instance.should_receive(:perform).and_call_original
|
97
|
+
string_capitalizer.any_instance.should_not_receive(:perform)
|
98
|
+
composed_operation.perform
|
99
|
+
end
|
100
|
+
|
101
|
+
it { should utilize_operations(string_generator, halting_operation, string_capitalizer) }
|
102
|
+
end
|
103
|
+
|
104
|
+
context "when composed of two operations and provided with a between block" do
|
105
|
+
|
106
|
+
|
107
|
+
let(:logger) { stub("Logger").as_null_object }
|
108
|
+
|
109
|
+
subject(:composed_operation) do
|
110
|
+
string_generator = string_generator()
|
111
|
+
string_capitalizer = string_capitalizer()
|
112
|
+
logger = logger()
|
113
|
+
|
114
|
+
operation = described_class.compose do
|
115
|
+
use string_generator
|
116
|
+
use string_capitalizer
|
117
|
+
|
118
|
+
between do |a, b, payload|
|
119
|
+
logger.info("#{a.name} -> #{b.name} with #{payload.inspect} as payload")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
operation.new
|
124
|
+
end
|
125
|
+
|
126
|
+
it { should succeed_to_perform.and_return("CHUNKY BACON") }
|
127
|
+
|
128
|
+
it "should generate the correct log message" do
|
129
|
+
logger.should_receive(:info).with("StringGenerator -> StringCapitalizer with \"chunky bacon\" as payload")
|
130
|
+
composed_operation.perform
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "An operation with two before and two after filters =>" do
|
4
|
+
|
5
|
+
let(:test_operation) do
|
6
|
+
Class.new(ComposableOperations::Operation) do
|
7
|
+
|
8
|
+
processes :flow_control
|
9
|
+
|
10
|
+
attr_accessor :trace
|
11
|
+
|
12
|
+
def initialize(input = [], options = {})
|
13
|
+
super
|
14
|
+
self.trace = [:initialize]
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
trace << :outer_before
|
19
|
+
fail "Fail in outer before" if flow_control.include?(:fail_in_outer_before)
|
20
|
+
halt "Halt in outer before" if flow_control.include?(:halt_in_outer_before)
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
trace << :inner_before
|
25
|
+
fail "Fail in inner before" if flow_control.include?(:fail_in_inner_before)
|
26
|
+
halt "Halt in inner before" if flow_control.include?(:halt_in_inner_before)
|
27
|
+
end
|
28
|
+
|
29
|
+
after do
|
30
|
+
trace << :inner_after
|
31
|
+
fail "Fail in inner after" if flow_control.include?(:fail_in_inner_after)
|
32
|
+
halt "Halt in inner after" if flow_control.include?(:halt_in_inner_after)
|
33
|
+
end
|
34
|
+
|
35
|
+
after do
|
36
|
+
trace << :outer_after
|
37
|
+
fail "Fail in outer after" if flow_control.include?(:fail_in_outer_after)
|
38
|
+
halt "Halt in outer after" if flow_control.include?(:halt_in_outer_after)
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute
|
42
|
+
trace << :execute_start
|
43
|
+
fail "Fail in execute" if flow_control.include?(:fail_in_execute)
|
44
|
+
halt "Halt in execute" if flow_control.include?(:halt_in_execute)
|
45
|
+
trace << :execute_stop
|
46
|
+
:final_result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when run and everything works as expected =>" do
|
52
|
+
subject { test_operation.new }
|
53
|
+
before(:each) { subject.perform }
|
54
|
+
|
55
|
+
it ("should run 'initialize' first") { subject.trace[0].should eq(:initialize) }
|
56
|
+
it ("should run 'outer before' after 'initialize'") { subject.trace[1].should eq(:outer_before) }
|
57
|
+
it ("should run 'inner before' after 'outer before'") { subject.trace[2].should eq(:inner_before) }
|
58
|
+
it ("should start 'execute' after 'inner before'") { subject.trace[3].should eq(:execute_start) }
|
59
|
+
it ("should stop 'execute' after it started 'execute'") { subject.trace[4].should eq(:execute_stop) }
|
60
|
+
it ("should run 'inner after' after 'execute'") { subject.trace[5].should eq(:inner_after) }
|
61
|
+
it ("should run 'outer after' after 'inner after'") { subject.trace[6].should eq(:outer_after) }
|
62
|
+
it ("should return :final_result as result") { subject.result.should eq(:final_result) }
|
63
|
+
it { should be_succeeded }
|
64
|
+
it { should_not be_failed }
|
65
|
+
it { should_not be_halted }
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# Now: TEST ALL! the possible code flows systematically
|
70
|
+
|
71
|
+
test_vectors = [
|
72
|
+
{
|
73
|
+
:context => "no complications =>",
|
74
|
+
:input => [],
|
75
|
+
:output => :final_result,
|
76
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :execute_stop, :inner_after, :outer_after],
|
77
|
+
:state => :succeeded
|
78
|
+
}, {
|
79
|
+
:context => "failing in outer_before filter =>",
|
80
|
+
:input => [:fail_in_outer_before],
|
81
|
+
:output => nil,
|
82
|
+
:trace => [:initialize, :outer_before, :inner_after, :outer_after],
|
83
|
+
:state => :failed
|
84
|
+
}, {
|
85
|
+
:context => "failing in inner_before filter =>",
|
86
|
+
:input => [:fail_in_inner_before],
|
87
|
+
:output => nil,
|
88
|
+
:trace => [:initialize, :outer_before, :inner_before, :inner_after, :outer_after],
|
89
|
+
:state => :failed
|
90
|
+
}, {
|
91
|
+
:context => "failing in execute =>",
|
92
|
+
:input => [:fail_in_execute],
|
93
|
+
:output => nil,
|
94
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :inner_after, :outer_after],
|
95
|
+
:state => :failed
|
96
|
+
}, {
|
97
|
+
:context => "failing in inner_after filter =>",
|
98
|
+
:input => [:fail_in_inner_after],
|
99
|
+
:output => nil,
|
100
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :execute_stop, :inner_after, :outer_after],
|
101
|
+
:state => :failed
|
102
|
+
}, {
|
103
|
+
:context => "failing in outer_after filter =>",
|
104
|
+
:input => [:fail_in_outer_after],
|
105
|
+
:output => nil,
|
106
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :execute_stop, :inner_after, :outer_after],
|
107
|
+
:state => :failed
|
108
|
+
}, {
|
109
|
+
:context => "halting in outer_before filter =>",
|
110
|
+
:input => [:halt_in_outer_before],
|
111
|
+
:output => [:halt_in_outer_before],
|
112
|
+
:trace => [:initialize, :outer_before, :inner_after, :outer_after],
|
113
|
+
:state => :halted
|
114
|
+
}, {
|
115
|
+
:context => "halting in inner_before filter =>",
|
116
|
+
:input => [:halt_in_inner_before],
|
117
|
+
:output => [:halt_in_inner_before],
|
118
|
+
:trace => [:initialize, :outer_before, :inner_before, :inner_after, :outer_after],
|
119
|
+
:state => :halted
|
120
|
+
}, {
|
121
|
+
:context => "halting in execute =>",
|
122
|
+
:input => [:halt_in_execute],
|
123
|
+
:output => [:halt_in_execute],
|
124
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :inner_after, :outer_after],
|
125
|
+
:state => :halted
|
126
|
+
}, {
|
127
|
+
:context => "halting in inner_after filter =>",
|
128
|
+
:input => [:halt_in_inner_after],
|
129
|
+
:output => [:halt_in_inner_after],
|
130
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :execute_stop, :inner_after, :outer_after],
|
131
|
+
:state => :halted
|
132
|
+
}, {
|
133
|
+
:context => "halting in outer_after filter =>",
|
134
|
+
:input => [:halt_in_outer_after],
|
135
|
+
:output => [:halt_in_outer_after],
|
136
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :execute_stop, :inner_after, :outer_after],
|
137
|
+
:state => :halted
|
138
|
+
}
|
139
|
+
]
|
140
|
+
|
141
|
+
context "when initialized with input that leads to =>" do
|
142
|
+
subject { test_operation.new(input) }
|
143
|
+
before(:each) { subject.perform }
|
144
|
+
|
145
|
+
test_vectors.each do |tv|
|
146
|
+
context tv[:context] do
|
147
|
+
let(:input) { tv[:input] }
|
148
|
+
let(:trace) { subject.trace }
|
149
|
+
|
150
|
+
it("then its trace should be #{tv[:trace].inspect}") { subject.trace.should eq(tv[:trace]) }
|
151
|
+
it("then its result should be #{tv[:output].inspect}") { subject.result.should eq(tv[:output]) }
|
152
|
+
it("then its succeeded? method should return #{(tv[:state] == :succeeded).inspect}") { subject.succeeded?.should eq(tv[:state] == :succeeded) }
|
153
|
+
it("then its failed? method should return #{(tv[:state] == :failed).inspect}") { subject.failed?.should eq(tv[:state] == :failed) }
|
154
|
+
it("then its halted? method should return #{(tv[:state] == :halted).inspect}") { subject.halted?.should eq(tv[:state] == :halted) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "when halt and fail are used together" do
|
160
|
+
subject { test_operation.new([:halt_in_execute, :fail_in_inner_after]) }
|
161
|
+
it "should raise on calling operation.perform" do
|
162
|
+
expect { subject.perform }.to raise_error
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context "when fail and halt are used together" do
|
167
|
+
subject { test_operation.new([:fail_in_execute, :halt_in_inner_after]) }
|
168
|
+
it "should raise on calling operation.perform" do
|
169
|
+
expect { subject.perform }.to raise_error
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "when halt is used twice" do
|
174
|
+
subject { test_operation.new([:halt_in_execute, :halt_in_inner_after]) }
|
175
|
+
it "should raise on calling operation.perform" do
|
176
|
+
expect { subject.perform }.to raise_error
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "when fail is used twice" do
|
181
|
+
subject { test_operation.new([:fail_in_execute, :fail_in_inner_after]) }
|
182
|
+
it "should raise on calling operation.perform" do
|
183
|
+
expect { subject.perform }.to raise_error
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ComposableOperations::Operation do
|
4
|
+
|
5
|
+
context "that always returns nil when executed" do
|
6
|
+
|
7
|
+
subject(:nil_operation) do
|
8
|
+
class << (operation = described_class.new(''))
|
9
|
+
def execute
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
operation
|
14
|
+
end
|
15
|
+
|
16
|
+
it { should succeed_to_perform.and_return(nil) }
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
context "that always halts" do
|
21
|
+
|
22
|
+
let(:halting_operation) do
|
23
|
+
Class.new(described_class) do
|
24
|
+
def execute
|
25
|
+
halt "Full stop!"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:halting_operation_instance) do
|
31
|
+
halting_operation.new("Test")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return the input value when executed using the class' method perform" do
|
35
|
+
halting_operation.perform("Test").should be == "Test"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return the input value when executed using the instance's peform method" do
|
39
|
+
halting_operation_instance.perform.should be == "Test"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should have halted after performing" do
|
43
|
+
halting_operation_instance.perform
|
44
|
+
halting_operation_instance.should be_halted
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
context "that always fails" do
|
50
|
+
|
51
|
+
let(:failing_operation) do
|
52
|
+
Class.new(described_class) do
|
53
|
+
def execute
|
54
|
+
fail "Operation failed"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
subject(:failing_operation_instance) do
|
60
|
+
failing_operation.new
|
61
|
+
end
|
62
|
+
|
63
|
+
before(:each) do
|
64
|
+
failing_operation_instance.perform
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should have nil as result" do
|
68
|
+
failing_operation_instance.result.should be_nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should have failed" do
|
72
|
+
failing_operation_instance.should be_failed
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should have a message" do
|
76
|
+
failing_operation_instance.message.should_not be_nil
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should raise an error when executed using the class method perform" do
|
80
|
+
expect { failing_operation.perform }.to raise_error("Operation failed")
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when extended with a finalizer" do
|
84
|
+
|
85
|
+
let(:supervisor) { mock("Supervisor") }
|
86
|
+
|
87
|
+
let(:failing_operation_instance_with_finalizer) do
|
88
|
+
supervisor = supervisor()
|
89
|
+
Class.new(failing_operation) do
|
90
|
+
after { supervisor.notify }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
subject(:failing_operation_instance_with_finalizer_instance) do
|
95
|
+
failing_operation_instance_with_finalizer.new
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should execute the finalizers" do
|
99
|
+
supervisor.should_receive(:notify)
|
100
|
+
failing_operation_instance_with_finalizer_instance.perform
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when configured to raise a custom exception" do
|
106
|
+
|
107
|
+
let(:custom_exception) { Class.new(RuntimeError) }
|
108
|
+
|
109
|
+
subject(:failing_operation_with_custom_exception) do
|
110
|
+
custom_exception = custom_exception()
|
111
|
+
Class.new(failing_operation) do
|
112
|
+
raises custom_exception
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should raise the custom exeception when executed using the class method perform" do
|
117
|
+
expect { failing_operation_with_custom_exception.perform }.to raise_error(custom_exception, "Operation failed")
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
context "that always returns something when executed" do
|
125
|
+
|
126
|
+
let(:simple_operation) do
|
127
|
+
Class.new(described_class) do
|
128
|
+
def execute
|
129
|
+
""
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
subject(:simple_operation_instance) do
|
135
|
+
simple_operation.new
|
136
|
+
end
|
137
|
+
|
138
|
+
before(:each) do
|
139
|
+
simple_operation_instance.perform
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should have a result" do
|
143
|
+
simple_operation_instance.result.should be
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should have succeeded" do
|
147
|
+
simple_operation_instance.should be_succeeded
|
148
|
+
end
|
149
|
+
|
150
|
+
context "when extended with a preparator and a finalizer" do
|
151
|
+
|
152
|
+
let(:logger) { double("Logger") }
|
153
|
+
|
154
|
+
subject(:simple_operation_with_preparator_and_finalizer) do
|
155
|
+
logger = logger()
|
156
|
+
Class.new(simple_operation) do
|
157
|
+
before { logger.info("preparing") }
|
158
|
+
after { logger.info("finalizing") }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should execute the preparator and finalizer when performing" do
|
163
|
+
logger.should_receive(:info).ordered.with("preparing")
|
164
|
+
logger.should_receive(:info).ordered.with("finalizing")
|
165
|
+
simple_operation_with_preparator_and_finalizer.perform
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
context "when extended with a finalizer that checks that the result is not an empty string" do
|
171
|
+
|
172
|
+
let(:simple_operation_with_sanity_check) do
|
173
|
+
Class.new(simple_operation) do
|
174
|
+
after { fail "the operational result is an empty string" if self.result == "" }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
subject(:simple_operation_with_sanity_check_instance) do
|
179
|
+
simple_operation_with_sanity_check.new
|
180
|
+
end
|
181
|
+
|
182
|
+
it { should fail_to_perform.because("the operational result is an empty string") }
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
context "that can be parameterized" do
|
189
|
+
|
190
|
+
subject(:string_multiplier) do
|
191
|
+
Class.new(described_class) do
|
192
|
+
property :multiplier, :default => 3
|
193
|
+
|
194
|
+
def execute
|
195
|
+
input.to_s * multiplier
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should operate according to the specified default value" do
|
201
|
+
string_multiplier.perform("-").should be == "---"
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should allow to overwrite default settings" do
|
205
|
+
string_multiplier.perform("-", :multiplier => 5).should be == "-----"
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
context "that processes two values (a string and a multiplier)" do
|
211
|
+
|
212
|
+
subject(:string_multiplier) do
|
213
|
+
Class.new(described_class) do
|
214
|
+
processes :string, :multiplier
|
215
|
+
|
216
|
+
def execute
|
217
|
+
string * multiplier
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should build a string that is multiplier-times long" do
|
223
|
+
string_multiplier.perform(["-", 3]).should be == "---"
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: composable_operations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Konstantin Tennhard
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: smart_properties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
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: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.11'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.11'
|
69
|
+
description: Composable Operations is a tool set for creating easy-to-use operation
|
70
|
+
pipelines.
|
71
|
+
email:
|
72
|
+
- me@t6d.de
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- composable_operations.gemspec
|
83
|
+
- lib/composable_operations.rb
|
84
|
+
- lib/composable_operations/composed_operation.rb
|
85
|
+
- lib/composable_operations/matcher.rb
|
86
|
+
- lib/composable_operations/matcher/fail_to_perform.rb
|
87
|
+
- lib/composable_operations/matcher/succeed_to_perform.rb
|
88
|
+
- lib/composable_operations/matcher/utilize_operation.rb
|
89
|
+
- lib/composable_operations/operation.rb
|
90
|
+
- lib/composable_operations/operation_error.rb
|
91
|
+
- lib/composable_operations/version.rb
|
92
|
+
- spec/composable_operations/composed_operation_spec.rb
|
93
|
+
- spec/composable_operations/control_flow_spec.rb
|
94
|
+
- spec/composable_operations/operation_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
homepage: http://github.com/t6d/composable_operations
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.0.3
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Tool set for operation pipelines.
|
120
|
+
test_files:
|
121
|
+
- spec/composable_operations/composed_operation_spec.rb
|
122
|
+
- spec/composable_operations/control_flow_spec.rb
|
123
|
+
- spec/composable_operations/operation_spec.rb
|
124
|
+
- spec/spec_helper.rb
|