rest_my_case 1.10.9 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/README.md +330 -2
- data/lib/rest_my_case/base.rb +46 -21
- data/lib/rest_my_case/context/base.rb +11 -0
- data/lib/rest_my_case/context/schema_validator/base.rb +27 -0
- data/lib/rest_my_case/context/schema_validator/compel.rb +36 -0
- data/lib/rest_my_case/context/status.rb +2 -0
- data/lib/rest_my_case/defense_attorney/base.rb +1 -1
- data/lib/rest_my_case/http_status.rb +5 -8
- data/lib/rest_my_case/judge/base.rb +19 -14
- data/lib/rest_my_case/status.rb +13 -12
- data/lib/rest_my_case/trial/case.rb +3 -3
- data/lib/rest_my_case/validator.rb +3 -8
- data/lib/rest_my_case/version.rb +1 -1
- data/lib/rest_my_case.rb +0 -4
- data/rest_my_case.gemspec +1 -1
- data/spec/rest_my_case/accusation_attorneys/base_spec.rb +1 -1
- data/spec/rest_my_case/accusation_attorneys/each_spec.rb +1 -1
- data/spec/rest_my_case/accusation_attorneys/format_spec.rb +2 -2
- data/spec/rest_my_case/base/context_accessor_spec.rb +29 -0
- data/spec/rest_my_case/base/context_reader_spec.rb +21 -0
- data/spec/rest_my_case/base/context_writer_spec.rb +23 -0
- data/spec/rest_my_case/base/dependencies_spec.rb +14 -0
- data/spec/rest_my_case/base/invoke!_spec.rb +140 -0
- data/spec/rest_my_case/base/invoke_spec.rb +29 -0
- data/spec/rest_my_case/{base_spec.rb → base/perform_spec.rb} +0 -226
- data/spec/rest_my_case/base/required_context_spec.rb +33 -0
- data/spec/rest_my_case/context/status_spec.rb +2 -2
- data/spec/rest_my_case/status_spec.rb +6 -6
- data/spec/support/required_context.rb +49 -0
- metadata +42 -24
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
Yjk3MjkxNzg0MzU4ODA4OTVhYmFmMGM4Zjg0ZWYyZWUxY2RmZTQ5NQ==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a0f32849ac70e42eea144b0fc564245bd76afa3a
|
4
|
+
data.tar.gz: 78ed4d4e6833243cfce284be8ec5d22407084111
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
OThkMzI3Njg0MzFlYzc2OWI4OGZjOWYxZjM3MGUyOTBkYzgzZGU5MDY5YjI2
|
11
|
-
MTg1MTBmNjExZDY5M2MyNzVlZTRkMjVlNTQyY2U5MmY2NTk2Yjg=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
ODIxZmI2NTNmZWU4NWEwNzE3ODUxY2E2N2EyZjA2ODNlMGY3ODA1OWQ3Y2I0
|
14
|
-
MDNlZTExZjUzOGEyZTAyNjM4NDZhMGQ1ODgyNTYxN2JlYzNlODc2MGFhNDE2
|
15
|
-
MmIzMTRhYjM0YWEzZDg4M2UyODQ4ZjY1OWUxY2M3ODQ0MWYyMzQ=
|
6
|
+
metadata.gz: c3725d52e75e29c2b9b3724ac38522a95a45a48e282a1560e4ce39245094fbc6718911378f67810c3055aff84eef9a7f57da215a0a5e0da982641da4f67e0878
|
7
|
+
data.tar.gz: 470adc3bfef70af304db5be018f7f57b5adb2d10eed7a17b325110148feccad8887d97fe258743e11f825982029bae19d9e2df5f4811fc430d0342f01cfa8cd1
|
data/README.md
CHANGED
@@ -1,4 +1,332 @@
|
|
1
1
|
# RestMyCase [![Code Climate](https://codeclimate.com/github/goncalvesjoao/rest_my_case/badges/gpa.svg)](https://codeclimate.com/github/goncalvesjoao/rest_my_case) [![Build Status](https://travis-ci.org/goncalvesjoao/rest_my_case.svg?branch=master)](https://travis-ci.org/goncalvesjoao/rest_my_case) [![Test Coverage](https://codeclimate.com/github/goncalvesjoao/rest_my_case/badges/coverage.svg)](https://codeclimate.com/github/goncalvesjoao/rest_my_case) [![Gem Version](https://badge.fury.io/rb/rest_my_case.svg)](http://badge.fury.io/rb/rest_my_case)
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
Light Ruby gem with everything you need in a "The Clean Architecture" use case scenario.
|
4
|
+
|
5
|
+
Many thanks to [@tdantas](https://github.com/tdantas) and [@junhanamaki](https://github.com/junhanamaki) and a shout-out to [@joaquimadraz](https://github.com/joaquimadraz) and his [compel](https://github.com/joaquimadraz/compel) ruby validations gem.
|
6
|
+
|
7
|
+
---
|
8
|
+
|
9
|
+
## 1) Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'rest_my_case'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install rest_my_case
|
22
|
+
|
23
|
+
---
|
24
|
+
|
25
|
+
## 2) Ideology
|
26
|
+
Your business logic goes into separated use cases...
|
27
|
+
```ruby
|
28
|
+
class FindPost < RestMyCase::Base
|
29
|
+
|
30
|
+
def perform
|
31
|
+
context.post = Post.find(context.id)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class ArchivePost < RestMyCase::Base
|
37
|
+
|
38
|
+
depends FindPost
|
39
|
+
|
40
|
+
def perform
|
41
|
+
context.post.status = 'archived'
|
42
|
+
|
43
|
+
context.result = context.post.save
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
web framework should only act as a bridge between the user and your business logic.
|
50
|
+
```ruby
|
51
|
+
class PostsController < ApplicationController
|
52
|
+
|
53
|
+
def archive
|
54
|
+
@context = ArchivePost.perform id: params[:id]
|
55
|
+
|
56
|
+
if @context.result
|
57
|
+
redirect_to @context.post
|
58
|
+
else
|
59
|
+
render "archive" #view post.errors
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
Ideally your business logic should be a ruby gem that can be tested independently from your framework.
|
67
|
+
|
68
|
+
Checkout this step by step tutorial: (WIP) on how to isolate your code into a ruby gem and connect it to your rails or sinatra api.
|
69
|
+
|
70
|
+
---
|
71
|
+
|
72
|
+
## 3) Basic usage
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
class BuildPost < RestMyCase::Base
|
76
|
+
def perform
|
77
|
+
puts context.id
|
78
|
+
puts context.post_attributes
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
```
|
84
|
+
irb> params = { id: 1, post: { title: 'my first post' } }
|
85
|
+
irb> context = BuildPost.perform id: params[:id], post_attributes: params[:post]
|
86
|
+
1
|
87
|
+
{:title=>"my first post"}
|
88
|
+
irb> context.id
|
89
|
+
1
|
90
|
+
```
|
91
|
+
|
92
|
+
The Hash passed down to **BuildPost.perform** will be available through an instance method called **#context** that will return an OpenStruct object initialized with that Hash (see more in section 7).
|
93
|
+
|
94
|
+
Executing **BuildPost.perform** will instantiate your use case and all of its **dependencies**, build a **context** with the contents of **params**, run your use case (and its dependencies) with that context and return it at the end.
|
95
|
+
|
96
|
+
## 3.1) Normal usage
|
97
|
+
Organize your use cases by single responsibilities and establish your use case flow through "dependencies".
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
class FindPost < RestMyCase::Base
|
101
|
+
def perform
|
102
|
+
context.post = Post.find(context.id)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class BuildPost < RestMyCase::Base
|
107
|
+
depends FindPost
|
108
|
+
|
109
|
+
def perform
|
110
|
+
context.post.assign_attributes context.post_attributes
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
The class method **.depends** will make **BuildPost** dependent of **FindPost** which means that calling **BuildPost.perform** will run **FindPost#perform** first and **BuildPost#perform** last. Both use cases will share the same context.
|
115
|
+
|
116
|
+
```
|
117
|
+
irb> params = { id: 1, post: { title: 'my first post' } }
|
118
|
+
irb> context = BuildPost.perform id: params[:id], post_attributes: params[:post]
|
119
|
+
irb> context.post.name
|
120
|
+
"my first post"
|
121
|
+
```
|
122
|
+
|
123
|
+
---
|
124
|
+
|
125
|
+
## 4) Lifecycle
|
126
|
+
|
127
|
+
## 4.1) Waiting to be implemented methods
|
128
|
+
Methods: **#setup**, **#perform**, **#rollback** and **#final**
|
129
|
+
```ruby
|
130
|
+
class UseCase1 < RestMyCase::Base
|
131
|
+
def setup
|
132
|
+
puts 'UseCase1#setup'
|
133
|
+
end
|
134
|
+
def perform
|
135
|
+
puts 'UseCase1#perform'
|
136
|
+
error if context.should_fail
|
137
|
+
end
|
138
|
+
def rollback
|
139
|
+
puts 'UseCase1#rollback'
|
140
|
+
end
|
141
|
+
def final
|
142
|
+
puts 'UseCase1#final'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
```
|
148
|
+
irb> UseCase1.perform #will print
|
149
|
+
"UseCase1#setup"
|
150
|
+
"UseCase1#perform"
|
151
|
+
"UseCase1#final"
|
152
|
+
```
|
153
|
+
|
154
|
+
Method **#rollback** will be called after **#perform** and before **#final** if **#error** is invoked inside a **#setup** of **#perform** (see more in section 5).
|
155
|
+
```
|
156
|
+
irb> UseCase1.perform(should_fail: true) #will print
|
157
|
+
"UseCase1#setup"
|
158
|
+
"UseCase1#perform"
|
159
|
+
"UseCase1#rollback"
|
160
|
+
"UseCase1#final"
|
161
|
+
```
|
162
|
+
|
163
|
+
Method **#final** will run last and always, no matter how many times **#error** was called.
|
164
|
+
|
165
|
+
---
|
166
|
+
|
167
|
+
### 4.2) Dependencies
|
168
|
+
Default behaviour is to run your dependencies (**#setup**, **#perform**, **#rollback** and **#final**) methods, first.
|
169
|
+
```ruby
|
170
|
+
class UseCase2 < RestMyCase::Base
|
171
|
+
def perform
|
172
|
+
puts 'UseCase2#perform'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class UseCase3 < RestMyCase::Base
|
177
|
+
def perform
|
178
|
+
puts 'UseCase3#perform'
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class UseCase1 < RestMyCase::Base
|
183
|
+
depends UseCase2,
|
184
|
+
UseCase3
|
185
|
+
|
186
|
+
def perform
|
187
|
+
puts 'UseCase1#perform'
|
188
|
+
end
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
```
|
193
|
+
irb> UseCase1.perform #will print
|
194
|
+
"UseCase2#perform"
|
195
|
+
"UseCase3#perform"
|
196
|
+
"UseCase1#perform"
|
197
|
+
```
|
198
|
+
|
199
|
+
See section 8 for more examples.
|
200
|
+
|
201
|
+
---
|
202
|
+
|
203
|
+
## 5) Flow control methods
|
204
|
+
|
205
|
+
Methods | Behaviour
|
206
|
+
------- | ---------
|
207
|
+
**#abort** | Stops other remaining use cases from running and triggers **#rollback** on already executed use cases (in reverse order).
|
208
|
+
**#skip** | Will prevent **#perform** (of the use case that called **#skip**) from running and will not stop other use cases from running nor trigger a **#rollback** (only works by being used inside a **#setup** method).
|
209
|
+
**#error(error_message = '')** | Will do the same as **#abort** but will also push **error_message** to **#context.errors** array so you can track down what happen in what use case (see more in section 7).
|
210
|
+
**#invoke(*use_case_classes)** | Does the same as the class method **.depends** but executes the use cases on demand. Shares the context to them and if they call **#abort** on their side, the use case that **invoked** will also **abort**.
|
211
|
+
|
212
|
+
**#skip**, **#abort**, **#error** and **#invoke** have a "bang!" version that will raise a controlled exception, preventing the remaining lines of code from running.
|
213
|
+
```ruby
|
214
|
+
class UseCase1 < RestMyCase::Base
|
215
|
+
def perform
|
216
|
+
puts 'before #error!'
|
217
|
+
error!
|
218
|
+
puts 'after #error!'
|
219
|
+
end
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
```
|
224
|
+
irb> UseCase1.perform #will print only
|
225
|
+
"before #error!"
|
226
|
+
```
|
227
|
+
|
228
|
+
---
|
229
|
+
|
230
|
+
## 6) Configuration methods
|
231
|
+
|
232
|
+
Methods | Behaviour
|
233
|
+
------- | ---------
|
234
|
+
**.depends(*use_case_classes)** | Adds the **use_case_classes** array to the use case's **dependencies** list, that will be executed by order before the actual use case (see more in section 4).
|
235
|
+
**.required_context(*attributes)** | WIP.
|
236
|
+
**.context_reader(*methods)** | Defines getter methods that return **context.send method**, to help reduce the **context.method** boilerplate.
|
237
|
+
**.context_writer(*methods)** | Defines setter methods that set **context.send "#{method}=", value**, to help reduce the **context.method = value** boilerplate.
|
238
|
+
**.context_accessor(*methods)** | Calls both **.context_reader** and **.context_writer** methods.
|
239
|
+
**.silence_dependencies_abort=** | If **false** once a dependency calls **#abort(!)** the next in line dependencies will not be called (and **#rollback** will be called in reverse order) but if **true** all dependencies will run no matter how many times **#abort(!)** was called (usefull when you want to run multiple validations (see more in section 9).
|
240
|
+
|
241
|
+
---
|
242
|
+
|
243
|
+
## 7) **#context** methods
|
244
|
+
The returning object is an instance of **RestMyCase::Context::Base** class that inherits from **OpenStruct** and implements the following methods:
|
245
|
+
|
246
|
+
Methods | Behaviour
|
247
|
+
------- | ---------
|
248
|
+
**#attributes** | Alias to **#marshal_dump**, returns all of the context's stored data.
|
249
|
+
**#to_hash** | Serializes and unserializes **#attributes** turning any existing ruby objects into serialized strings.
|
250
|
+
**#valid?** | Checks if **#errors** is empty
|
251
|
+
**#ok?** | Alias to **#valid?**
|
252
|
+
**#success?** | Alias to **#ok?**
|
253
|
+
**#errors** | Array that gets 'pushed' with **{ message: error_message, class_name: UseCase.class.name }** (or **error_message** itself if **error_message** already a Hash) every time **UseCase#error(error_message)** is called.
|
254
|
+
|
255
|
+
If **defined?(ActiveModel)** is true, **ActiveModel::Serialization** will be included and in turn methods like **#to_json(options = {})** and **#serializable_hash(options = nil)** will become available.
|
256
|
+
|
257
|
+
---
|
258
|
+
|
259
|
+
### 8) Examples
|
260
|
+
If **UseCase1** depends on **UseCase2** and **UseCase3** in that respective order.
|
261
|
+
|
262
|
+
Running **UseCase1.perform** will pass down the context to each use case in the following manner:
|
263
|
+
|
264
|
+
#### 8.1) Given that no use case called the method(s) **#error(!)**
|
265
|
+
```
|
266
|
+
UseCase2#setup -> UseCase3#setup -> UseCase1#setup ->
|
267
|
+
UseCase2#perform -> UseCase3#perform -> UseCase1#perform ->
|
268
|
+
UseCase2#final -> UseCase3#final -> UseCase1#final
|
269
|
+
```
|
270
|
+
|
271
|
+
#### 8.2) Given that **UseCase3#setup** calls **#skip(!)**
|
272
|
+
```
|
273
|
+
UseCase2#setup -> UseCase3#setup -> UseCase1#setup ->
|
274
|
+
UseCase2#perform -> UseCase1#perform ->
|
275
|
+
UseCase2#final -> UseCase3#final -> UseCase1#final
|
276
|
+
```
|
277
|
+
|
278
|
+
#### 8.3) Given that **UseCase3#setup** calls **#error(!)**
|
279
|
+
```
|
280
|
+
UseCase2#setup -> UseCase3#setup ->
|
281
|
+
UseCase3#rollback -> UseCase2#rollback ->
|
282
|
+
UseCase2#final -> UseCase3#final -> UseCase1#final
|
283
|
+
```
|
284
|
+
|
285
|
+
#### 8.4) Given that **UseCase3#perform** calls **#error(!)**
|
286
|
+
```
|
287
|
+
UseCase2#setup -> UseCase3#setup -> UseCase1#setup ->
|
288
|
+
UseCase2#perform -> UseCase3#perform ->
|
289
|
+
UseCase3#rollback -> UseCase2#rollback ->
|
290
|
+
UseCase2#final -> UseCase3#final -> UseCase1#final
|
291
|
+
```
|
292
|
+
|
293
|
+
---
|
294
|
+
|
295
|
+
## 9) RestMyCase::Validator class
|
296
|
+
WIP
|
297
|
+
|
298
|
+
---
|
299
|
+
|
300
|
+
## 10) RestMyCase::Status module
|
301
|
+
```ruby
|
302
|
+
class UseCase1 < RestMyCase::Base
|
303
|
+
include RestMyCase::Status
|
304
|
+
end
|
305
|
+
```
|
306
|
+
|
307
|
+
Adds following methods:
|
308
|
+
|
309
|
+
Methods | Behaviour
|
310
|
+
------- | ---------
|
311
|
+
**#context** | Returns an instance of **RestMyCase::Context::Status**
|
312
|
+
**#status** | Returns **context.status** (see more in section 10.1)
|
313
|
+
**#failure(status, error_message = nil)** | WIP
|
314
|
+
|
315
|
+
**#failure!** is also present and does the same as the other flow control "bang!" methods (see section 5).
|
316
|
+
|
317
|
+
### 10.1) RestMyCase::Context::Status
|
318
|
+
WIP
|
319
|
+
|
320
|
+
---
|
321
|
+
|
322
|
+
## 11) RestMyCase::HttpStatus module (for seamless API integration)
|
323
|
+
```ruby
|
324
|
+
class UseCase1 < RestMyCase::Base
|
325
|
+
include RestMyCase::HttpStatus
|
326
|
+
end
|
327
|
+
```
|
328
|
+
|
329
|
+
Includes the module **RestMyCase::Status** and **#context** becomes an instance of **RestMyCase::Context::HttpStatus**.
|
330
|
+
|
331
|
+
### 11.1) RestMyCase::Context::HttpStatus
|
332
|
+
WIP
|
data/lib/rest_my_case/base.rb
CHANGED
@@ -9,6 +9,18 @@ module RestMyCase
|
|
9
9
|
Judge::Base, DefenseAttorney::Base, RestMyCase::Base, Context::Base
|
10
10
|
end
|
11
11
|
|
12
|
+
def self.required_context(*schema)
|
13
|
+
if schema.length == 1 && (schema[0].is_a?(Hash) || schema[0].is_a?(Array))
|
14
|
+
@required_context_schema = schema[0]
|
15
|
+
else
|
16
|
+
@required_context_schema = schema
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.required_context_schema
|
21
|
+
@required_context_schema ||= {}
|
22
|
+
end
|
23
|
+
|
12
24
|
def self.depends(*use_case_classes)
|
13
25
|
dependencies.push(*use_case_classes)
|
14
26
|
end
|
@@ -44,42 +56,56 @@ module RestMyCase
|
|
44
56
|
|
45
57
|
######################## INSTANCE METHODS BELLOW ###########################
|
46
58
|
|
47
|
-
attr_reader :context, :
|
59
|
+
attr_reader :context, :options
|
48
60
|
|
49
61
|
def initialize(context, dependent_use_case = nil)
|
50
|
-
@
|
51
|
-
@
|
52
|
-
|
62
|
+
@context = context
|
63
|
+
@options = { dependent_use_case: dependent_use_case }
|
64
|
+
|
65
|
+
return unless dependent_use_case
|
66
|
+
|
67
|
+
@options[:silent_abort] = RestMyCase.get_config \
|
68
|
+
:silence_dependencies_abort, dependent_use_case.class
|
53
69
|
end
|
54
70
|
|
55
|
-
def setup;
|
71
|
+
def setup; end
|
56
72
|
|
57
|
-
def perform;
|
73
|
+
def perform; end
|
58
74
|
|
59
75
|
def rollback; end
|
60
76
|
|
61
77
|
def final; end
|
62
78
|
|
63
79
|
def invoke(*use_case_classes)
|
64
|
-
trial_court.execute(use_case_classes, context.to_hash).context
|
80
|
+
self.class.trial_court.execute(use_case_classes, context.to_hash).context
|
65
81
|
end
|
66
82
|
|
67
83
|
def invoke!(*use_case_classes)
|
68
|
-
trial_court.execute(use_case_classes, context)
|
69
|
-
|
70
|
-
|
84
|
+
trial_case = self.class.trial_court.execute(use_case_classes, context)
|
85
|
+
|
86
|
+
abort! if trial_case.aborted
|
87
|
+
|
88
|
+
trial_case.context
|
71
89
|
end
|
72
90
|
|
73
91
|
def abort
|
74
|
-
|
92
|
+
if options[:silent_abort]
|
93
|
+
options[:dependent_use_case].abort
|
94
|
+
else
|
95
|
+
options[:should_abort] = true
|
96
|
+
end
|
75
97
|
end
|
76
98
|
|
77
99
|
def abort!
|
78
100
|
abort && fail(Errors::Abort)
|
79
101
|
end
|
80
102
|
|
81
|
-
def error(
|
82
|
-
|
103
|
+
def error(error_message = '')
|
104
|
+
if error_message.is_a?(Hash)
|
105
|
+
error_data = error_message
|
106
|
+
else
|
107
|
+
error_data = { message: error_message }
|
108
|
+
end
|
83
109
|
|
84
110
|
error_data[:class_name] = self.class.name
|
85
111
|
|
@@ -98,17 +124,16 @@ module RestMyCase
|
|
98
124
|
skip && fail(Errors::Skip)
|
99
125
|
end
|
100
126
|
|
101
|
-
|
127
|
+
def validate_context(schema = self.class.required_context_schema)
|
128
|
+
errors = context.validate_schema(schema)
|
102
129
|
|
103
|
-
|
104
|
-
self.class.trial_court
|
105
|
-
end
|
130
|
+
error(context_errors: errors, message: 'invalid context') if errors
|
106
131
|
|
107
|
-
|
108
|
-
|
132
|
+
Helpers.blank? errors
|
133
|
+
end
|
109
134
|
|
110
|
-
|
111
|
-
|
135
|
+
def validate_context!(schema = self.class.required_context)
|
136
|
+
validate_context(schema) && fail(Errors::Abort)
|
112
137
|
end
|
113
138
|
|
114
139
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'rest_my_case/context/errors/base'
|
2
|
+
require 'rest_my_case/context/schema_validator/base'
|
3
|
+
|
1
4
|
module RestMyCase
|
2
5
|
module Context
|
3
6
|
|
@@ -11,10 +14,18 @@ module RestMyCase
|
|
11
14
|
Errors::Base
|
12
15
|
end
|
13
16
|
|
17
|
+
def self.schema_validator_class
|
18
|
+
SchemaValidator::Base
|
19
|
+
end
|
20
|
+
|
14
21
|
def to_hash
|
15
22
|
Marshal.load Marshal.dump(attributes)
|
16
23
|
end
|
17
24
|
|
25
|
+
def validate_schema(schema)
|
26
|
+
self.class.schema_validator_class.new(self).validate(schema)
|
27
|
+
end
|
28
|
+
|
18
29
|
def errors
|
19
30
|
@errors ||= self.class.error_class.new(self)
|
20
31
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module RestMyCase
|
2
|
+
module Context
|
3
|
+
module SchemaValidator
|
4
|
+
|
5
|
+
class Base
|
6
|
+
|
7
|
+
def initialize(context)
|
8
|
+
@context = context
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate(schema)
|
12
|
+
errors = {}
|
13
|
+
|
14
|
+
schema.each do |required_attribute|
|
15
|
+
if Helpers.blank?(@context.send(required_attribute))
|
16
|
+
errors[required_attribute] = 'is required'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Helpers.blank?(errors) ? nil : errors
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'compel'
|
2
|
+
|
3
|
+
module RestMyCase
|
4
|
+
module Context
|
5
|
+
module SchemaValidator
|
6
|
+
|
7
|
+
class Base
|
8
|
+
|
9
|
+
def initialize(context)
|
10
|
+
@context = context
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate(schema)
|
14
|
+
result = ::Compel.run(@context, build_schema(schema))
|
15
|
+
|
16
|
+
result.valid? ? nil : result.errors
|
17
|
+
end
|
18
|
+
|
19
|
+
protected ###################### PROTECTED #############################
|
20
|
+
|
21
|
+
def build_schema(schema)
|
22
|
+
::Compel.hash.keys \
|
23
|
+
schema.is_a?(Hash) ? schema : all_attributes_required(schema)
|
24
|
+
end
|
25
|
+
|
26
|
+
def all_attributes_required(schema)
|
27
|
+
{}.tap do |new_schema|
|
28
|
+
schema.each { |key| new_schema[key] = Compel.any.required }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,18 +1,15 @@
|
|
1
|
+
require 'rest_my_case/context/http_status'
|
2
|
+
|
1
3
|
module RestMyCase
|
2
4
|
|
3
5
|
module HttpStatus
|
4
6
|
|
5
7
|
include Status
|
6
8
|
|
7
|
-
module ClassMethods
|
8
|
-
def trial_court
|
9
|
-
@trial_court ||= Trial::Court.new \
|
10
|
-
Judge::Base, DefenseAttorney::Base, Base, Context::HttpStatus
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
9
|
def self.included(parent_class)
|
15
|
-
parent_class.
|
10
|
+
return unless parent_class.respond_to? :trial_court
|
11
|
+
|
12
|
+
parent_class.trial_court.context_class = Context::HttpStatus
|
16
13
|
end
|
17
14
|
|
18
15
|
end
|
@@ -4,8 +4,8 @@ module RestMyCase
|
|
4
4
|
class Base
|
5
5
|
|
6
6
|
def initialize(trial_case)
|
7
|
-
@trial_case
|
8
|
-
@performed_use_cases
|
7
|
+
@trial_case = trial_case
|
8
|
+
@performed_use_cases = []
|
9
9
|
@use_case_that_aborted = nil
|
10
10
|
end
|
11
11
|
|
@@ -22,16 +22,21 @@ module RestMyCase
|
|
22
22
|
|
23
23
|
def run_setup_methods
|
24
24
|
@trial_case.use_cases.each do |use_case|
|
25
|
-
break if
|
25
|
+
break if method_aborts?(:setup, use_case)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
def run_perform_methods
|
30
|
-
return nil if @use_case_that_aborted
|
31
|
-
|
32
30
|
@trial_case.use_cases.each do |use_case|
|
33
|
-
|
31
|
+
validate_context_aborts?(use_case)
|
32
|
+
|
33
|
+
next if use_case.options[:should_skip] || @use_case_that_aborted
|
34
|
+
|
35
|
+
@performed_use_cases.push use_case
|
36
|
+
|
37
|
+
method_aborts?(:perform, use_case)
|
34
38
|
end
|
39
|
+
|
35
40
|
end
|
36
41
|
|
37
42
|
def run_rollback_methods
|
@@ -50,16 +55,16 @@ module RestMyCase
|
|
50
55
|
|
51
56
|
private ########################### PRIVATE ##############################
|
52
57
|
|
53
|
-
def
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
def method_perform_has_aborted(use_case)
|
58
|
-
return false if use_case.options[:should_skip]
|
58
|
+
def validate_context_aborts?(use_case)
|
59
|
+
should_abort_before = use_case.options[:should_abort]
|
59
60
|
|
60
|
-
|
61
|
+
use_case.validate_context
|
61
62
|
|
62
|
-
|
63
|
+
if !should_abort_before && use_case.options[:should_abort]
|
64
|
+
@use_case_that_aborted = use_case
|
65
|
+
end
|
66
|
+
rescue Errors::Abort
|
67
|
+
@use_case_that_aborted = use_case
|
63
68
|
end
|
64
69
|
|
65
70
|
def method_aborts?(method_name, use_case)
|