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.
@@ -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