nifty_services 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4b51ddb57c9b14189952188821a22c36ea9b7c35
4
- data.tar.gz: 1bf6174e4899e88817ea3ec77db991d2cf249cdc
3
+ metadata.gz: 487b8be2050ec50ec28c0129defd800f1f94885b
4
+ data.tar.gz: 6e05b7663023277532c301820b32acc2da5c1580
5
5
  SHA512:
6
- metadata.gz: b098ca14f8ad2789d6dc7e0fc4026452a79d30697f241d31d6b74dfb7397e50b89df681e7648648550e66b66e2f2ec52605dc96359a741ead9c4ebf456645024
7
- data.tar.gz: ee3633820cc0690f469acd67a74c3ff63d6f86d19f08f92b4b09d26296cc448cfb003159295485ab5c4a47532defc8ca7072c302e523ee2cdbfcd7a69db251c8
6
+ metadata.gz: f3808572625a8be8a29d066bd928b766dbfbca794cc63e0c0bb7fd18e17b8e31d02bc201eb1aa49a961d340a62245242797a597751e3eec285e6b0ba36d5f5f9
7
+ data.tar.gz: 9dc671f34a2c6e2e8c5f5ee1103af341429da09c950b37e2f92c037ab06f7ae4247263db0a40ff9bf768dd7a9f72edbbd4f21581ae394fce84efae380e6eae7c
@@ -1,4 +1,8 @@
1
+ bundler_args: --retry=3 --jobs=3
2
+ cache: bundler
1
3
  language: ruby
4
+ sudo: false
2
5
  rvm:
3
- - 2.2.1
4
- before_install: gem install bundler -v 1.10.4
6
+ - 2.2.2
7
+ script:
8
+ - bundle exec rspec
data/README.md CHANGED
@@ -1,76 +1,25 @@
1
- # NiftyServices
1
+ # NiftyServices
2
2
 
3
+ [![Build Status](https://travis-ci.org/fidelisrafael/nifty_services.svg)](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
- * &#9745; All services classes must inherit from `NiftyServices::BaseService`
300
-
301
- * &#9745; For convention(but not a rule) all services must expose only `execute`(and of course, `initialize`) as public methods.
302
-
303
- * &#9745; `execute_action(&block)` **MUST** be called to properly setup things in execution context.
304
-
305
- * &#9745; `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
- * &#9745; There's a very simple DSL for marking result as success/fail (eg: `unprocessable_entity_error!` or `success_response`).
309
-
310
- * &#9745; 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).