rest_my_case 1.10.9 → 1.11.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 +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 [](https://codeclimate.com/github/goncalvesjoao/rest_my_case) [](https://travis-ci.org/goncalvesjoao/rest_my_case) [](https://codeclimate.com/github/goncalvesjoao/rest_my_case) [](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)
|