usecasing 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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +156 -0
- data/Rakefile +6 -0
- data/lib/usecasing/base.rb +82 -0
- data/lib/usecasing/context.rb +48 -0
- data/lib/usecasing/version.rb +3 -0
- data/lib/usecasing.rb +6 -0
- data/spec/context_spec.rb +59 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/usecase_base_spec.rb +268 -0
- data/usecasing.gemspec +26 -0
- metadata +117 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Thiago Dantas
|
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,156 @@
|
|
1
|
+
# UseCase your code
|
2
|
+
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
|
6
|
+
[](http://travis-ci.org/tdantas/usecasing)
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
I did not (YET) upload this code to the rubygems.
|
11
|
+
To early adopters use the :git path
|
12
|
+
|
13
|
+
gem 'usecasing', :git => 'https://github.com/tdantas/usecasing.git'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
### Usage
|
20
|
+
|
21
|
+
Let's build a Invoice System, right ?
|
22
|
+
So the product owner will create some usecases/stories to YOU.
|
23
|
+
|
24
|
+
Imagine this usecase/story:
|
25
|
+
|
26
|
+
````
|
27
|
+
As a user I want to finalize an Invoice and an email should be delivered to the customer.
|
28
|
+
````
|
29
|
+
|
30
|
+
Let's build a controller
|
31
|
+
|
32
|
+
````
|
33
|
+
class InvoicesController < ApplicationController
|
34
|
+
|
35
|
+
def finalize
|
36
|
+
|
37
|
+
params[:current_user] = current_user
|
38
|
+
# params = { invoice_id: 123 , current_user: #<User:007> }
|
39
|
+
context = FinalizeInvoiceUseCase.perform(params)
|
40
|
+
|
41
|
+
if context.success?
|
42
|
+
redirect_to invoices_path(context.invoice)
|
43
|
+
else
|
44
|
+
@errors = context.errors
|
45
|
+
redirect_to invoices_path
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
````
|
52
|
+
|
53
|
+
Ok, What is FinalizeInvoiceUseCase ?
|
54
|
+
|
55
|
+
FinalizeInvoiceUseCase will be responsible for perform the Use Case/Story.
|
56
|
+
Each usecase should satisfy the [Single Responsability Principle](http://en.wikipedia.org/wiki/Single_responsibility_principle) and to achieve this principle, one usecase depends of others usecases building a Chain of Resposability.
|
57
|
+
|
58
|
+
|
59
|
+
````
|
60
|
+
|
61
|
+
class FinalizeInvoiceUseCase < UseCase::Base
|
62
|
+
depends FindInvoice, ValidateToFinalize, FinalizeInvoice, SendEmail
|
63
|
+
end
|
64
|
+
|
65
|
+
````
|
66
|
+
|
67
|
+
IMHO, when I read this Chain I really know what this class will do.
|
68
|
+
astute readers will ask: How FindInvoice pass values to ValidateToFinalize ?
|
69
|
+
|
70
|
+
When we call in the Controller *FinalizeInvoiceUseCase.perform* we pass a parameter (Hash) to the usecase.
|
71
|
+
|
72
|
+
This is what we call context, the usecase context will be shared between all chain.
|
73
|
+
|
74
|
+
````
|
75
|
+
class FindInvoice < UseCase::Base
|
76
|
+
|
77
|
+
def perform
|
78
|
+
|
79
|
+
# available because it was added in the controller context
|
80
|
+
user = context.current_user
|
81
|
+
|
82
|
+
# we could do that in one before_filter
|
83
|
+
invoice = user.invoices.find(context.invoice_id)
|
84
|
+
|
85
|
+
# asign to the context make available to all chain
|
86
|
+
context.invoice = invoice
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
````
|
92
|
+
|
93
|
+
Is the invoice valid to be finalized ?
|
94
|
+
|
95
|
+
````
|
96
|
+
class ValidateToFinalize < UseCase::Base
|
97
|
+
|
98
|
+
def perform
|
99
|
+
#failure will stop the chain flow and mark the context as error.
|
100
|
+
|
101
|
+
failure(:validate, "#{context.invoice.id} not ready to be finalized") unless valid?
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def valid?
|
106
|
+
#contextual validation to finalize an invoice
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
````
|
111
|
+
|
112
|
+
So, after validate, we already know that the invoice exists and it is ready to be finalized.
|
113
|
+
|
114
|
+
````
|
115
|
+
class FinalizeInvoice < UseCase::Base
|
116
|
+
|
117
|
+
def perform
|
118
|
+
invoice = context.invoice
|
119
|
+
invoice.finalize! #update database with finalize state
|
120
|
+
context.customer = invoice.customer
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
````
|
125
|
+
|
126
|
+
Oww, yeah, let's notify the customer
|
127
|
+
|
128
|
+
````
|
129
|
+
class SendEmail < UseCase::Base
|
130
|
+
|
131
|
+
def perform
|
132
|
+
to = context.customer.email
|
133
|
+
|
134
|
+
# Call whatever service
|
135
|
+
EmailService.send('customer_invoice_template', to, locals: { invoice: context.invoice } )
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
````
|
140
|
+
|
141
|
+
|
142
|
+
Let me know what do you think about it.
|
143
|
+
|
144
|
+
#### TODO
|
145
|
+
|
146
|
+
Create real case examples (40%)
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
## Contributing
|
151
|
+
|
152
|
+
1. Fork it
|
153
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
154
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
155
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
156
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
module UseCase
|
2
|
+
|
3
|
+
module BaseClassMethod
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def depends(*deps)
|
12
|
+
@dependencies ||= []
|
13
|
+
@dependencies.push(*deps)
|
14
|
+
end
|
15
|
+
|
16
|
+
def dependencies
|
17
|
+
return [] unless superclass.ancestors.include? UseCase::Base
|
18
|
+
value = (@dependencies && @dependencies.dup || []).concat(superclass.dependencies)
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform(ctx = { })
|
23
|
+
execution_order = build_execution_order(self, {})
|
24
|
+
tx(execution_order, ctx) do |usecase, context|
|
25
|
+
usecase.new(context).perform
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def tx(execution_order, context)
|
32
|
+
ctx = Context.new(context)
|
33
|
+
executed = []
|
34
|
+
execution_order.each do |usecase|
|
35
|
+
break unless ctx.success?
|
36
|
+
executed.push(usecase)
|
37
|
+
yield usecase, ctx
|
38
|
+
end
|
39
|
+
rollback(executed, ctx) unless ctx.success?
|
40
|
+
ctx
|
41
|
+
end
|
42
|
+
|
43
|
+
def rollback(execution_order, context)
|
44
|
+
execution_order.each do |usecase|
|
45
|
+
usecase.new(context).rollback
|
46
|
+
end
|
47
|
+
context
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_execution_order(start_point, visited)
|
51
|
+
raise StandardError.new("cyclic detected: #{start_point} in #{self}") if visited[start_point]
|
52
|
+
visited[start_point] = true
|
53
|
+
return [start_point] if start_point.dependencies.empty?
|
54
|
+
|
55
|
+
childrens = start_point.dependencies.each do |point|
|
56
|
+
build_execution_order(point, visited).unshift point
|
57
|
+
end
|
58
|
+
childrens.push(start_point)
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
class Base
|
66
|
+
|
67
|
+
include BaseClassMethod
|
68
|
+
|
69
|
+
attr_reader :context
|
70
|
+
def initialize(context)
|
71
|
+
@context = context
|
72
|
+
end
|
73
|
+
|
74
|
+
def perform; end
|
75
|
+
def rollback; end
|
76
|
+
|
77
|
+
def failure(key, value)
|
78
|
+
@context.failure(key, value)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module UseCase
|
2
|
+
|
3
|
+
class Context
|
4
|
+
|
5
|
+
attr_reader :errors
|
6
|
+
|
7
|
+
def initialize(hash = {})
|
8
|
+
raise ArgumentError.new('Must be a Hash') unless hash.is_a? ::Hash
|
9
|
+
@values = symbolyze_keys(hash)
|
10
|
+
@errors = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(method, *args, &block)
|
14
|
+
return @values[extract_key_from(method)] = args.first if setter? method
|
15
|
+
@values[method]
|
16
|
+
end
|
17
|
+
|
18
|
+
def respond_to?(method)
|
19
|
+
@values.keys.include?(method.to_sym)
|
20
|
+
end
|
21
|
+
|
22
|
+
def success?
|
23
|
+
@errors.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def failure(key, value)
|
27
|
+
@errors.push({ key => value })
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def setter?(method)
|
32
|
+
!! ((method.to_s) =~ /=$/)
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_key_from(method)
|
36
|
+
method.to_s[0..-2].to_sym
|
37
|
+
end
|
38
|
+
|
39
|
+
def symbolyze_keys(hash)
|
40
|
+
hash.keys.reduce({ }) do |acc, key|
|
41
|
+
acc[key.to_sym] = hash[key]
|
42
|
+
acc
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/lib/usecasing.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe UseCase::Context do
|
4
|
+
|
5
|
+
|
6
|
+
it 'receives a hash and generate setters from key' do
|
7
|
+
hash = {name: 'thiago', last: 'dantas', github: 'tdantas'}
|
8
|
+
context = described_class.new(hash)
|
9
|
+
expect(context.name).to eql(hash[:name])
|
10
|
+
expect(context.last).to eql(hash[:last])
|
11
|
+
expect(context.github).to eql(hash[:github])
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'initializes without parameters' do
|
15
|
+
expect(described_class.new).to be_an(described_class)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'raises exception when argument is not a hash' do
|
19
|
+
expect {described_class.new(Object.new)}.to raise_error(ArgumentError)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'assign new values' do
|
23
|
+
context = described_class.new
|
24
|
+
context.dog_name = 'mali'
|
25
|
+
context.country = 'lisbon'
|
26
|
+
context.age = 1
|
27
|
+
|
28
|
+
expect(context.dog_name).to eql('mali')
|
29
|
+
expect(context.country).to eql('lisbon')
|
30
|
+
expect(context.age).to eql(1)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'handle hash with indifference' do
|
34
|
+
hash = { "name" => 'thiago', last: 'dantas'}
|
35
|
+
context = described_class.new(hash)
|
36
|
+
expect(context.name).to eql('thiago')
|
37
|
+
expect(context.last).to eql('dantas')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'is success when there is no error' do
|
41
|
+
context = described_class.new({})
|
42
|
+
expect(context.success?).to eql(true)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'adds error messages to errors' do
|
46
|
+
context = described_class.new({})
|
47
|
+
context.failure(:email, 'email already exist')
|
48
|
+
expect(context.errors.length).to eql(1)
|
49
|
+
expect(context.errors.first).to eql({ email: 'email already exist' })
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'fails when exist errors' do
|
53
|
+
context = described_class.new({})
|
54
|
+
context.failure(:email, 'email already exist')
|
55
|
+
expect(context.success?).to eql(false)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe UseCase::Base do
|
4
|
+
|
5
|
+
context "depends" do
|
6
|
+
|
7
|
+
it 'initialize without any dependency' do
|
8
|
+
AppUseCaseInitialize = Class.new(UseCase::Base)
|
9
|
+
expect(AppUseCaseInitialize.dependencies).to be_empty
|
10
|
+
end
|
11
|
+
|
12
|
+
it "adds usecase dependency" do
|
13
|
+
AppOtherUseCase = Class.new
|
14
|
+
AppUseCase = Class.new(UseCase::Base) do
|
15
|
+
depends AppOtherUseCase
|
16
|
+
end
|
17
|
+
|
18
|
+
expect(AppUseCase.dependencies).to eql([AppOtherUseCase])
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
it 'subclass adds dependency from subclass to superclass' do
|
23
|
+
|
24
|
+
SuperClassDependency = Class.new(UseCase::Base)
|
25
|
+
UseCaseSuperClass = Class.new(UseCase::Base) do
|
26
|
+
depends SuperClassDependency
|
27
|
+
end
|
28
|
+
|
29
|
+
SubClassDependency = Class.new(UseCase::Base)
|
30
|
+
UseCaseSubClass = Class.new(UseCaseSuperClass) do
|
31
|
+
depends SubClassDependency
|
32
|
+
end
|
33
|
+
|
34
|
+
expect(UseCaseSubClass.dependencies).to eql([SubClassDependency, SuperClassDependency])
|
35
|
+
#idempotent operation
|
36
|
+
expect(UseCaseSubClass.dependencies).to eql([SubClassDependency, SuperClassDependency])
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
context '##perform' do
|
45
|
+
|
46
|
+
it 'call instance #perform method' do
|
47
|
+
AppUseCaseInstance = Class.new(UseCase::Base) do
|
48
|
+
def perform
|
49
|
+
#some business rule here
|
50
|
+
end
|
51
|
+
end
|
52
|
+
AppUseCaseInstance.any_instance.expects(:perform).once
|
53
|
+
AppUseCaseInstance.perform
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'receives receives a hash and create a execution context' do
|
57
|
+
|
58
|
+
SendEmailUseCase = Class.new(UseCase::Base) do
|
59
|
+
def perform
|
60
|
+
context.sent = 'sent'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
ctx = SendEmailUseCase.perform({email: 'thiago.teixeira.dantas@gmail.com' })
|
65
|
+
expect(ctx.sent).to eql('sent')
|
66
|
+
expect(ctx.email).to eql('thiago.teixeira.dantas@gmail.com')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'must receive an hash' do
|
70
|
+
UseCaseArgumentException = Class.new(UseCase::Base)
|
71
|
+
expect{ UseCaseArgumentException.perform(Object.new) }.to raise_error(ArgumentError)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'with success when usecase do not register failure' do
|
75
|
+
pending
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'fail when usecase register failure' do
|
79
|
+
pending
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
it 'detects cyclic' do
|
84
|
+
|
85
|
+
CyclicFirst = Class.new(UseCase::Base)
|
86
|
+
CyclicSecond = Class.new(UseCase::Base) do
|
87
|
+
depends CyclicFirst
|
88
|
+
end
|
89
|
+
|
90
|
+
CyclicFirst.instance_eval do
|
91
|
+
depends CyclicSecond
|
92
|
+
end
|
93
|
+
|
94
|
+
FinalUseCase = Class.new(UseCase::Base) do
|
95
|
+
depends CyclicSecond
|
96
|
+
end
|
97
|
+
|
98
|
+
expect { FinalUseCase.perform }.to raise_error(StandardError, /cyclic detected/)
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
context '##perfoms execution chain' do
|
105
|
+
it 'executes in lexical order cascading context among usecases' do
|
106
|
+
|
107
|
+
FirstUseCase = Class.new(UseCase::Base) do
|
108
|
+
def perform
|
109
|
+
context.first = context.first_arg
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
SecondUseCase = Class.new(UseCase::Base) do
|
114
|
+
def perform
|
115
|
+
context.second = context.second_arg
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
UseCaseChain = Class.new(UseCase::Base) do
|
120
|
+
depends FirstUseCase, SecondUseCase
|
121
|
+
end
|
122
|
+
|
123
|
+
ctx = UseCaseChain.perform({:first_arg => 1, :second_arg => 2})
|
124
|
+
expect(ctx.first).to eql(1)
|
125
|
+
expect(ctx.second).to eql(2)
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
it 'stops the flow when failure happen' do
|
130
|
+
|
131
|
+
FirstUseCaseFailure = Class.new(UseCase::Base) do
|
132
|
+
def perform
|
133
|
+
context.first = context.first_arg
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
SecondUseCaseFailure = Class.new(UseCase::Base) do
|
138
|
+
|
139
|
+
def perform
|
140
|
+
context.second = context.second_arg
|
141
|
+
failure(:second, 'next will not be called')
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
ThirdUseCaseFailure = Class.new(UseCase::Base) do
|
147
|
+
def perform
|
148
|
+
context.third = true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
UseCaseFailure = Class.new(UseCase::Base) do
|
153
|
+
depends FirstUseCaseFailure, SecondUseCaseFailure, ThirdUseCaseFailure
|
154
|
+
end
|
155
|
+
|
156
|
+
ThirdUseCaseFailure.any_instance.expects(:perform).never
|
157
|
+
UseCaseFailure.perform
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context '#perform' do
|
163
|
+
it 'receive an Context instance' do
|
164
|
+
InstanceUseCase = Class.new(UseCase::Base) do
|
165
|
+
def perform
|
166
|
+
context.executed = true
|
167
|
+
end
|
168
|
+
end
|
169
|
+
ctx = UseCase::Context.new
|
170
|
+
InstanceUseCase.new(ctx).perform
|
171
|
+
expect(ctx.executed).to be_true
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
context 'rolling back the flow' do
|
177
|
+
|
178
|
+
it 'rollback without dependencies' do
|
179
|
+
|
180
|
+
UseCaseWithRollback = Class.new(UseCase::Base) do
|
181
|
+
|
182
|
+
def perform
|
183
|
+
failure(:rollback, 'must be called')
|
184
|
+
end
|
185
|
+
|
186
|
+
def rollback
|
187
|
+
context.rollback_executed = 'true'
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
context = UseCaseWithRollback.perform
|
193
|
+
expect(context.rollback_executed).to eql('true')
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'in reverse order of execution' do
|
198
|
+
order = 0
|
199
|
+
|
200
|
+
UseCaseWithRollbackOrderDepdens = Class.new(UseCase::Base) do
|
201
|
+
define_method :rollback do
|
202
|
+
order += 1
|
203
|
+
context.first_rollback = order
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
UseCaseWithRollbackOrder = Class.new(UseCase::Base) do
|
209
|
+
depends UseCaseWithRollbackOrderDepdens
|
210
|
+
|
211
|
+
def perform
|
212
|
+
failure(:rollback, 'error')
|
213
|
+
end
|
214
|
+
|
215
|
+
define_method :rollback do
|
216
|
+
order += 1
|
217
|
+
context.second_rollback = order
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context = UseCaseWithRollbackOrder.perform
|
222
|
+
expect(context.first_rollback).to eql(1)
|
223
|
+
expect(context.second_rollback).to eql(2)
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
it 'only rollbacks usecase that ran' do
|
228
|
+
|
229
|
+
UseCaseFailRanThird = Class.new(UseCase::Base) do
|
230
|
+
|
231
|
+
def rollback
|
232
|
+
context.should_not_appear = 'true'
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
|
237
|
+
UseCaseFailRanSecond = Class.new(UseCase::Base) do
|
238
|
+
|
239
|
+
def perform
|
240
|
+
failure(:rollback, 'error')
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
def rollback
|
245
|
+
context.rollback_second 'true_2'
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
UseCaseFailRanFirst = Class.new(UseCase::Base) do
|
251
|
+
|
252
|
+
def rollback
|
253
|
+
context.rollback_first = 'true_1'
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
UseCaseFailRan = Class.new(UseCase::Base) do
|
259
|
+
depends UseCaseFailRanFirst, UseCaseFailRanSecond, UseCaseFailRanThird
|
260
|
+
end
|
261
|
+
|
262
|
+
context = UseCaseFailRan.perform
|
263
|
+
expect(context.should_not_appear).to be_nil
|
264
|
+
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
data/usecasing.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'usecasing/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "usecasing"
|
8
|
+
gem.version = Usecasing::VERSION
|
9
|
+
gem.authors = ["Thiago Dantas"]
|
10
|
+
gem.email = ["thiago.teixeira.dantas@gmail.com"]
|
11
|
+
gem.description = %q{UseCase Driven approach to your code}
|
12
|
+
gem.summary = %q{UseCase Driven Approach}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
|
21
|
+
#development dependecy
|
22
|
+
gem.add_development_dependency "rspec"
|
23
|
+
gem.add_development_dependency "rake"
|
24
|
+
gem.add_development_dependency "mocha"
|
25
|
+
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: usecasing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Thiago Dantas
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-12-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mocha
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: UseCase Driven approach to your code
|
63
|
+
email:
|
64
|
+
- thiago.teixeira.dantas@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .rspec
|
71
|
+
- .travis.yml
|
72
|
+
- Gemfile
|
73
|
+
- LICENSE.txt
|
74
|
+
- README.md
|
75
|
+
- Rakefile
|
76
|
+
- lib/usecasing.rb
|
77
|
+
- lib/usecasing/base.rb
|
78
|
+
- lib/usecasing/context.rb
|
79
|
+
- lib/usecasing/version.rb
|
80
|
+
- spec/context_spec.rb
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
- spec/usecase_base_spec.rb
|
83
|
+
- usecasing.gemspec
|
84
|
+
homepage: ''
|
85
|
+
licenses: []
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
hash: 2175319097785181606
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
hash: 2175319097785181606
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 1.8.24
|
111
|
+
signing_key:
|
112
|
+
specification_version: 3
|
113
|
+
summary: UseCase Driven Approach
|
114
|
+
test_files:
|
115
|
+
- spec/context_spec.rb
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
- spec/usecase_base_spec.rb
|