tzu 0.0.2.0 → 0.1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
- run_before_hooks(params)
33
- result = block.call(params)
34
- run_after_hooks(params)
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
@@ -1,9 +1,7 @@
1
1
  module Tzu
2
2
  class Invalid < Failure
3
-
4
3
  def initialize(errors = nil)
5
4
  super(:validation, errors)
6
5
  end
7
-
8
6
  end
9
7
  end
@@ -1,5 +1,4 @@
1
1
  module Tzu
2
-
3
2
  # The result of executing a command
4
3
  class Outcome
5
4
  attr_reader :success, :result, :type
@@ -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
@@ -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
@@ -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
@@ -15,20 +15,8 @@ module Tzu
15
15
  end
16
16
 
17
17
  def validate(params)
18
- # see if the command defines a valid? method
19
- if self.respond_to?(:valid?)
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