tzu 0.0.2.0 → 0.1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +395 -0
- data/lib/tzu.rb +12 -51
- data/lib/tzu/core_extensions/string.rb +17 -0
- data/lib/tzu/hooks.rb +21 -4
- data/lib/tzu/invalid.rb +0 -2
- data/lib/tzu/outcome.rb +0 -1
- data/lib/tzu/run_methods.rb +32 -0
- data/lib/tzu/sequence.rb +68 -0
- data/lib/tzu/step.rb +34 -0
- data/lib/tzu/validation.rb +5 -14
- data/spec/sequence_spec.rb +206 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/step_spec.rb +24 -0
- data/spec/tzu_spec.rb +195 -20
- data/spec/validation_spec.rb +21 -37
- metadata +24 -5
- data/lib/tzu/organizer.rb +0 -35
- data/spec/organizer_spec.rb +0 -48
@@ -0,0 +1,17 @@
|
|
1
|
+
module Tzu
|
2
|
+
module CoreExtensions
|
3
|
+
module String
|
4
|
+
def symbolize
|
5
|
+
underscore.to_sym
|
6
|
+
end
|
7
|
+
|
8
|
+
def underscore
|
9
|
+
gsub(/::/, '/')
|
10
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
11
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
12
|
+
.tr('-', '_')
|
13
|
+
.downcase
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/tzu/hooks.rb
CHANGED
@@ -19,6 +19,11 @@ module Tzu
|
|
19
19
|
hooks.each { |hook| after_hooks.push(hook) }
|
20
20
|
end
|
21
21
|
|
22
|
+
def around(*hooks, &block)
|
23
|
+
hooks << block if block
|
24
|
+
hooks.each { |hook| around_hooks.push(hook) }
|
25
|
+
end
|
26
|
+
|
22
27
|
def before_hooks
|
23
28
|
@before_hooks ||= []
|
24
29
|
end
|
@@ -26,17 +31,30 @@ module Tzu
|
|
26
31
|
def after_hooks
|
27
32
|
@after_hooks ||= []
|
28
33
|
end
|
34
|
+
|
35
|
+
def around_hooks
|
36
|
+
@around_hooks ||= []
|
37
|
+
end
|
29
38
|
end
|
30
39
|
|
31
40
|
def with_hooks(params, &block)
|
32
|
-
|
33
|
-
|
34
|
-
|
41
|
+
result = nil
|
42
|
+
run_around_hooks do
|
43
|
+
run_before_hooks(params)
|
44
|
+
result = yield(params)
|
45
|
+
run_after_hooks(params)
|
46
|
+
end
|
35
47
|
result
|
36
48
|
end
|
37
49
|
|
38
50
|
private
|
39
51
|
|
52
|
+
def run_around_hooks(&block)
|
53
|
+
self.class.around_hooks.reverse.inject(block) do |chain, hook|
|
54
|
+
proc { run_hook(hook, chain) }
|
55
|
+
end.call
|
56
|
+
end
|
57
|
+
|
40
58
|
def run_before_hooks(params)
|
41
59
|
run_hooks(self.class.before_hooks, params)
|
42
60
|
end
|
@@ -52,6 +70,5 @@ module Tzu
|
|
52
70
|
def run_hook(hook, args)
|
53
71
|
hook.is_a?(Symbol) ? send(hook, args) : instance_exec(args, &hook)
|
54
72
|
end
|
55
|
-
|
56
73
|
end
|
57
74
|
end
|
data/lib/tzu/invalid.rb
CHANGED
data/lib/tzu/outcome.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Tzu
|
2
|
+
module RunMethods
|
3
|
+
attr_reader :request_klass
|
4
|
+
|
5
|
+
def run(params, *context, &block)
|
6
|
+
result = get_instance(*context).run(params)
|
7
|
+
return result.handle(&block) if block
|
8
|
+
result
|
9
|
+
end
|
10
|
+
|
11
|
+
def run!(params, *context)
|
12
|
+
get_instance(*context).run!(params)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_instance(*context)
|
16
|
+
method = respond_to?(:build) ? :build : :new
|
17
|
+
send(method, *context)
|
18
|
+
end
|
19
|
+
|
20
|
+
def command_name(value = nil)
|
21
|
+
if value.nil?
|
22
|
+
@name ||= name.underscore.to_sym
|
23
|
+
else
|
24
|
+
@name = (value.presence && value.to_sym)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(method, *args, &block)
|
29
|
+
@request_klass = args.first if method == :request_object
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/tzu/sequence.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module Tzu
|
2
|
+
module Sequence
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
include Hooks
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :steps, :result_block
|
9
|
+
|
10
|
+
def run(params)
|
11
|
+
new(params).run
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing(method, *args, &block)
|
15
|
+
return add_step(args.first, &block) if method == :step
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
@type ||= :take_last
|
21
|
+
end
|
22
|
+
|
23
|
+
def result(type = nil, &block)
|
24
|
+
return @result_block = block if block
|
25
|
+
@type = type
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_step(klass, &block)
|
29
|
+
@steps = [] unless @steps
|
30
|
+
|
31
|
+
step = Step.new(klass)
|
32
|
+
step.instance_eval(&block)
|
33
|
+
|
34
|
+
@steps << step
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(params)
|
39
|
+
@params = params
|
40
|
+
@last_outcome = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def run
|
44
|
+
results = sequence_results
|
45
|
+
return mutated_result(results) if self.class.result_block
|
46
|
+
return @last_outcome if self.class.type == :take_last
|
47
|
+
Outcome.new(true, results)
|
48
|
+
end
|
49
|
+
|
50
|
+
def sequence_results
|
51
|
+
with_hooks(@params) do |params|
|
52
|
+
self.class.steps.reduce({}) do |prior_results, step|
|
53
|
+
@last_outcome = step.run(params, prior_results)
|
54
|
+
break if @last_outcome.failure?
|
55
|
+
prior_results.merge!(step.name => @last_outcome.result)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def mutated_result(results)
|
63
|
+
Outcome.new(true, instance_exec(@params, results, &self.class.result_block))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/tzu/step.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Tzu
|
2
|
+
class Step
|
3
|
+
String.send(:include, ::Tzu::CoreExtensions::String)
|
4
|
+
attr_reader :klass, :param_mutator
|
5
|
+
|
6
|
+
def initialize(klass)
|
7
|
+
@klass = klass
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(params, prior_results)
|
11
|
+
command_params = process(params, prior_results)
|
12
|
+
@klass.run(command_params)
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
return @name if @name && @name.is_a?(Symbol)
|
17
|
+
@klass.to_s.symbolize
|
18
|
+
end
|
19
|
+
|
20
|
+
def receives(&block)
|
21
|
+
@param_mutator = block
|
22
|
+
end
|
23
|
+
|
24
|
+
def as(name)
|
25
|
+
@name = name
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def process(params, prior_results)
|
31
|
+
instance_exec(params, prior_results, &@param_mutator)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/tzu/validation.rb
CHANGED
@@ -15,20 +15,8 @@ module Tzu
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def validate(params)
|
18
|
-
|
19
|
-
|
20
|
-
result = valid?(params)
|
21
|
-
errors = self.respond_to?(:errors) ? self.errors : nil
|
22
|
-
return !!result == result ? ValidationResult.new(result, errors) : result
|
23
|
-
end
|
24
|
-
|
25
|
-
# do the params define their own validation method (cf. ActiveRecord)
|
26
|
-
if params.respond_to?(:valid?)
|
27
|
-
return ValidationResult.new(params.valid?, params.errors)
|
28
|
-
end
|
29
|
-
|
30
|
-
# otherwise valid
|
31
|
-
return ValidationResult.new(true)
|
18
|
+
return ValidationResult.new(params.valid?, params.errors) if params.respond_to?(:valid?)
|
19
|
+
ValidationResult.new(true)
|
32
20
|
end
|
33
21
|
|
34
22
|
def invalid!(obj)
|
@@ -39,5 +27,8 @@ module Tzu
|
|
39
27
|
raise Invalid.new(output)
|
40
28
|
end
|
41
29
|
|
30
|
+
def fail!(type, data = {})
|
31
|
+
raise Failure.new(type, data)
|
32
|
+
end
|
42
33
|
end
|
43
34
|
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class SayMyName
|
4
|
+
include Tzu
|
5
|
+
|
6
|
+
def call(params)
|
7
|
+
"Hello, #{params[:name]}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class MakeMeSoundImportant
|
12
|
+
include Tzu
|
13
|
+
|
14
|
+
def call(params)
|
15
|
+
"#{params[:boring_message]}! You are the most important citizen of #{params[:country]}!"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class InvalidCommand
|
20
|
+
include Tzu
|
21
|
+
|
22
|
+
def call(params)
|
23
|
+
invalid!('Who am I? Why am I here?')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class MultiStepSimple
|
28
|
+
include Tzu::Sequence
|
29
|
+
|
30
|
+
step SayMyName do
|
31
|
+
receives do |params|
|
32
|
+
{ name: params[:name] }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
step MakeMeSoundImportant do
|
37
|
+
receives do |params, prior_results|
|
38
|
+
{
|
39
|
+
boring_message: prior_results[:say_my_name],
|
40
|
+
country: params[:country]
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class MultiStepComplex
|
47
|
+
include Tzu::Sequence
|
48
|
+
|
49
|
+
step SayMyName do
|
50
|
+
as :first_command
|
51
|
+
receives do |params|
|
52
|
+
{ name: params[:name] }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
step MakeMeSoundImportant do
|
57
|
+
as :final_command
|
58
|
+
receives do |params, prior_results|
|
59
|
+
{
|
60
|
+
boring_message: prior_results[:first_command],
|
61
|
+
country: params[:country]
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
result :take_all
|
67
|
+
end
|
68
|
+
|
69
|
+
class MultiStepProcessResults
|
70
|
+
include Tzu::Sequence
|
71
|
+
|
72
|
+
step SayMyName do
|
73
|
+
as :first_command
|
74
|
+
receives do |params|
|
75
|
+
{ name: params[:name] }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
step MakeMeSoundImportant do
|
80
|
+
as :final_command
|
81
|
+
receives do |params, prior_results|
|
82
|
+
{
|
83
|
+
boring_message: prior_results[:first_command],
|
84
|
+
country: params[:country]
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
result do |params, prior_results|
|
90
|
+
{
|
91
|
+
status: :important,
|
92
|
+
message: "BULLETIN: #{prior_results[:final_command]}"
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class MultiStepInvalid
|
98
|
+
include Tzu::Sequence
|
99
|
+
|
100
|
+
step SayMyName do
|
101
|
+
receives do |params|
|
102
|
+
{ name: params[:name] }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
step InvalidCommand do
|
107
|
+
receives do |params, prior_results|
|
108
|
+
{ answer: "#{params[:name]}!!! #{prior_results[:say_my_name]}" }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
step MakeMeSoundImportant do
|
113
|
+
receives do |params, prior_results|
|
114
|
+
{
|
115
|
+
boring_message: prior_results[:say_my_name],
|
116
|
+
country: params[:country]
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe Tzu::Sequence do
|
123
|
+
context '#steps' do
|
124
|
+
context MultiStepSimple do
|
125
|
+
let(:steps) { MultiStepSimple.steps }
|
126
|
+
|
127
|
+
it 'returns array of Steps' do
|
128
|
+
steps.each { |step| expect(step.is_a? Tzu::Step).to be true }
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'passes the appropriate klass, name, and param_mutator to each step' do
|
132
|
+
say_my_name = steps.first
|
133
|
+
expect(say_my_name.klass).to eq SayMyName
|
134
|
+
expect(say_my_name.name).to eq :say_my_name
|
135
|
+
expect(say_my_name.param_mutator.is_a? Proc).to be true
|
136
|
+
|
137
|
+
make_me_sound_important = steps.last
|
138
|
+
expect(make_me_sound_important.klass).to eq MakeMeSoundImportant
|
139
|
+
expect(make_me_sound_important.name).to eq :make_me_sound_important
|
140
|
+
expect(make_me_sound_important.param_mutator.is_a? Proc).to be true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context MultiStepComplex do
|
145
|
+
let(:steps) { MultiStepComplex.steps }
|
146
|
+
|
147
|
+
it 'returns array of Steps' do
|
148
|
+
steps.each { |step| expect(step.is_a? Tzu::Step).to be true }
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'passes the appropriate klass, name, and param_mutator to each step' do
|
152
|
+
say_my_name = steps.first
|
153
|
+
expect(say_my_name.klass).to eq SayMyName
|
154
|
+
expect(say_my_name.name).to eq :first_command
|
155
|
+
expect(say_my_name.param_mutator.is_a? Proc).to be true
|
156
|
+
|
157
|
+
make_me_sound_important = steps.last
|
158
|
+
expect(make_me_sound_important.klass).to eq MakeMeSoundImportant
|
159
|
+
expect(make_me_sound_important.name).to eq :final_command
|
160
|
+
expect(make_me_sound_important.param_mutator.is_a? Proc).to be true
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context '#run' do
|
166
|
+
let(:params) do
|
167
|
+
{
|
168
|
+
name: 'Jessica',
|
169
|
+
country: 'Azerbaijan'
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
context MultiStepSimple do
|
174
|
+
it 'returns the outcome of the last command' do
|
175
|
+
outcome = MultiStepSimple.run(params)
|
176
|
+
expect(outcome.result).to eq 'Hello, Jessica! You are the most important citizen of Azerbaijan!'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context MultiStepComplex do
|
181
|
+
it 'returns the outcome of the last command' do
|
182
|
+
outcome = MultiStepComplex.run(params)
|
183
|
+
results = outcome.result
|
184
|
+
expect(results[:first_command]).to eq 'Hello, Jessica'
|
185
|
+
expect(results[:final_command]).to eq 'Hello, Jessica! You are the most important citizen of Azerbaijan!'
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context MultiStepProcessResults do
|
190
|
+
it 'returns the outcome of the last command' do
|
191
|
+
outcome = MultiStepProcessResults.run(params)
|
192
|
+
result = outcome.result
|
193
|
+
expect(result[:status]).to eq :important
|
194
|
+
expect(result[:message]).to eq 'BULLETIN: Hello, Jessica! You are the most important citizen of Azerbaijan!'
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context MultiStepInvalid do
|
199
|
+
it 'stops its execution at the invalid command, which it returns' do
|
200
|
+
outcome = MultiStepInvalid.run(params)
|
201
|
+
expect(outcome.success?).to be false
|
202
|
+
expect(outcome.result).to eq(errors: 'Who am I? Why am I here?')
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|