nifty_services 0.0.5 → 0.0.6
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 +4 -4
- data/.travis.yml +6 -2
- data/README.md +64 -1195
- data/docs/api.md +142 -0
- data/docs/callbacks.md +67 -0
- data/docs/cli.md +13 -0
- data/docs/configuration.md +62 -0
- data/docs/crud_services.md +534 -0
- data/docs/i18n.md +55 -0
- data/docs/services_markup.md +271 -0
- data/docs/usage.md +204 -0
- data/docs/webframeworks_integration.md +145 -0
- data/lib/nifty_services.rb +5 -8
- data/lib/nifty_services/base_action_service.rb +7 -10
- data/lib/nifty_services/base_create_service.rb +35 -45
- data/lib/nifty_services/base_crud_service.rb +18 -43
- data/lib/nifty_services/base_delete_service.rb +22 -21
- data/lib/nifty_services/base_service.rb +52 -42
- data/lib/nifty_services/base_update_service.rb +19 -20
- data/lib/nifty_services/configuration.rb +16 -17
- data/lib/nifty_services/errors.rb +2 -2
- data/lib/nifty_services/extensions/callbacks.rb +173 -0
- data/lib/nifty_services/support/hash.rb +14 -0
- data/lib/nifty_services/support/string.rb +21 -0
- data/lib/nifty_services/util.rb +4 -4
- data/lib/nifty_services/version.rb +1 -1
- data/nifty_services.gemspec +1 -1
- metadata +21 -10
- data/lib/nifty_services/extensions/callbacks_interface.rb +0 -171
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 487b8be2050ec50ec28c0129defd800f1f94885b
|
4
|
+
data.tar.gz: 6e05b7663023277532c301820b32acc2da5c1580
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3808572625a8be8a29d066bd928b766dbfbca794cc63e0c0bb7fd18e17b8e31d02bc201eb1aa49a961d340a62245242797a597751e3eec285e6b0ba36d5f5f9
|
7
|
+
data.tar.gz: 9dc671f34a2c6e2e8c5f5ee1103af341429da09c950b37e2f92c037ab06f7ae4247263db0a40ff9bf768dd7a9f72edbbd4f21581ae394fce84efae380e6eae7c
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,76 +1,25 @@
|
|
1
|
-
# NiftyServices
|
1
|
+
# NiftyServices
|
2
2
|
|
3
|
+
[](https://travis-ci.org/fidelisrafael/nifty_services)
|
3
4
|
## Introduction
|
4
5
|
|
5
6
|
Nifty Services comes to solve your Ruby applications(*including but not limited to* Rails, Grape, Sinatra, and plain Ruby) code mess with **simplicity in mind**!
|
6
7
|
|
7
8
|
NiftyServices provides a very nifty, simple & clear API to **organize and reuse** your application **domain logic in plain Ruby Services Objects** turning your codebase in a very extensible, standardized and reusable components.
|
8
9
|
|
9
|
-
**Most important:** You and your team win what I consider the best benefit when using Nifty Services: **Easily and scalable maintained code.**
|
10
|
+
**Most important:** You and your team win what I consider the best benefit when using Nifty Services: **Easily and scalable maintained code.**
|
10
11
|
Believe me, you'll fall in :heart_eyes: with this small piece of code, keep reading!
|
11
12
|
|
12
13
|
This gem was designed and conventioned to be used specially with **Web API applications**, but this is just a convention, you can use it even with [shoes (for desktop apps)](https://github.com/shoes/shoes) applications if you want, for example.
|
13
14
|
|
14
15
|
#### :book: I know, this README is very huge
|
15
16
|
|
16
|
-
As you can see, this README needs some time to be full read, but is very difficulty to explain all things, concepts and philosophy of this gem without writing a lot, we can't escape this :(
|
17
|
+
As you can see, this README needs some time to be full read, but is very difficulty to explain all things, concepts and philosophy of this gem without writing a lot, we can't escape this :(
|
17
18
|
|
18
|
-
But remember one thing: This is a **tecnical documentation**, not a blog post, I'm pretty sure you can take about 30 minutes + some cups of :coffee: to better understand all NiftyServices can
|
19
|
+
But remember one thing: This is a **tecnical documentation**, not a blog post, I'm pretty sure you can take about 30 minutes + some cups of :coffee: to better understand all NiftyServices can
|
19
20
|
do for you and your project. Good reading, and if you have some question, [please let me know](issues/new).
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
## Table of Contents
|
24
|
-
|
25
|
-
* [Dafuck is this gem](#introduction)
|
26
|
-
* [Conventions](#conventions)
|
27
|
-
* [Single Responsability](#conventions-single-responsibility)
|
28
|
-
* [Method execution](#hammer-common-and-single-run-execution-method)
|
29
|
-
* [Rich Service Objects](#package-rich-service-objects)
|
30
|
-
* [Security & Access Level Control](#lock-security---access-control-level)
|
31
|
-
* [Installation](#installation)
|
32
|
-
* [Usage](#usage)
|
33
|
-
* [Basic Service Markup](#basic-service-markup)
|
34
|
-
* [How a Service must be created](#wrapping-things-up)
|
35
|
-
* [Services API](#services-public-api)
|
36
|
-
* [Full Public Service API Methods List](#full-public-api-methods-list)
|
37
|
-
* [Handling Success & Error Responses](#success--error-responses)
|
38
|
-
* [Success response](#white_check_mark-handling-success-zap)
|
39
|
-
* [Error response](#red_circle-handling-error-boom)
|
40
|
-
* [Custom error response methods](#custom-error-response-methods)
|
41
|
-
* [CRUD Services](#crud-services)
|
42
|
-
* [**Create** - BaseCreateService](#white_check_mark-crud-create)
|
43
|
-
* [I18n Setup](#earth_americas-i18n-setup)
|
44
|
-
* [Error - Invalid User](#alien-invalid-user)
|
45
|
-
* [Error - Not authorized](#no_entry_sign-not-authorized-to-create)
|
46
|
-
* [Error - Invalid record](#boom-record-is-invalid)
|
47
|
-
* [**Update** - BaseUpdateService](#white_check_mark-crud-update)
|
48
|
-
* [I18n Setup](#earth_asia-i18n-setup)
|
49
|
-
* [Error - Invalid User](#update-resource-user-invalid)
|
50
|
-
* [Error - Resource don't belongs to user](#update-resource-dont-belongs-to-user)
|
51
|
-
* [Error - Resource dont exists](#update-resource-dont-exists)
|
52
|
-
* [**Delete** - BaseDeleteService](#white_check_mark-crud-delete)
|
53
|
-
* [I18n Setup](#earth_africa-i18n-setup)
|
54
|
-
* [Error - Invalid User](#delete-resource-user-invalid)
|
55
|
-
* [Error - Resource don't belongs to user](#delete-resource-dont-belongs-to-user)
|
56
|
-
* [Error - Resource dont exists](#delete-resource-dont-exists)
|
57
|
-
* [I18n Setup](#us-fr-jp-i18n-support-uk-es-de)
|
58
|
-
* [Callbacks](#callbacks)
|
59
|
-
* [Using custom callbacks](#creating-custom-callbacks)
|
60
|
-
* [Configuration](#construction-configuration-construction)
|
61
|
-
* [Web Frameworks integration](#web-frameworks-integrations)
|
62
|
-
* [Ruby on Rails](#frameworks-rails)
|
63
|
-
* [Grape/Sinatra/Rack](#frameworks-rack)
|
64
|
-
* [ Basic Services class Markups](#pray-basic-service-markups-raised_hands)
|
65
|
-
* [BaseCreateService Basic Markup](#basecreateservice-basic-markup)
|
66
|
-
* [BaseUpdateService Basic Markup](#baseupdateservice-basic-markup)
|
67
|
-
* [BaseDeleteService Basic Markup](#basedeleteservice-basic-markup)
|
68
|
-
* [BaseActionService Basic Markup](#baseactionservice-basic-markup)
|
69
|
-
* [CLI Generators](#cli-generators)
|
70
|
-
* [Roadmap](#roadmap)
|
71
|
-
* [Development](#computer-development)
|
72
|
-
* [Contributing](#thumbsup-contributing)
|
73
|
-
* [License - MIT](#memo-license)
|
22
|
+
Update: Now the documentation was separated in Wiki format. So it's a lot easier for reading.
|
74
23
|
|
75
24
|
---
|
76
25
|
|
@@ -98,6 +47,62 @@ Think and implement security rules from the first minutes of live in your applic
|
|
98
47
|
|
99
48
|
Now you know the basic concepts and philosophy of `NiftyServices`, lets start working with this candy library?
|
100
49
|
|
50
|
+
---
|
51
|
+
|
52
|
+
## Table of Contents
|
53
|
+
|
54
|
+
* [Dafuck is this gem](#introduction)
|
55
|
+
* [Conventions](#conventions)
|
56
|
+
* [Single Responsability](#conventions-single-responsibility)
|
57
|
+
* [Method execution](#hammer-common-and-single-run-execution-method)
|
58
|
+
* [Rich Service Objects](#package-rich-service-objects)
|
59
|
+
* [Security & Access Level Control](#lock-security---access-control-level)
|
60
|
+
* [Installation](#installation)
|
61
|
+
* [Usage](./docs/usage.md#usage)
|
62
|
+
* [Basic Service Markup](./docs/usage.md#basic-service-markup)
|
63
|
+
* [How a Service must be created](./docs/usage.md#wrapping-things-up)
|
64
|
+
* [Services Objects API](./docs/api.md#services-public-api)
|
65
|
+
* [Handling Success & Error Responses](./docs/api.md#success--error-responses)
|
66
|
+
* [Success response](./docs/api.md#white_check_mark-handling-success-zap)
|
67
|
+
* [Error response](./docs/api.md#red_circle-handling-error-boom)
|
68
|
+
* [Custom error response methods](./docs/api.md#custom-error-response-methods)
|
69
|
+
* [Full Public Service API Methods List](./docs/api.md#full-public-api-methods-list)
|
70
|
+
* [CRUD Services](./docs/crud_services.md#crud-services)
|
71
|
+
* [**Create** - BaseCreateService](./docs/crud_services.md#white_check_mark-crud-create)
|
72
|
+
* [I18n Setup](./docs/crud_services.md#earth_americas-i18n-setup)
|
73
|
+
* [Error - Invalid User](./docs/crud_services.md#alien-invalid-user)
|
74
|
+
* [Error - Not authorized](./docs/crud_services.md#no_entry_sign-not-authorized-to-create)
|
75
|
+
* [Error - Invalid record](./docs/crud_services.md#boom-record-is-invalid)
|
76
|
+
* [**Update** - BaseUpdateService](./docs/crud_services.md#white_check_mark-crud-update)
|
77
|
+
* [I18n Setup](./docs/crud_services.md#earth_asia-i18n-setup)
|
78
|
+
* [Error - Invalid User](./docs/crud_services.md#update-resource-user-invalid)
|
79
|
+
* [Error - Resource don't belongs to user](./docs/crud_services.md#update-resource-dont-belongs-to-user)
|
80
|
+
* [Error - Resource dont exists](./docs/crud_services.md#update-resource-dont-exists)
|
81
|
+
* [**Delete** - BaseDeleteService](./docs/crud_services.md#white_check_mark-crud-delete)
|
82
|
+
* [I18n Setup](./docs/crud_services.md#earth_africa-i18n-setup)
|
83
|
+
* [Error - Invalid User](./docs/crud_services.md#delete-resource-user-invalid)
|
84
|
+
* [Error - Resource don't belongs to user](./docs/crud_services.md#delete-resource-dont-belongs-to-user)
|
85
|
+
* [Error - Resource dont exists](./docs/crud_services.md#delete-resource-dont-exists)
|
86
|
+
* [I18n Setup](./docs/i18n.md)
|
87
|
+
* [Callbacks](./docs/callbacks.md)
|
88
|
+
* [Using custom callbacks](./docs/callbacks.md#creating-custom-callbacks)
|
89
|
+
* [Configuration](./docs/configuration.md)
|
90
|
+
* [Web Frameworks integration](./docs/webframeworks_integration.md)
|
91
|
+
* [Ruby on Rails](./docs/webframeworks_integration.md#frameworks-rails)
|
92
|
+
* [Grape/Sinatra/Rack](./docs/webframeworks_integration.md#frameworks-rack)
|
93
|
+
* [Sample Integrations](./docs/webframeworks_integration.md#integration-examples)
|
94
|
+
* [Base Services Class Markups](./docs/services_markup.md)
|
95
|
+
* [BaseCreateService Base Markup](./docs/services_markup.md#basecreateservice-basic-markup)
|
96
|
+
* [BaseUpdateService Base Markup](./docs/services_markup.md#baseupdateservice-basic-markup)
|
97
|
+
* [BaseDeleteService Base Markup](./docs/services_markup.md#basedeleteservice-basic-markup)
|
98
|
+
* [BaseActionService Base Markup](./docs/services_markup.md#baseactionservice-basic-markup)
|
99
|
+
* [CLI Generators](./docs/cli.md)
|
100
|
+
* [Roadmap](#roadmap)
|
101
|
+
* [Development](#computer-development)
|
102
|
+
* [Contributing](#thumbsup-contributing)
|
103
|
+
* [License - MIT](#memo-license)
|
104
|
+
|
105
|
+
|
101
106
|
---
|
102
107
|
|
103
108
|
## Installation
|
@@ -118,1144 +123,8 @@ Or install it yourself as:
|
|
118
123
|
|
119
124
|
---
|
120
125
|
|
121
|
-
## Usage
|
122
|
-
|
123
|
-
NiftyServices provide a start basic service class for generic code which is `NiftyServices::BaseService`, the very basic service markup is demonstrated below:
|
124
|
-
|
125
|
-
### Basic Service Markup
|
126
|
-
|
127
|
-
|
128
|
-
```ruby
|
129
|
-
class SemanticServiceName < NiftyServices::BaseService
|
130
|
-
|
131
|
-
def execute
|
132
|
-
execute_action do
|
133
|
-
success_response if do_something_complex
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
def do_something_complex
|
138
|
-
# (...) some complex bussiness logic
|
139
|
-
return true
|
140
|
-
end
|
141
|
-
|
142
|
-
private
|
143
|
-
def can_execute?
|
144
|
-
return forbidden_error!('errors.message_key') if some_condition
|
145
|
-
|
146
|
-
return not_found_error!('errors.message_key') if another_condition
|
147
|
-
|
148
|
-
return unprocessable_entity_error!('errors.message_key') if other_condition
|
149
|
-
|
150
|
-
# ok, this service can be executed
|
151
|
-
return true
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
service = SemanticServiceName.new(options)
|
156
|
-
service.execute
|
157
|
-
```
|
158
|
-
|
159
|
-
---
|
160
|
-
|
161
|
-
### Ok, real world example plizzz
|
162
|
-
|
163
|
-
Lets work with a real and a little more complex example, an Service responsible to send daily news mail to users.
|
164
|
-
The code below shows basically everything you need to know about services structure, such: entry point, callbacks, authorization, error and success response handling, so after understanding this little piece of code, you will be **ready to code your own services**!
|
165
|
-
|
166
|
-
```ruby
|
167
|
-
class DailyNewsMailSendService < NiftyServices::BaseService
|
168
|
-
|
169
|
-
before_execute do
|
170
|
-
log.info('Routine started at: %s' % Time.now)
|
171
|
-
end
|
172
|
-
|
173
|
-
after_execute do
|
174
|
-
log.info('Routine ended at: %s' % Time.now)
|
175
|
-
end
|
176
|
-
|
177
|
-
after_initialize do
|
178
|
-
user_data = [@user.name, @user.email]
|
179
|
-
log.info('Routine Details: Send daily news email to user %s(%s)' % user_data)
|
180
|
-
end
|
181
|
-
|
182
|
-
after_success do
|
183
|
-
log.info('Success sent daily news feed email to user')
|
184
|
-
end
|
185
|
-
|
186
|
-
before_error do
|
187
|
-
log.warn('Something went wrong')
|
188
|
-
end
|
189
|
-
|
190
|
-
after_error do
|
191
|
-
log.error('Error sending email to user. See details below :(')
|
192
|
-
log.error(errors)
|
193
|
-
end
|
194
|
-
|
195
|
-
attr_reader :user
|
196
|
-
|
197
|
-
def initialize(user, options = {})
|
198
|
-
@user = user
|
199
|
-
super(options)
|
200
|
-
end
|
201
|
-
|
202
|
-
def execute
|
203
|
-
execute_action do
|
204
|
-
success_response if send_mail_to_user
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
private
|
209
|
-
def can_execute?
|
210
|
-
unless valid_user?
|
211
|
-
# returns false
|
212
|
-
return not_found_error!('users.not_found')
|
213
|
-
end
|
214
|
-
|
215
|
-
unless @user.abble_to_receive_daily_news_mail?
|
216
|
-
# returns false
|
217
|
-
return forbidden_error!('users.already_received_daily_news_mail')
|
218
|
-
end
|
219
|
-
|
220
|
-
return true
|
221
|
-
end
|
222
|
-
|
223
|
-
def send_mail_to_user
|
224
|
-
# just to fake, a real implementation could be something like:
|
225
|
-
# @user.send_daily_news_mail!
|
226
|
-
return true
|
227
|
-
end
|
228
|
-
|
229
|
-
def valid_user?
|
230
|
-
# check if object is valid and is a User class type
|
231
|
-
valid_object?(@user, User)
|
232
|
-
end
|
233
|
-
|
234
|
-
# you can use `default_options` method to add default { keys => values } to @options
|
235
|
-
# so you can use the option_enabled?(key) to verify if option is enabled
|
236
|
-
# or option_disabled?(key) to verify is option is disabled
|
237
|
-
# This default values can be override when creating new instance of Service, eg:
|
238
|
-
# DailyNewsMailSendService.new(User.last, validate_api_key: false)
|
239
|
-
def default_options
|
240
|
-
{ validate_api_key: true }
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
class User < Struct.new(:name, :email)
|
245
|
-
# just to play around with results
|
246
|
-
def abble_to_receive_daily_news_mail?
|
247
|
-
rand(10) < 5
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
user = User.new('Rafael Fidelis', 'rafa_fidelis@yahoo.com.br')
|
252
|
-
|
253
|
-
# Default logger is NiftyService.config.logger = Logger.new('/dev/null')
|
254
|
-
service = DailyNewsMailSendService.new(user, logger: Logger.new('daily_news.log'))
|
255
|
-
service.execute
|
256
|
-
```
|
257
|
-
|
258
|
-
### Sample outputs results
|
259
|
-
|
260
|
-
#### :smile: Success:
|
261
|
-
|
262
|
-
```
|
263
|
-
I, [2016-07-15T17:13:40.092854 #2480] INFO -- : Routine Details: Send daily news email to user
|
264
|
-
Rafael Fidelis(rafa_fidelis@yahoo.com.br)
|
265
|
-
|
266
|
-
I, [2016-07-15T17:13:40.092987 #2480] INFO -- : Routine started at: 2016-07-15 17:13:40 -0300
|
267
|
-
|
268
|
-
I, [2016-07-15T17:13:40.093143 #2480] INFO -- : Success sent daily news feed email to user
|
269
|
-
|
270
|
-
I, [2016-07-15T17:13:40.093242 #2480] INFO -- : Routine ended at: 2016-07-15 17:13:40 -0300
|
271
|
-
|
272
|
-
|
273
|
-
```
|
274
|
-
|
275
|
-
#### :weary: Error:
|
276
|
-
|
277
|
-
```
|
278
|
-
I, [2016-07-15T17:12:10.954792 #756] INFO -- : Routine Details: Send daily news email to user
|
279
|
-
Rafael Fidelis(rafa_fidelis@yahoo.com.br)
|
280
|
-
|
281
|
-
I, [2016-07-15T17:12:10.955025 #756] INFO -- : Routine started at: 2016-07-15 17:12:10 -0300
|
282
|
-
|
283
|
-
W, [2016-07-15T17:12:10.955186 #756] WARN -- : Something went wrong
|
284
|
-
|
285
|
-
E, [2016-07-15T17:12:11.019645 #756] ERROR -- : Error sending email to user. See details below :(
|
286
|
-
|
287
|
-
E, [2016-07-15T17:12:11.019838 #756] ERROR -- : ["User has already received daily news mail today"]
|
288
|
-
|
289
|
-
I, [2016-07-15T17:12:11.020073 #756] INFO -- : Routine ended at: 2016-07-15 17:12:11 -0300
|
290
|
-
|
291
|
-
```
|
292
|
-
|
293
|
-
<br />
|
294
|
-
|
295
|
-
### Wrapping things up
|
296
|
-
|
297
|
-
The code above demonstrate a very basic example of **how dead easy** is to work with Services, let me clarify some things to your better understanding:
|
298
|
-
|
299
|
-
* ☑ All services classes must inherit from `NiftyServices::BaseService`
|
300
|
-
|
301
|
-
* ☑ For convention(but not a rule) all services must expose only `execute`(and of course, `initialize`) as public methods.
|
302
|
-
|
303
|
-
* ☑ `execute_action(&block)` **MUST** be called to properly setup things in execution context.
|
304
|
-
|
305
|
-
* ☑ `can_execute?` must be **ALWAYS** implemented in service classes, **ALWAYS**, this ensure that your code will **safely runned**.
|
306
|
-
Note: A `NotImplementedError` exception will be raised if service won't define your own `can_execute?` method.
|
307
|
-
|
308
|
-
* ☑ There's a very simple DSL for marking result as success/fail (eg: `unprocessable_entity_error!` or `success_response`).
|
309
|
-
|
310
|
-
* ☑ Simple DSL for actions callbacks inside current execution context. (eg: `after_success` or `before_error`)
|
311
|
-
Note: You don't need to use the DSL if you don't want, you can simply define the methods(such as: `private def after_success; do_something; end`
|
312
|
-
|
313
|
-
This is the very basic concept of creating and executing a service object, now we need to know how to work with responses to get the most of our services, for this, let's digg in the mainly public API methods of `NiftyService::BaseService` class:
|
314
|
-
|
315
|
-
---
|
316
|
-
|
317
|
-
## Services Public API
|
318
|
-
|
319
|
-
Below, a list of most common public accessible methods for any instance of service:
|
320
|
-
(Detailed usage and full API list is available below this section)
|
321
|
-
|
322
|
-
```ruby
|
323
|
-
service.success? # boolean
|
324
|
-
service.fail? # boolean
|
325
|
-
service.errors # array
|
326
|
-
service.response_status # symbol (eg: :ok)
|
327
|
-
service.response_status_code # integer (eg: 200)
|
328
|
-
```
|
329
|
-
|
330
|
-
So, grabbing our `DailyNewsMailSendService` service again, we could do:
|
331
|
-
|
332
|
-
```ruby
|
333
|
-
service = DailyNewsMailSendService.new(User.new('test', 'test@test.com'))
|
334
|
-
service.execute
|
335
|
-
|
336
|
-
if service.success? # or unless service.fail?
|
337
|
-
SentEmails.create(user: service.user, type: 'welcome_email')
|
338
|
-
else
|
339
|
-
puts 'Error sending email, details below:'
|
340
|
-
puts 'Status: %s' % service.response_status
|
341
|
-
puts 'Status code: %s' % service.response_status_code
|
342
|
-
puts service.errors
|
343
|
-
end
|
344
|
-
|
345
|
-
# trying to re-execute the service will return `nil`
|
346
|
-
service.execute
|
347
|
-
```
|
348
|
-
|
349
|
-
This is really great and nifty, no? But we already started, there's some really cool stuff when dealing with **Restful** API's actions, before entering this subject let's see how to handle error and success response.
|
350
|
-
|
351
|
-
---
|
352
|
-
|
353
|
-
## Success & Error Responses
|
354
|
-
|
355
|
-
### :white_check_mark: Handling Success :zap:
|
356
|
-
|
357
|
-
To mark a service running as successfully, you must call one of this methods (preferencially inside of `execute_action` block):
|
358
|
-
|
359
|
-
* `success_response # [200, :ok]`
|
360
|
-
* `success_created_response [201, :created]`
|
361
|
-
|
362
|
-
The first value in comments above is the value which will be defined to `service.response_status_code` and the last is the value set to `service.response_status`.
|
363
|
-
|
364
|
-
---
|
365
|
-
|
366
|
-
|
367
|
-
### :red_circle: Handling Error :boom:
|
368
|
-
|
369
|
-
By default, all services comes with following error methods:
|
370
|
-
(**Hint**: See all available error methods [`here`](lib/nifty_services/configuration.rb#L10-L16))
|
371
|
-
|
372
|
-
```ruby
|
373
|
-
bad_request_error(message_key) # set response_status_code to 400
|
374
|
-
|
375
|
-
not_authorized_error(message_key) # set response_status_code to 401,
|
376
|
-
|
377
|
-
forbidden_error(message_key) # set response_status_code to 403,
|
378
|
-
|
379
|
-
not_found_error(message_key) # set response_status_code to 404,
|
380
|
-
|
381
|
-
unprocessable_entity_error(message_key) # set response_status_code to 422,
|
382
|
-
|
383
|
-
internal_server_error(message_key) # set response_status_code to 500,
|
384
|
-
|
385
|
-
not_implemented_error(message_key) # set response_status_code to 501
|
386
|
-
```
|
387
|
-
|
388
|
-
Beside this methods, you can always use **low level** API to generate errors, just call the `error` method, ex:
|
389
|
-
|
390
|
-
```ruby
|
391
|
-
# API
|
392
|
-
error(status, message_key, options = {})
|
393
|
-
|
394
|
-
# eg:
|
395
|
-
error(409, :conflict_error, reason: 'unkown')
|
396
|
-
error!(409, :conflict_error, reason: 'unkown')
|
397
|
-
|
398
|
-
# suppose you YML locale file have the configuration:
|
399
|
-
# nifty_services:
|
400
|
-
# errors:
|
401
|
-
# conflict_error: 'Conflict! The reason is %{reason}'
|
402
|
-
```
|
403
|
-
|
404
|
-
#### Custom error response methods
|
405
|
-
|
406
|
-
But you can always add new convenience errors methods via API, this way you will have more expressivity and sintax sugar:
|
407
|
-
|
408
|
-
```ruby
|
409
|
-
## API
|
410
|
-
NiftyServices.add_response_error_method(status, status_code)
|
411
|
-
|
412
|
-
## eg:
|
413
|
-
|
414
|
-
NiftyServices.add_response_error_method(:conflict, 409)
|
415
|
-
|
416
|
-
## now you have the methods:
|
417
|
-
|
418
|
-
## conflict_error(:conflict_error)
|
419
|
-
## conflit_error!(:conflict_error)
|
420
|
-
```
|
421
|
-
|
422
|
-
---
|
423
|
-
|
424
|
-
## CRUD Services
|
425
|
-
|
426
|
-
So, until now we saw how to use `NiftyServices::BaseService` to create generic services to couple specific domain logic for actions, this is very usefull, but things get a lot better when you're working with **CRUD** actions for your api.
|
427
|
-
|
428
|
-
Follow an example of **Create, Update and Delete** CRUD services for `Post` resource:
|
429
|
-
|
430
|
-
## :white_check_mark: CRUD: Create
|
431
|
-
|
432
|
-
```ruby
|
433
|
-
class PostCreateService < NiftyServices::BaseCreateService
|
434
|
-
|
435
|
-
# record_type must be a object respond to :build and :save methods
|
436
|
-
# is possible to access this record outside of service using
|
437
|
-
# `service.record` or `service.post`
|
438
|
-
# if you want to create a custom alias name, use:
|
439
|
-
# record_type Post, alias_name: :user_post
|
440
|
-
# This way, you can access the record using
|
441
|
-
# `service.user_post`
|
442
|
-
|
443
|
-
record_type Post
|
444
|
-
|
445
|
-
WHITELIST_ATTRIBUTES = [:title, :content]
|
446
|
-
|
447
|
-
def record_attributes_whitelist
|
448
|
-
WHITELIST_ATTRIBUTES
|
449
|
-
end
|
450
|
-
|
451
|
-
# use custom scope to create the record
|
452
|
-
# scope returned below must respond_to :build instance method
|
453
|
-
def build_record_scope
|
454
|
-
@user.posts
|
455
|
-
end
|
456
|
-
|
457
|
-
# this key is used for I18n translations
|
458
|
-
def record_error_key
|
459
|
-
:posts
|
460
|
-
end
|
461
|
-
|
462
|
-
def user_can_create_record?
|
463
|
-
# (here you can do any kind of validation, eg:)
|
464
|
-
# check if user is trying to recreate a recent resource
|
465
|
-
# this will return false if user has already created a post with
|
466
|
-
# this title in the last 30 seconds (usefull to ban bots)
|
467
|
-
@user.posts.exists(title: record_allowed_attributes[:title], created_at: "NOW() - interval(30 seconds)")
|
468
|
-
end
|
469
|
-
end
|
470
|
-
|
471
|
-
service = PostCreateService.new(User.first, title: 'Teste', content: 'Post example content')
|
472
|
-
|
473
|
-
service.execute
|
474
|
-
|
475
|
-
service.success? # true
|
476
|
-
service.response_status_code # 200
|
477
|
-
service.response_status # :created
|
478
|
-
```
|
479
|
-
|
480
|
-
#### :earth_americas: I18n setup
|
481
|
-
|
482
|
-
You must have the following keys setup up in your locales files:
|
483
|
-
|
484
|
-
```yml
|
485
|
-
nifty_services:
|
486
|
-
users:
|
487
|
-
not_found: "Invalid or not found user"
|
488
|
-
ip_temporarily_blocked: "This IP is temporarily blocked from creating records"
|
489
|
-
# note: posts is the key return in `record_error_key` service method
|
490
|
-
posts:
|
491
|
-
user_cant_create: "User cant create this record"
|
492
|
-
```
|
493
|
-
|
494
|
-
#### :alien: Invalid user <a name="create-resource-user-invalid"></a>
|
495
|
-
|
496
|
-
If you try to create a post for a invalid user, such as:
|
497
|
-
|
498
|
-
```ruby
|
499
|
-
# PostCreateService.new(user, options)
|
500
|
-
service = PostCreateService.new(nil, options)
|
501
|
-
service.execute
|
502
|
-
|
503
|
-
service.success? # false
|
504
|
-
service.response_status # :not_found
|
505
|
-
service.response_status_code # 404
|
506
|
-
service.errors # ["Invalid or not found user"]
|
507
|
-
```
|
508
|
-
|
509
|
-
#### :no_entry_sign: Not authorized to create
|
510
|
-
|
511
|
-
Or if user is trying to create a duplicate resource:
|
512
|
-
|
513
|
-
```ruby
|
514
|
-
# PostCreateService.new(user, options)
|
515
|
-
service = PostCreateService.new(User.first, options)
|
516
|
-
service.execute
|
517
|
-
|
518
|
-
service.success? # false
|
519
|
-
service.errors # ["User cant create this record"]
|
520
|
-
service.response_status # :forbidden_error
|
521
|
-
service.response_status_code # 400
|
522
|
-
```
|
523
|
-
|
524
|
-
#### :boom: Record is invalid
|
525
|
-
|
526
|
-
Eg: if any validation in Post model won't pass:
|
527
|
-
|
528
|
-
```ruby
|
529
|
-
# PostCreateService.new(user, options)
|
530
|
-
# Post model as the validation:
|
531
|
-
# validates_presence_of :title, :content
|
532
|
-
service = PostCreateService.new(User.first, title: nil, content: nil)
|
533
|
-
service.execute
|
534
|
-
|
535
|
-
service.success? # false
|
536
|
-
|
537
|
-
service.errors # => [{ title: 'is empty', content: 'is empty' }]
|
538
|
-
|
539
|
-
service.response_status # :unprocessable_entity
|
540
|
-
service.response_status_code # 422
|
541
|
-
```
|
542
|
-
---
|
543
|
-
|
544
|
-
## :white_check_mark: CRUD: Update
|
545
|
-
|
546
|
-
```ruby
|
547
|
-
class PostUpdateService < NiftyServices::BaseUpdateService
|
548
|
-
|
549
|
-
# service.post or service.record
|
550
|
-
record_type Post
|
551
|
-
|
552
|
-
WHITELIST_ATTRIBUTES = [:title, :content]
|
553
|
-
|
554
|
-
def record_allowed_attributes
|
555
|
-
WHITELIST_ATTRIBUTES
|
556
|
-
end
|
557
|
-
|
558
|
-
# by default, internally @record must respond to
|
559
|
-
# user_can_update(user)
|
560
|
-
# so you can do specific validations per resource
|
561
|
-
def user_can_update_record?
|
562
|
-
# only system admins and owner can update this record
|
563
|
-
@user.admin? || @user.id == @record.id
|
564
|
-
end
|
565
|
-
|
566
|
-
def record_error_key
|
567
|
-
:posts
|
568
|
-
end
|
569
|
-
end
|
570
|
-
|
571
|
-
# :user_id will be ignored since it's not in whitelisted attributes
|
572
|
-
# this can safe yourself from parameter inject attacks, by default
|
573
|
-
update_service = PostUpdateService.new(Post.first, User.first, title: 'Changing title', content: 'Updating content', user_id: 2)
|
574
|
-
|
575
|
-
update_service.execute
|
576
|
-
|
577
|
-
update_service.success? # true
|
578
|
-
update_service.response_status # :ok
|
579
|
-
update_service.response_status_code # 200
|
580
|
-
|
581
|
-
update_service.changed_attributes # [:title, :content]
|
582
|
-
update_service.changed? # true
|
583
|
-
```
|
584
|
-
|
585
|
-
#### :earth_asia: I18n setup
|
586
|
-
|
587
|
-
Your locale file must have the following keys:
|
588
|
-
|
589
|
-
```yml
|
590
|
-
posts:
|
591
|
-
not_found: "Invalid or not found post"
|
592
|
-
user_cant_update: "User can't update this record"
|
593
|
-
users:
|
594
|
-
not_found: "Invalid or not found user"
|
595
|
-
```
|
596
|
-
|
597
|
-
#### :alien: User is invalid <a name="update-resource-user-invalid"></a>
|
598
|
-
|
599
|
-
Response when owner user is not valid:
|
600
|
-
|
601
|
-
```ruby
|
602
|
-
# PostUpdateService.new(post, user, params)
|
603
|
-
update_service = PostUpdateService.new(Post.first, nil, title: 'Changing title', content: 'Updating content')
|
604
|
-
|
605
|
-
update_service.execute
|
606
|
-
|
607
|
-
update_service.success? # false
|
608
|
-
update_service.response_status # :not_found_error
|
609
|
-
update_service.response_status_code # 404
|
610
|
-
|
611
|
-
update_service.errors # ["Invalid or not found user"]
|
612
|
-
```
|
613
|
-
|
614
|
-
#### :closed_lock_with_key: Resource (Post) don't belongs to user <a name="update-resource-dont-belongs-to-user"></a>
|
615
|
-
|
616
|
-
Responses when trying to update to update a resource who don't belongs to owner:
|
617
|
-
|
618
|
-
```ruby
|
619
|
-
# PostUpdateService.new(post, user, params)
|
620
|
-
update_service = PostUpdateService.new(Post.first, User.last, title: 'Changing title', content: 'Updating content')
|
621
|
-
|
622
|
-
update_service.execute
|
623
|
-
|
624
|
-
update_service.success? # false
|
625
|
-
update_service.response_status # :forbidden
|
626
|
-
update_service.response_status_code # 400
|
627
|
-
|
628
|
-
update_service.changed_attributes # []
|
629
|
-
update_service.changed? # false
|
630
|
-
update_service.errors # ["User can't update this record"]
|
631
|
-
```
|
632
|
-
|
633
|
-
#### :santa: Resource don't exists <a name="update-resource-dont-exists"></a>
|
634
|
-
|
635
|
-
Response when post don't exists:
|
636
|
-
|
637
|
-
```ruby
|
638
|
-
# PostUpdateService.new(post, user, params)
|
639
|
-
update_service = PostUpdateService.new(nil, User.last, title: 'Changing title', content: 'Updating content')
|
640
|
-
|
641
|
-
update_service.execute
|
642
|
-
|
643
|
-
update_service.success? # false
|
644
|
-
update_service.response_status # :not_found_error
|
645
|
-
update_service.response_status_code # 404
|
646
|
-
|
647
|
-
update_service.errors # ["Invalid or not found post"]
|
648
|
-
```
|
649
|
-
|
650
|
-
---
|
651
|
-
|
652
|
-
## :white_check_mark: CRUD: Delete
|
653
|
-
|
654
|
-
```ruby
|
655
|
-
class PostDeleteService < NiftyServices::BaseDeleteService
|
656
|
-
# record_type object must respond to :destroy or :delete method
|
657
|
-
record_type Post
|
658
|
-
|
659
|
-
def record_error_key
|
660
|
-
:posts
|
661
|
-
end
|
662
|
-
|
663
|
-
# below the code used internally, you can override to
|
664
|
-
# create custom delete, but remembers that this method
|
665
|
-
# must return a boolean value
|
666
|
-
def destroy_record
|
667
|
-
@record.try(:destroy) || @record.try(:delete)
|
668
|
-
end
|
669
|
-
|
670
|
-
# by default, internally @record must respond to
|
671
|
-
# @record.user_can_delete?(user)
|
672
|
-
# so you can do specific validations per resource
|
673
|
-
def user_can_delete_record?
|
674
|
-
# only system admins and owner can delete this record
|
675
|
-
@user.admin? || @user.id == @record.id
|
676
|
-
end
|
677
|
-
end
|
678
|
-
```
|
679
|
-
|
680
|
-
#### :earth_africa: I18n setup
|
681
|
-
|
682
|
-
Your locale file must have the following keys:
|
683
|
-
|
684
|
-
```yml
|
685
|
-
posts:
|
686
|
-
not_found: "Invalid or not found post"
|
687
|
-
user_cant_delete: "User can't delete this record"
|
688
|
-
users:
|
689
|
-
not_found: "Invalid or not found user"
|
690
|
-
```
|
691
|
-
|
692
|
-
#### :alien: User is invalid <a name="delete-resource-user-invalid"></a>
|
693
|
-
|
694
|
-
Response when owner user is not valid:
|
695
|
-
|
696
|
-
```ruby
|
697
|
-
# PostDeleteService.new(post, user, params)
|
698
|
-
delete_service = PostDeleteService.new(Post.first, nil)
|
699
|
-
|
700
|
-
delete_service.execute
|
701
|
-
|
702
|
-
delete_service.success? # false
|
703
|
-
delete_service.response_status # :not_found_error
|
704
|
-
delete_service.response_status_code # 404
|
705
|
-
|
706
|
-
delete_service.errors # ["Invalid or not found user"]
|
707
|
-
```
|
708
|
-
|
709
|
-
#### :closed_lock_with_key: Resource don't belongs to user <a name="delete-resource-dont-belongs-to-user"></a>
|
710
|
-
|
711
|
-
Responses when trying to delete a resource who don't belongs to owner:
|
712
|
-
|
713
|
-
```ruby
|
714
|
-
# PostDeleteService.new(post, user, params)
|
715
|
-
delete_service = PostDeleteService.new(Post.first, User.last)
|
716
|
-
delete_service.execute
|
717
|
-
|
718
|
-
delete_service.success? # false
|
719
|
-
delete_service.response_status # :forbidden
|
720
|
-
delete_service.response_status_code # 400
|
721
|
-
delete_service.errors # ["User can't delete this record"]
|
722
|
-
```
|
723
|
-
|
724
|
-
#### :santa: Resource(Post) don't exists <a name="delete-resource-dont-exists"></a>
|
725
|
-
|
726
|
-
Response when post don't exists:
|
727
|
-
|
728
|
-
```ruby
|
729
|
-
# PostDeleteService.new(post, user, params)
|
730
|
-
delete_service = PostDeleteService.new(nil, User.last)
|
731
|
-
|
732
|
-
delete_service.execute
|
733
|
-
|
734
|
-
delete_service.success? # false
|
735
|
-
delete_service.response_status # :not_found_error
|
736
|
-
delete_service.response_status_code # 404
|
737
|
-
|
738
|
-
delete_service.errors # ["Invalid or not found post"]
|
739
|
-
```
|
740
|
-
|
741
|
-
|
742
|
-
---
|
743
|
-
|
744
|
-
## :us: :fr: :jp: I18n Support :uk: :es: :de:
|
745
|
-
|
746
|
-
As you see in the above examples, with `NiftyServices` you can respond in multiples languages for the same service error messages, by default your locales config file must be configured as:
|
747
|
-
|
748
|
-
```yml
|
749
|
-
# attention: dont use `resource_type`
|
750
|
-
# use the key setup up in `record_error_key` methods
|
751
|
-
resource_type:
|
752
|
-
not_found: "Invalid or not found post"
|
753
|
-
user_cant_create: "User can't delete this record"
|
754
|
-
user_cant_read: "User can't access this record"
|
755
|
-
user_cant_update: "User can't delete this record"
|
756
|
-
user_cant_delete: "User can't delete this record"
|
757
|
-
users:
|
758
|
-
not_found: "Invalid or not found user"
|
759
|
-
```
|
760
|
-
|
761
|
-
You can configure the default I18n namespace using configuration:
|
762
|
-
|
763
|
-
```ruby
|
764
|
-
NiftyServies.configure do |config|
|
765
|
-
config.i18n_namespace = :my_app
|
766
|
-
end
|
767
|
-
```
|
768
|
-
|
769
|
-
Example config for `Post` and `Comment` resources using `my_app` locale namespace:
|
770
|
-
|
771
|
-
```yml
|
772
|
-
# default is nifty_services
|
773
|
-
my_app:
|
774
|
-
errors:
|
775
|
-
default_crud: &default_crud
|
776
|
-
user_cant_create: "User can't delete this record"
|
777
|
-
user_cant_read: "User can't access this record"
|
778
|
-
user_cant_update: "User can't delete this record"
|
779
|
-
user_cant_delete: "User can't delete this record"
|
780
|
-
users:
|
781
|
-
not_found: "Invalid or not found user"
|
782
|
-
posts:
|
783
|
-
<<: *default_crud
|
784
|
-
not_found: "Invalid or not found post"
|
785
|
-
comments:
|
786
|
-
<<: *default_crud
|
787
|
-
not_found: "Invalid or not found comment"
|
788
|
-
```
|
789
|
-
|
790
|
-
---
|
791
|
-
|
792
|
-
## Callbacks
|
793
|
-
|
794
|
-
Here the most common callbacks list you can use to hook actions in run-time:
|
795
|
-
(**Hint**: See all existent callbacks definitions in [`extensions/callbacks_interface.rb`](lib/nifty_services/extensions/callbacks_interface.rb#L8-L24) file)
|
796
|
-
|
797
|
-
```
|
798
|
-
- before_initialize
|
799
|
-
- after_initialize
|
800
|
-
- before_execute
|
801
|
-
- after_execute
|
802
|
-
- before_error
|
803
|
-
- after_error
|
804
|
-
- before_success
|
805
|
-
- after_success
|
806
|
-
```
|
807
|
-
|
808
|
-
### Creating custom Callbacks
|
809
|
-
|
810
|
-
Well, probably you will need to add custom callbacks to your services, in my case I need to save in database an object which tracks information about the environment used to create **ALL RECORDS** in my application, I was able to do it with just a few lines of code, see for yourself:
|
811
|
-
|
812
|
-
```ruby
|
813
|
-
# Some monkey patch :)
|
814
|
-
|
815
|
-
NiftyServices::BaseCreateService.class_eval do
|
816
|
-
ORIGIN_WHITELIST_ATTRIBUTES = [:provider, :locale, :user_agent, :ip]
|
817
|
-
|
818
|
-
def origin_params(params = {})
|
819
|
-
filter_hash(params.fetch(:origin, {}).to_h, ORIGIN_WHITELIST_ATTRIBUTES)
|
820
|
-
end
|
821
|
-
|
822
|
-
def create_origin(originable, params = {})
|
823
|
-
return unless originable.respond_to?(:create_origin)
|
824
|
-
return unless create_origin?
|
825
|
-
|
826
|
-
originable.create_origin(origin_params(params))
|
827
|
-
end
|
828
|
-
|
829
|
-
# for records which we don't need to create origins, just
|
830
|
-
# overwrite this method inside service class turning it off with:
|
831
|
-
# return false
|
832
|
-
def create_origin?
|
833
|
-
Application::Config.create_origin_for_records
|
834
|
-
end
|
835
|
-
end
|
836
|
-
|
837
|
-
# This register a callback for ALL services who inherit from `NiftyServices::BaseCreateService`
|
838
|
-
# In other words: Every and all records created in my application will be tracked
|
839
|
-
# I can believe that is easy like this, I need a beer right now!
|
840
|
-
NiftyServices::BaseCreateService.register_callback(:after_success, :create_origin_for_record) do
|
841
|
-
create_origin(@record, @options)
|
842
|
-
end
|
843
|
-
|
844
|
-
```
|
845
|
-
|
846
|
-
Now, every record created in my application will have an associated `origin` object, really simple and cool!
|
847
|
-
|
848
|
-
---
|
849
|
-
|
850
|
-
## :construction: Configuration :construction:
|
851
|
-
|
852
|
-
There are only a few things you must want and have to configure for your services work properly, below you can see all needed configuration:
|
853
|
-
|
854
|
-
```ruby
|
855
|
-
NiftyServices.config do |config|
|
856
|
-
# [optional - but very recommend! Please, do it]
|
857
|
-
# class used to control ACL
|
858
|
-
config.user_class = User
|
859
|
-
|
860
|
-
# [optional]
|
861
|
-
# global logger for all services
|
862
|
-
# [Default: Logger.new('/dev/null')]
|
863
|
-
config.logger = Logger.new('log/services_logger.log')
|
864
|
-
|
865
|
-
# [optional]
|
866
|
-
# Namespace to lookup when using concerns with services
|
867
|
-
# [Default: 'NitfyServices::Concerns']
|
868
|
-
config.service_concerns_namespace = "Services::V1::Concerns"
|
869
|
-
|
870
|
-
end
|
871
|
-
```
|
872
|
-
|
873
|
-
---
|
874
|
-
|
875
|
-
## Web Frameworks Integrations
|
876
|
-
|
877
|
-
### Rails <a name="frameworks-rails"></a>
|
878
|
-
|
879
|
-
You need a very minimal setup to integrate with your existing or new Rails application. I prefer to put my services files inside the `lib/services` folder, cause this allow better namespacing configuration over `app/services`, but this is up to you to decide.
|
880
|
-
|
881
|
-
First thing to do is add `lib/` folder in `autoload` path, place the following in your `config/application.rb`
|
882
|
-
|
883
|
-
```ruby
|
884
|
-
# config/application.rb
|
885
|
-
config.paths.add(File.join(Rails.root, 'lib'), glob: File.join('**', '*.rb'))
|
886
|
-
|
887
|
-
config.autoload_paths << Rails.root.join('lib')
|
888
|
-
```
|
889
|
-
|
890
|
-
Second, create `lib/services` directory:
|
891
|
-
|
892
|
-
`$ mkdir -p lib/services/v1/users`
|
893
|
-
|
894
|
-
Next, configure:
|
895
|
-
|
896
|
-
```ruby
|
897
|
-
NiftyServices.configure do |config|
|
898
|
-
config.user_class = User
|
899
|
-
end
|
900
|
-
```
|
901
|
-
**Note**: See [Configurations](#construction-configuration-construction) section to see all available configs
|
902
|
-
|
903
|
-
Create your first service:
|
904
|
-
|
905
|
-
```
|
906
|
-
$ touch lib/services/v1/users/create_service.rb
|
907
|
-
```
|
908
|
-
|
909
|
-
Use in your controller:
|
910
|
-
|
911
|
-
```ruby
|
912
|
-
class UsersController < BaseController
|
913
|
-
def create
|
914
|
-
service = Services::V1::Users::CreateService.new(params).execute
|
915
|
-
|
916
|
-
default_response = { status: service.response_status, status_code: service.response_status_code }
|
917
|
-
|
918
|
-
if service.success?
|
919
|
-
response = { user: service.user, subscription: service.subscription }
|
920
|
-
else
|
921
|
-
response = { error: true, errors: service.errors }
|
922
|
-
end
|
923
|
-
|
924
|
-
render json: default_response.merge(response), status: service.response_status
|
925
|
-
end
|
926
|
-
end
|
927
|
-
```
|
928
|
-
|
929
|
-
This can be even better if you move response code to a helper:
|
930
|
-
|
931
|
-
```ruby
|
932
|
-
# helpers/users_helper.rb
|
933
|
-
module UsersHelper
|
934
|
-
|
935
|
-
def response_for_user_create_service(service)
|
936
|
-
success_response = { user: service.user, subscription: service.subscription }
|
937
|
-
generic_response_for_service(service, success_response)
|
938
|
-
end
|
939
|
-
|
940
|
-
# THIS IS GREAT, you can use this method to standardize ALL of your
|
941
|
-
# endpoints responses, THIS IS SO FUCKING COOL!
|
942
|
-
def generic_response_for_service(service, success_response)
|
943
|
-
default_response = {
|
944
|
-
status: service.response_status,
|
945
|
-
status_code: service.response_status_code,
|
946
|
-
success: service.success?
|
947
|
-
}
|
948
|
-
|
949
|
-
if service.success?
|
950
|
-
response = success_response
|
951
|
-
else
|
952
|
-
response = {
|
953
|
-
error: true,
|
954
|
-
errors: service.errors
|
955
|
-
}
|
956
|
-
end
|
957
|
-
|
958
|
-
default_response.merge(response)
|
959
|
-
end
|
960
|
-
end
|
961
|
-
```
|
962
|
-
|
963
|
-
Changing controller again: (looks so readable now <3)
|
964
|
-
|
965
|
-
```ruby
|
966
|
-
# controllers/users_controller.rb
|
967
|
-
class UsersController < BaseController
|
968
|
-
def create
|
969
|
-
service = Services::V1::Users::CreateService.new(params).execute
|
970
|
-
|
971
|
-
render json: response_for_user_create_service(service), status: service.response_status
|
972
|
-
end
|
973
|
-
end
|
974
|
-
```
|
975
|
-
|
976
|
-
Well done sir! Did you read the comments in `generic_response_for_service`? Read it and think a little about this and prepare yourself for having orgasms when you realize how fuck awesome this will be for your API's. Need mode? Checkout [Sample Standartized API with NiftyServices Repository](http://github.com/fidelisrafael/nifty_services-api_sample)
|
977
|
-
|
978
|
-
---
|
979
|
-
|
980
|
-
### Grape/Sinatra/Padrino/Hanami/Rack <a name="frameworks-rack"></a>
|
981
|
-
|
982
|
-
Well, the integration here don't variate too much from Rails, just follow the steps:
|
983
|
-
|
984
|
-
**1 -** Decide where you'll put your services
|
985
|
-
**2 -** Code that dam amazing services!
|
986
|
-
**3 -** Instantiate the service in your framework entry point
|
987
|
-
**4 -** Create helpers to handle service response
|
988
|
-
**5 -** Be happy and go party!
|
989
|
-
|
990
|
-
Need examples? Check out one of the following repositories:
|
991
|
-
|
992
|
-
NiftyServices - Rails Sample
|
993
|
-
NiftyServices - Grape Sample
|
994
|
-
NiftyServices - Sinatra Sample
|
995
|
-
|
996
|
-
---
|
997
|
-
|
998
|
-
## :pray: Basic Service Markups :raised_hands:
|
999
|
-
|
1000
|
-
Here, for your convenience and sanity all basic service structures for reference when you start a brand new Service.
|
1001
|
-
Most of time, the best way is to copy all content from each service described below and change according to your needs.
|
1002
|
-
|
1003
|
-
### BaseCreateService Basic Markup
|
1004
|
-
|
1005
|
-
```ruby
|
1006
|
-
class SomeCreateService < NiftyServices::BaseCreateService
|
1007
|
-
|
1008
|
-
# [Required]
|
1009
|
-
# remember that inside the Service you always can use
|
1010
|
-
# @record variable to access current record
|
1011
|
-
# and from outside (service instance):
|
1012
|
-
# service.record or service.record_type
|
1013
|
-
# eg:
|
1014
|
-
# record_type BlogPost
|
1015
|
-
# service.record # BlogPost.new(...)
|
1016
|
-
# service.blog_post # BlogPost.new(...)
|
1017
|
-
# service.record == service.blog_post # true
|
1018
|
-
# alias_name can be used to create a custom alias name
|
1019
|
-
# eg:
|
1020
|
-
# record_type BlogPost, alias_name: :post
|
1021
|
-
# service.record # BlogPost.new(...)
|
1022
|
-
# service.post # BlogPost.new(...)
|
1023
|
-
# service.record == service.post # true
|
1024
|
-
|
1025
|
-
record_type RecordType, alias_name: :my_custom_alias_name
|
1026
|
-
|
1027
|
-
private
|
1028
|
-
# [Required]
|
1029
|
-
# Always validate if @user can create the current record_type
|
1030
|
-
# If this method is not implemented a NotImplementedError exception will be raised
|
1031
|
-
def user_can_create_record?
|
1032
|
-
return forbidden_error!('errors.some_error') if (some_validation)
|
1033
|
-
|
1034
|
-
return bad_request_error!('errors.some_other_error') if (another_validation)
|
1035
|
-
|
1036
|
-
# remember to return true after all validations
|
1037
|
-
# if you don't return true Service will not be able to create the record
|
1038
|
-
return true
|
1039
|
-
end
|
1040
|
-
|
1041
|
-
# [Optional]
|
1042
|
-
# method called when save_error method call raises an exception
|
1043
|
-
# this ocurr for example with ActiveRecord objects
|
1044
|
-
# default: unprocessable_entity_error!(error)
|
1045
|
-
def on_save_record_error(error)
|
1046
|
-
logger.error(error)
|
1047
|
-
if error.is_a?(ActiveRecord::RecordNotUnique)
|
1048
|
-
return unprocessable_entity_error!(%s(posts.duplicate_record))
|
1049
|
-
end
|
1050
|
-
end
|
1051
|
-
|
1052
|
-
# [Optional]
|
1053
|
-
# determine wheter user will be validate as valid object before
|
1054
|
-
# record creation
|
1055
|
-
# (default: true)
|
1056
|
-
def validate_user?
|
1057
|
-
return true
|
1058
|
-
end
|
1059
|
-
|
1060
|
-
# [Optional]
|
1061
|
-
# custom scope for record, eg: @user.posts
|
1062
|
-
# default is nil
|
1063
|
-
def build_record_scope
|
1064
|
-
end
|
1065
|
-
end
|
1066
|
-
```
|
1067
|
-
|
1068
|
-
### BaseUpdateService Basic Markup
|
1069
|
-
|
1070
|
-
```ruby
|
1071
|
-
class SomeUpdateService < NiftyServices::BaseUpdateService
|
1072
|
-
|
1073
|
-
# [Required]
|
1074
|
-
record_type RecordType, alias_name: :custom_alias_name
|
1075
|
-
|
1076
|
-
WHITELIST_ATTRIBUTES = [
|
1077
|
-
:safe_attribute_1,
|
1078
|
-
:safe_attribute_2,
|
1079
|
-
]
|
1080
|
-
|
1081
|
-
private
|
1082
|
-
# [Required]
|
1083
|
-
# When a new instance of Service is created, the @options variables receive some
|
1084
|
-
# values, eg: { user: { email: "...", name: "...."} }
|
1085
|
-
# use record_attributes_hash to tell the Service from where to pull theses values
|
1086
|
-
# eg: @options.fetch(:user, {})
|
1087
|
-
# If this method is not implemented a NotImplementedError exception will be raised
|
1088
|
-
def record_attributes_hash
|
1089
|
-
@options.fetch(options_key, {})
|
1090
|
-
end
|
1091
|
-
|
1092
|
-
# [Required]
|
1093
|
-
# whitelisted attributes (must be an Array) which can be updated by this Service
|
1094
|
-
# If this method is not implemented a NotImplementedError exception will be raised
|
1095
|
-
def record_attributes_whitelist
|
1096
|
-
WHITELIST_ATTRIBUTES
|
1097
|
-
end
|
1098
|
-
|
1099
|
-
# [required]
|
1100
|
-
# This is a VERY IMPORTANT point of attention
|
1101
|
-
# always verify if @user has permissions to update the current @record object
|
1102
|
-
# Hint: if @record respond_to `user_can_update?(user)` you can remove this
|
1103
|
-
# method and do the validation inside `user_can_update(user)` method in @record
|
1104
|
-
# If this method is not implemented a NotImplementedError exception will be raised
|
1105
|
-
def user_can_update_record?
|
1106
|
-
@record.user_id == @user.id
|
1107
|
-
end
|
1108
|
-
|
1109
|
-
|
1110
|
-
# [Optional]
|
1111
|
-
# This is the default implementation of update record, you may overwrite it
|
1112
|
-
# to to custom updates (MOST OF TIME YOU DONT NEED TO DO THIS)
|
1113
|
-
# only change this if you know what you are really doing
|
1114
|
-
def update_record
|
1115
|
-
@record.class.send(:update, @record.id, record_allowed_attributes)
|
1116
|
-
end
|
1117
|
-
|
1118
|
-
|
1119
|
-
# [optional]
|
1120
|
-
# Any callback is optional, this is just a example
|
1121
|
-
def after_success
|
1122
|
-
if changed?
|
1123
|
-
logger.info 'Successfully update record ID %s' % @record.id
|
1124
|
-
logger.info 'Changed attributes are %s' % changed_attributes
|
1125
|
-
end
|
1126
|
-
end
|
1127
|
-
end
|
1128
|
-
```
|
1129
|
-
|
1130
|
-
---
|
1131
|
-
|
1132
|
-
### BaseDeleteService Basic Markup
|
1133
|
-
|
1134
|
-
```ruby
|
1135
|
-
class SomeDeleteService < NiftyServices::BaseDeleteService
|
1136
|
-
|
1137
|
-
# [Required]
|
1138
|
-
record_type RecordType, alias_name: :custom_alias_name
|
1139
|
-
|
1140
|
-
private
|
1141
|
-
|
1142
|
-
# [Required]
|
1143
|
-
# This is a VERY IMPORTANT point of attention
|
1144
|
-
# always verify if @user has permissions to delete the current @record object
|
1145
|
-
# Hint: if @record respond_to `user_can_delete?(user)` you can remove this
|
1146
|
-
# method and do the validation inside `user_can_delete(user)` method in @record
|
1147
|
-
# If this method is not implemented a NotImplementedError exception will be raised
|
1148
|
-
|
1149
|
-
def user_can_delete_record?
|
1150
|
-
@record.user_id == @user.id
|
1151
|
-
end
|
1152
|
-
|
1153
|
-
# [optional]
|
1154
|
-
# Any callback is optional, this is just a example
|
1155
|
-
def after_success
|
1156
|
-
logger.info('Successfully Deleted resource ID %s' % @record.id)
|
1157
|
-
end
|
1158
|
-
|
1159
|
-
# [Optional]
|
1160
|
-
# This is the default implementation of delete record, you may overwrite it
|
1161
|
-
# to do custom delete (MOST OF TIME YOU DONT NEED TO DO THIS)
|
1162
|
-
# only change this if you know what you are really doing
|
1163
|
-
def destroy_record
|
1164
|
-
@record.try(:destroy) || @record.try(:delete)
|
1165
|
-
end
|
1166
|
-
|
1167
|
-
end
|
1168
|
-
|
1169
|
-
```
|
1170
|
-
|
1171
|
-
### BaseActionService Basic Markup
|
1172
|
-
|
1173
|
-
```ruby
|
1174
|
-
class SomeCustomActionService < NiftyServices::BaseActionService
|
1175
|
-
|
1176
|
-
# [required]
|
1177
|
-
# this is the action identifier used internally
|
1178
|
-
# and to generate error messages
|
1179
|
-
# see: invalid_action_error_key method
|
1180
|
-
action_name :custom_action_name
|
1181
|
-
|
1182
|
-
private
|
1183
|
-
# [Required]
|
1184
|
-
# Always validate if Service can execute the action
|
1185
|
-
# This method MUST return a boolean value indicating if Service can or not
|
1186
|
-
# run the method `execute_service_action`
|
1187
|
-
# If this method is not implemented a NotImplementedError exception will be raised
|
1188
|
-
def user_can_execute_action?
|
1189
|
-
# do some specific validation here, you can return errors such:
|
1190
|
-
# return not_found_error!(%(users.invalid_user)) # returns false and avoid execution
|
1191
|
-
return true
|
1192
|
-
end
|
1193
|
-
|
1194
|
-
# [Required]
|
1195
|
-
# The core function of BaseActionServices
|
1196
|
-
# This method is called when all validations passes, so here you can put
|
1197
|
-
# all logic for Service (eg: send mails, clear logs, any kind of action you want)
|
1198
|
-
# If this method is not implemented a NotImplementedError exception will be raised
|
1199
|
-
def execute_service_action
|
1200
|
-
# (do some complex stuff)
|
1201
|
-
end
|
1202
|
-
|
1203
|
-
# You dont need to overwrite this method, just `record_error_key`
|
1204
|
-
# But it's important you know how final message key will be created
|
1205
|
-
# using the pattern below
|
1206
|
-
def invalid_action_error_key
|
1207
|
-
"#{record_error_key}.cant_execute_#{action_name}"
|
1208
|
-
end
|
1209
|
-
|
1210
|
-
# [Required]
|
1211
|
-
# Key used to created the error messages for this Service
|
1212
|
-
# If this method is not implemented a NotImplementedError exception will be raised
|
1213
|
-
def record_error_key
|
1214
|
-
:users
|
1215
|
-
end
|
1216
|
-
end
|
1217
|
-
```
|
1218
|
-
|
1219
|
-
|
1220
|
-
---
|
1221
|
-
|
1222
|
-
## Full Public API methods list
|
1223
|
-
|
1224
|
-
You can use any of the methods above with your `services instances`:
|
1225
|
-
|
1226
|
-
```ruby
|
1227
|
-
service.success? # boolean
|
1228
|
-
service.fail? # boolean
|
1229
|
-
|
1230
|
-
service.errors # hash
|
1231
|
-
service.add_error(error) # array
|
1232
|
-
|
1233
|
-
service.response_status # symbol (eg: :ok)
|
1234
|
-
service.response_status_code # integer (eg: 200)
|
1235
|
-
|
1236
|
-
service.changed_attributes # array
|
1237
|
-
service.changed? # boolean
|
1238
|
-
|
1239
|
-
service.callback_fired?(callback_name) # boolean
|
1240
|
-
service.register_callback(name, method, &block) # nil
|
1241
|
-
service.register_callback_action(&block) # nil
|
1242
|
-
|
1243
|
-
service.option_exists?(option_name) # boolean
|
1244
|
-
service.option_enabled?(option_name) # boolean
|
1245
|
-
service.option_disabled?(option_name) # boolean
|
1246
|
-
```
|
1247
|
-
|
1248
|
-
---
|
1249
|
-
|
1250
|
-
## :question: CLI Generators <a name="cli-generators"></a>
|
1251
|
-
|
1252
|
-
Currently NiftyServices don't have CLI(command line interface) generators, but is in the roadmap, so keep your eyes here!
|
1253
|
-
|
1254
|
-
---
|
1255
|
-
|
1256
126
|
## :calendar: Roadmap <a name="roadmap"></a>
|
1257
127
|
|
1258
|
-
- :white_medium_small_square: Remove ActiveSupport dependency
|
1259
128
|
- :white_medium_small_square: Create CLI Generators
|
1260
129
|
- :white_medium_small_square: Beter documentation for `BaseActionService`
|
1261
130
|
- :white_medium_small_square: Write Sample Applications
|
@@ -1263,7 +132,7 @@ Currently NiftyServices don't have CLI(command line interface) generators, but i
|
|
1263
132
|
- :white_medium_small_square: Write better tests for `BaseActionServices`
|
1264
133
|
- :white_medium_small_square: Write tests for Configuration
|
1265
134
|
- :white_medium_small_square: Write tests for Callbacks
|
1266
|
-
|
135
|
+
|
1267
136
|
---
|
1268
137
|
|
1269
138
|
## :computer: Development
|
@@ -1282,4 +151,4 @@ Bug reports and pull requests are welcome on GitHub at http://github.com/fidelis
|
|
1282
151
|
|
1283
152
|
## :memo: License
|
1284
153
|
|
1285
|
-
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
154
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|