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.
@@ -0,0 +1,142 @@
1
+ # NiftyServices documentation
2
+
3
+ ---
4
+
5
+ ## Services Public API
6
+
7
+ Below, a list of most common public accessible methods for any instance of service:
8
+ (Detailed usage and full API list is available below this section)
9
+
10
+ ```ruby
11
+ service.success? # boolean
12
+ service.fail? # boolean
13
+ service.errors # array
14
+ service.response_status # symbol (eg: :ok)
15
+ service.response_status_code # integer (eg: 200)
16
+ ```
17
+
18
+ So, grabbing our `DailyNewsMailSendService` service again, we could do:
19
+
20
+ ```ruby
21
+ service = DailyNewsMailSendService.new(User.new('test', 'test@test.com'))
22
+ service.execute
23
+
24
+ if service.success? # or unless service.fail?
25
+ SentEmails.create(user: service.user, type: 'welcome_email')
26
+ else
27
+ puts 'Error sending email, details below:'
28
+ puts 'Status: %s' % service.response_status
29
+ puts 'Status code: %s' % service.response_status_code
30
+ puts service.errors
31
+ end
32
+
33
+ # trying to re-execute the service will return `nil`
34
+ service.execute
35
+ ```
36
+
37
+ 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.
38
+
39
+ ---
40
+
41
+ ## Success & Error Responses
42
+
43
+ ### :white_check_mark: Handling Success :zap:
44
+
45
+ To mark a service running as successfully, you must call one of this methods (preferencially inside of `execute_action` block):
46
+
47
+ * `success_response # [200, :ok]`
48
+ * `success_created_response [201, :created]`
49
+
50
+ 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`.
51
+
52
+ ---
53
+
54
+
55
+ ### :red_circle: Handling Error :boom:
56
+
57
+ By default, all services comes with following error methods:
58
+ (**Hint**: See all available error methods [`here`](lib/nifty_services/configuration.rb#L10-L16))
59
+
60
+ ```ruby
61
+ bad_request_error(message_key) # set response_status_code to 400
62
+
63
+ not_authorized_error(message_key) # set response_status_code to 401,
64
+
65
+ forbidden_error(message_key) # set response_status_code to 403,
66
+
67
+ not_found_error(message_key) # set response_status_code to 404,
68
+
69
+ unprocessable_entity_error(message_key) # set response_status_code to 422,
70
+
71
+ internal_server_error(message_key) # set response_status_code to 500,
72
+
73
+ not_implemented_error(message_key) # set response_status_code to 501
74
+ ```
75
+
76
+ Beside this methods, you can always use **low level** API to generate errors, just call the `error` method, ex:
77
+
78
+ ```ruby
79
+ # API
80
+ error(status, message_key, options = {})
81
+
82
+ # eg:
83
+ error(409, :conflict_error, reason: 'unkown')
84
+ error!(409, :conflict_error, reason: 'unkown')
85
+
86
+ # suppose you YML locale file have the configuration:
87
+ # nifty_services:
88
+ # errors:
89
+ # conflict_error: 'Conflict! The reason is %{reason}'
90
+ ```
91
+
92
+ #### Custom error response methods
93
+
94
+ But you can always add new convenience errors methods via API, this way you will have more expressivity and sintax sugar:
95
+
96
+ ```ruby
97
+ ## API
98
+ NiftyServices.add_response_error_method(status, status_code)
99
+
100
+ ## eg:
101
+
102
+ NiftyServices.add_response_error_method(:conflict, 409)
103
+
104
+ ## now you have the methods:
105
+
106
+ ## conflict_error(:conflict_error)
107
+ ## conflit_error!(:conflict_error)
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Full Public API methods list
113
+
114
+ You can use any of the methods above with your `services instances`:
115
+
116
+ ```ruby
117
+ service.success? # boolean
118
+ service.fail? # boolean
119
+
120
+ service.errors # hash
121
+ service.add_error(error) # array
122
+
123
+ service.response_status # symbol (eg: :ok)
124
+ service.response_status_code # integer (eg: 200)
125
+
126
+ service.changed_attributes # array
127
+ service.changed? # boolean
128
+
129
+ service.callback_fired?(callback_name) # boolean
130
+ service.register_callback(name, method, &block) # nil
131
+ service.register_callback_action(&block) # nil
132
+
133
+ service.option_exists?(option_name) # boolean
134
+ service.option_enabled?(option_name) # boolean
135
+ service.option_disabled?(option_name) # boolean
136
+ ```
137
+
138
+ ---
139
+
140
+ ### Next
141
+
142
+ See [Crud Services](./crud_services.md)
@@ -0,0 +1,67 @@
1
+ # NiftyServices documentation
2
+
3
+ ---
4
+
5
+ ## Callbacks
6
+
7
+ Here the most common callbacks list you can use to hook actions in run-time:
8
+ (**Hint**: See all existent callbacks definitions in [`extensions/callbacks.rb`](../lib/nifty_services/extensions/callbacks.rb#L7-L22) file)
9
+
10
+ ```
11
+ - before_initialize
12
+ - after_initialize
13
+ - before_execute
14
+ - after_execute
15
+ - before_error
16
+ - after_error
17
+ - before_success
18
+ - after_success
19
+ ```
20
+
21
+
22
+
23
+ ### Creating custom Callbacks
24
+
25
+ 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:
26
+
27
+ ```ruby
28
+ # Some monkey patch :)
29
+
30
+ NiftyServices::BaseCreateService.class_eval do
31
+ ORIGIN_WHITELIST_ATTRIBUTES = [:provider, :locale, :user_agent, :ip]
32
+
33
+ def origin_params(params = {})
34
+ filter_hash(params.fetch(:origin, {}).to_h, ORIGIN_WHITELIST_ATTRIBUTES)
35
+ end
36
+
37
+ def create_origin(originable, params = {})
38
+ return unless originable.respond_to?(:create_origin)
39
+ return unless create_origin?
40
+
41
+ originable.create_origin(origin_params(params))
42
+ end
43
+
44
+ # for records which we don't need to create origins, just
45
+ # overwrite this method inside service class turning it off with:
46
+ # return false
47
+ def create_origin?
48
+ Application::Config.create_origin_for_records
49
+ end
50
+ end
51
+
52
+ # This register a callback for ALL services who inherit from `NiftyServices::BaseCreateService`
53
+ # In other words: Every and all records created in my application will be tracked
54
+ # I can believe that is easy like this, I need a beer right now!
55
+ NiftyServices::BaseCreateService.register_callback(:after_success, :create_origin_for_record) do
56
+ create_origin(@record, @options)
57
+ end
58
+
59
+ ```
60
+
61
+ Now, every record created in my application will have an associated `origin` object, really simple and cool!
62
+
63
+ ---
64
+
65
+ ### Next
66
+
67
+ See [Configuration](./configuration.md)
@@ -0,0 +1,13 @@
1
+ # NiftyServices documentation
2
+
3
+ ---
4
+
5
+ ## :question: CLI Generators <a name="cli-generators"></a>
6
+
7
+ Currently NiftyServices don't have CLI(command line interface) generators, but is in the roadmap, so keep your eyes here!
8
+
9
+ ---
10
+
11
+ ### Next
12
+
13
+ See [Install on your Ruby Application](../README.md#installation)
@@ -0,0 +1,62 @@
1
+ # NiftyServices documentation
2
+
3
+ ---
4
+
5
+ ## :construction: Configuration :construction:
6
+
7
+ 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:
8
+
9
+ ```ruby
10
+ NiftyServices.config do |config|
11
+ # [optional]
12
+ # global logger for all services
13
+ # [Default: Logger.new('/dev/null')]
14
+ config.logger = Logger.new('log/services_logger.log')
15
+
16
+ # [Optional - Default: `'nifty_services'`]
17
+ # Set a custom I18n lookup namespace for error messages
18
+ config.i18n_namespace = 'my_app'
19
+
20
+ # [Optional - Default: `save`]
21
+ # Set the method called when saving a record using `BaseCreateService`
22
+ # The default value is `save`, this will call:`record.save`
23
+ # Eg: If you want to use `#persist`(`record.persist`), use:
24
+ config.save_record_method = :persist
25
+
26
+ # But you can pass any object that responds to `#call` method, like a Proc:
27
+ # This way, NiftyServices will call the method sending the record as argument.
28
+ config.save_record_method = ->(record) {
29
+ record.save_in_database!
30
+ }
31
+
32
+ # [Optional - Default: `delete`]
33
+ # Set the method called when deleting a record using BaseDeleteService
34
+ # The default value is `delete`, this will call:`record.delete`
35
+ # Eg: If you want to use `#destroy`(`record.destroy`), use:
36
+ config.delete_record_method = :destroy
37
+
38
+ # But you can pass any object that responds to `#call` method, like a Proc:
39
+ # This way, NiftyServices will call the method sending the record as argument.
40
+ config.delete_record_method = ->(record) {
41
+ record.remove
42
+ }
43
+
44
+ # [Optional - Default: `update`]
45
+ # Set the method called when updating a record using BaseUpdateService
46
+ # The default value is `update`, this will call:`record.update(attributes)`
47
+ # Eg: If you want to use `sync`(`record.sync(attributes)`), use:
48
+ config.update_record_method = :sync
49
+
50
+ # But you can pass any object that responds to `#call` method, like a Proc:
51
+ # This way, NiftyServices will call this block sending the record and attributes as arguments.
52
+ config.update_record_method = ->(record, attributes) {
53
+ record.update_attributes(attributes)
54
+ }
55
+ end
56
+ ```
57
+
58
+ ---
59
+
60
+ ### Next
61
+
62
+ See [Web Frameworks Integration](./webframeworks_integration.md)
@@ -0,0 +1,534 @@
1
+ # NiftyServices documentation
2
+
3
+ ---
4
+
5
+ ## CRUD Services
6
+
7
+ 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.
8
+
9
+ Follow an example of **Create, Update and Delete** CRUD services for `Post` resource:
10
+
11
+ ## :white_check_mark: CRUD: Create
12
+
13
+ By default, NiftyServices expect that record responds to `new` class method. (eg: `Post.new`).
14
+
15
+ NiftyServices will expect that record respond to `#save` instance method when trying to save the record after creating it, you can override this behavior using the method `save_record` or using global configuration:
16
+
17
+ ```ruby
18
+ NiftyServices.config do |config|
19
+ # The default value is `save`, this will call:`record.save`
20
+ # Eg: If you want to use `#persist`(`record.persist`), use:
21
+ config.save_record_method = :persist
22
+
23
+ # But you can pass any object that responds to `#call` method, like a Proc:
24
+ # This way, NiftyServices will call the method sending the record as argument.
25
+ config.save_record_method = ->(record) {
26
+ record.save_in_database!
27
+ }
28
+ end
29
+ ```
30
+
31
+
32
+ ```ruby
33
+ class PostCreateService < NiftyServices::BaseCreateService
34
+
35
+ attr_reader :user
36
+
37
+ # You can freely override initialize method to receive more arguments
38
+ def initialize(user, options = {})
39
+ @user = user
40
+ super(options)
41
+ end
42
+
43
+ # record_type must be a object respond to :build and :save methods
44
+ # is possible to access this record outside of service using
45
+ # `service.record` or `service.post`
46
+ # if you want to create a custom alias name, use:
47
+ # record_type Post, alias_name: :user_post
48
+ # This way, you can access the record using
49
+ # `service.user_post`
50
+
51
+ record_type Post
52
+
53
+ WHITELIST_ATTRIBUTES = [:title, :content]
54
+
55
+ whitelist_attributes WHITELIST_ATTRIBUTES
56
+
57
+
58
+ # use custom scope to create the record
59
+ # scope returned below must respond_to :build method
60
+ def build_record_scope
61
+ @user.posts
62
+ end
63
+
64
+ # this key is used for I18n translations, you don't need to override or implement
65
+ # NiftyService will try to inflect this using `record_type.to_s.underscore + 's'`
66
+ # So, if your record type is `Post`, `record_error_key` will be `posts`
67
+ def record_error_key
68
+ :posts
69
+ end
70
+
71
+ # This method is strict required by NiftyServices, each service must implement
72
+ def can_create_record?
73
+ # Checking user before trying to create a post for this same user
74
+ unless valid_user?
75
+ return not_found_error!('users.not_found')
76
+ end
77
+
78
+ return !duplicated?
79
+ end
80
+
81
+ def duplicated?
82
+ # (here you can do any kind of validation, eg:)
83
+ # check if user is trying to recreate a recent resource
84
+ # this will return false if user has already created a post with
85
+ # this title in the last 30 seconds (usefull to ban bots)
86
+ @user.posts.exists(title: record_allowed_attributes[:title], created_at: "NOW() - interval(30 seconds)")
87
+ end
88
+
89
+ # This is a custom method of this class, not NiftyService stuff
90
+ def valid_user?
91
+ # `valid_object?` signature: `valid_object?(object, expected_class)`
92
+ valid_object?(@user, User)
93
+ end
94
+ end
95
+
96
+ service = PostCreateService.new(User.first, title: 'Teste', content: 'Post example content')
97
+
98
+ service.execute
99
+
100
+ service.success? # true
101
+ service.response_status_code # 201
102
+ service.response_status # :created
103
+ ```
104
+
105
+
106
+
107
+ #### :earth_americas: I18n setup
108
+
109
+ You must have the following keys setup up in your locales files:
110
+
111
+ ```yml
112
+ nifty_services:
113
+ users:
114
+ not_found: "Invalid or not found user"
115
+ ip_temporarily_blocked: "This IP is temporarily blocked from creating records"
116
+ # note: posts is the key return in `record_error_key` service method
117
+ posts:
118
+ cant_create: "Can't create this record"
119
+ ```
120
+
121
+ #### :alien: Invalid user <a name="create-resource-user-invalid"></a>
122
+
123
+ If you try to create a post for a invalid user, such as:
124
+
125
+ ```ruby
126
+ # PostCreateService.new(user, options)
127
+ service = PostCreateService.new(nil, options)
128
+ service.execute
129
+
130
+ service.success? # false
131
+ service.response_status # :not_found
132
+ service.response_status_code # 404
133
+ service.errors # ["Invalid or not found user"]
134
+ ```
135
+
136
+ #### :no_entry_sign: Not authorized to create
137
+
138
+ Or if user is trying to create a duplicate resource:
139
+
140
+ ```ruby
141
+ # PostCreateService.new(user, options)
142
+ service = PostCreateService.new(User.first, options)
143
+ service.execute
144
+
145
+ service.success? # false
146
+ service.errors # ["User cant create this record"]
147
+ service.response_status # :forbidden_error
148
+ service.response_status_code # 400
149
+ ```
150
+
151
+ #### :boom: Record is invalid
152
+
153
+ Eg: if any validation in Post model won't pass:
154
+
155
+ ```ruby
156
+ # PostCreateService.new(user, options)
157
+ # Post model as the validation:
158
+ # validates_presence_of :title, :content
159
+ service = PostCreateService.new(User.first, title: nil, content: nil)
160
+ service.execute
161
+
162
+ service.success? # false
163
+
164
+ service.errors # => [{ title: 'is empty', content: 'is empty' }]
165
+
166
+ service.response_status # :unprocessable_entity
167
+ service.response_status_code # 422
168
+ ```
169
+ ---
170
+
171
+ ## :white_check_mark: CRUD: Update
172
+
173
+ By default, NiftyServices expect that record responds to `update` class method. (eg: `Post.update(id, data)`)
174
+
175
+ You can override this behavior using `update_record` method in your service class, or using global configuration:
176
+
177
+ ```ruby
178
+ NiftyServices.config do |config|
179
+ # Set the method called when updating a record using BaseUpdateService
180
+ # Eg: If you want to use `sync`(`record.sync(attributes)`), use:
181
+ config.update_record_method = :sync
182
+
183
+ # But you can pass any object that responds to `#call` method, like a Proc:
184
+ # This way, NiftyServices will call the method sending the record and attributes.
185
+ config.update_record_method = ->(record, attributes) {
186
+ record.update_attributes(attributes)
187
+ }
188
+ end
189
+ ```
190
+
191
+ For validation, NiftyServices expect that record respond to `#valid?` and `#errors` instance methods.
192
+
193
+ You can override this behavior using `success_updated?` and `update_errors` methods.
194
+
195
+
196
+ ```ruby
197
+ class PostUpdateService < NiftyServices::BaseUpdateService
198
+
199
+ attr_reader :user
200
+
201
+ # service.post or service.record
202
+ record_type Post
203
+
204
+ WHITELIST_ATTRIBUTES = [:title, :content]
205
+
206
+ whitelist_attributes WHITELIST_ATTRIBUTES
207
+
208
+ # You can freely override initialize method to receive more arguments
209
+ def initialize(record, user, options = {})
210
+ @user = user
211
+ super(record, options)
212
+ end
213
+
214
+ # This method is strict required by NiftyServices, each service must implement
215
+ def can_update_record?
216
+ unless valid_user?
217
+ return not_found_error!('users.not_found')
218
+ end
219
+
220
+ return user_has_permission?
221
+ end
222
+
223
+ def user_has_permission?
224
+ # only system admins and owner can update this record
225
+ # or you can transfer the logic below to something like:
226
+ # @record.user_can_update_record?(@user)
227
+ @user.admin? || @user.id == @record.id
228
+ end
229
+
230
+ # this key is used for I18n translations, you don't need to override or implement
231
+ def record_error_key
232
+ :posts
233
+ end
234
+
235
+ # This is a custom method of this class, not NiftyService stuff
236
+ def valid_user?
237
+ valid_object?(@user, User)
238
+ end
239
+ end
240
+
241
+ # :user_id will be ignored since it's not in whitelisted attributes
242
+ # this can safe yourself from parameter inject attacks, by default
243
+ update_service = PostUpdateService.new(Post.first, User.first, title: 'Changing title', content: 'Updating content', user_id: 2)
244
+
245
+ update_service.execute
246
+
247
+ update_service.success? # true
248
+ update_service.response_status # :ok
249
+ update_service.response_status_code # 200
250
+
251
+ update_service.changed_attributes # [:title, :content]
252
+ update_service.changed? # true
253
+ ```
254
+
255
+ #### :earth_asia: I18n setup
256
+
257
+ Your locale file must have the following keys:
258
+
259
+ ```yml
260
+ posts:
261
+ not_found: "Invalid or not found post"
262
+ cant_update: "Can't update this record"
263
+ users:
264
+ not_found: "Invalid or not found user"
265
+ ```
266
+
267
+ #### :alien: User is invalid <a name="update-resource-user-invalid"></a>
268
+
269
+ Response when owner user is not valid:
270
+
271
+ ```ruby
272
+ # PostUpdateService.new(post, user, params)
273
+ update_service = PostUpdateService.new(Post.first, nil, title: 'Changing title', content: 'Updating content')
274
+
275
+ update_service.execute
276
+
277
+ update_service.success? # false
278
+ update_service.response_status # :not_found_error
279
+ update_service.response_status_code # 404
280
+
281
+ update_service.errors # ["Invalid or not found user"]
282
+ ```
283
+
284
+ #### :closed_lock_with_key: Resource (Post) don't belongs to user <a name="update-resource-dont-belongs-to-user"></a>
285
+
286
+ Responses when trying to update to update a resource who don't belongs to owner:
287
+
288
+ ```ruby
289
+ # PostUpdateService.new(post, user, params)
290
+ update_service = PostUpdateService.new(Post.first, User.last, title: 'Changing title', content: 'Updating content')
291
+
292
+ update_service.execute
293
+
294
+ update_service.success? # false
295
+ update_service.response_status # :forbidden
296
+ update_service.response_status_code # 400
297
+
298
+ update_service.changed_attributes # []
299
+ update_service.changed? # false
300
+ update_service.errors # ["User can't update this record"]
301
+ ```
302
+
303
+ #### :santa: Resource don't exists <a name="update-resource-dont-exists"></a>
304
+
305
+ Response when post don't exists:
306
+
307
+ ```ruby
308
+ # PostUpdateService.new(post, user, params)
309
+ update_service = PostUpdateService.new(nil, User.last, title: 'Changing title', content: 'Updating content')
310
+
311
+ update_service.execute
312
+
313
+ update_service.success? # false
314
+ update_service.response_status # :not_found_error
315
+ update_service.response_status_code # 404
316
+
317
+ update_service.errors # ["Invalid or not found post"]
318
+ ```
319
+
320
+ ---
321
+
322
+ ## :white_check_mark: CRUD: Delete
323
+
324
+
325
+ By default, NiftyServices expect that record responds to `delete` instance method. (eg: `post.delete`)
326
+
327
+ You can override this behavior using `delete_record` method in your Service class, or using global configuration:
328
+
329
+ ```ruby
330
+ NiftyServices.config do |config|
331
+ # The default value is `delete`, this will call:`record.delete`
332
+ # Eg: If you want to use `#destroy`(`record.destroy`), use:
333
+ config.delete_record_method = :destroy
334
+
335
+ # But you can pass any object that responds to `#call` method, like a Proc:
336
+ # This way, NiftyServices will call the method sending the record
337
+ config.delete_record_method = ->(record) {
338
+ record.remove
339
+ }
340
+ end
341
+ ```
342
+
343
+
344
+ ```ruby
345
+ class PostDeleteService < NiftyServices::BaseDeleteService
346
+
347
+ attr_reader :user
348
+
349
+ # record_type object must respond to :delete method
350
+ # But you can override `delete_method` method to do whatever you want
351
+ record_type Post
352
+
353
+ # You can freely override initialize method to receive more arguments
354
+ def initialize(record, user, options = {})
355
+ @user = user
356
+ super(record, options)
357
+ end
358
+
359
+ # this key is used for I18n translations, you don't need to override or implement
360
+ def record_error_key
361
+ :posts
362
+ end
363
+
364
+ # below the code used internally, you can override to
365
+ # create custom delete, but remembers that this method
366
+ # must return a boolean value
367
+ def delete_record
368
+ @record.delete
369
+ end
370
+
371
+ # This method is strict required by NiftyServices, each service must implement
372
+ def can_delete_record?
373
+ # Checking user before trying to create a post for this same user
374
+ unless valid_user?
375
+ return not_found_error!('users.not_found')
376
+ end
377
+
378
+ return user_has_permission?
379
+ end
380
+
381
+ def user_has_permission?
382
+ # only system admins and owner can delete this record
383
+ # or you can transfer the logic below something like:
384
+ # @record.user_can_delete_record?(@user)
385
+ @user.admin? || @user.id == @record.id
386
+ end
387
+
388
+ # This is a custom method of this class, not NiftyService stuff
389
+ def valid_user?
390
+ valid_object?(@user, User)
391
+ end
392
+ end
393
+ ```
394
+
395
+ #### :earth_africa: I18n setup
396
+
397
+ Your locale file must have the following keys:
398
+
399
+ ```yml
400
+ posts:
401
+ not_found: "Invalid or not found post"
402
+ cant_delete: "Can't delete this record"
403
+ users:
404
+ not_found: "Invalid or not found user"
405
+ ```
406
+
407
+ #### :alien: User is invalid <a name="delete-resource-user-invalid"></a>
408
+
409
+ Response when owner user is not valid:
410
+
411
+ ```ruby
412
+ # PostDeleteService.new(post, user, params)
413
+ delete_service = PostDeleteService.new(Post.first, nil)
414
+
415
+ delete_service.execute
416
+
417
+ delete_service.success? # false
418
+ delete_service.response_status # :not_found_error
419
+ delete_service.response_status_code # 404
420
+
421
+ delete_service.errors # ["Invalid or not found user"]
422
+ ```
423
+
424
+ #### :closed_lock_with_key: Resource don't belongs to user <a name="delete-resource-dont-belongs-to-user"></a>
425
+
426
+ Responses when trying to delete a resource who don't belongs to owner:
427
+
428
+ ```ruby
429
+ # PostDeleteService.new(post, user, params)
430
+ delete_service = PostDeleteService.new(Post.first, User.last)
431
+ delete_service.execute
432
+
433
+ delete_service.success? # false
434
+ delete_service.response_status # :forbidden
435
+ delete_service.response_status_code # 400
436
+ delete_service.errors # ["User can't delete this record"]
437
+ ```
438
+
439
+ #### :santa: Resource(Post) don't exists <a name="delete-resource-dont-exists"></a>
440
+
441
+ Response when post don't exists:
442
+
443
+ ```ruby
444
+ # PostDeleteService.new(post, user, params)
445
+ delete_service = PostDeleteService.new(nil, User.last)
446
+
447
+ delete_service.execute
448
+
449
+ delete_service.success? # false
450
+ delete_service.response_status # :not_found_error
451
+ delete_service.response_status_code # 404
452
+
453
+ delete_service.errors # ["Invalid or not found post"]
454
+ ```
455
+
456
+ ---
457
+
458
+ #### Tip
459
+
460
+ You can DRY the examples above using `concerns`, if you take a look you will see
461
+ that in `PostUpdateService` and `PostDeleteService` same validation
462
+ methods are repeated, so lets improve this, first thing is create a Ruby plain module:
463
+
464
+ ```ruby
465
+ module PostCrudExtensions
466
+ # You can freely override initialize method to receive more arguments
467
+ def initialize(record, user, options = {})
468
+ @user = user
469
+ super(record, options)
470
+ end
471
+
472
+ def record_allowed_attributes
473
+ WHITELIST_ATTRIBUTES
474
+ end
475
+
476
+ def self.whitelist_attributes
477
+ WHITELIST_ATTRIBUTES
478
+ end
479
+
480
+ def user_has_permission?
481
+ # only system admins and owner can update this record
482
+ # or you can transfer the logic below to something like:
483
+ # @record.user_can_update_record?(@user)
484
+ @user.admin? || @user.id == @record.id
485
+ end
486
+
487
+ # this key is used for I18n translations, you don't need to override or implement
488
+ def record_error_key
489
+ :posts
490
+ end
491
+
492
+ def valid_user?
493
+ valid_object?(@user, User)
494
+ end
495
+ end
496
+ ```
497
+
498
+ The second step, is call class method `concern` in Service class, lets use `PostDeleteService`
499
+
500
+ ```ruby
501
+ class PostDeleteService < NiftyServices::BaseDeleteService
502
+
503
+ # Include shared CRUD methods to Post's CRUD Services
504
+ concern PostCrudExtensions
505
+
506
+ # record_type object must respond to :delete method
507
+ # But you can override `delete_method` method to do whatever you want
508
+
509
+ # below the code used internally, you can override to
510
+ # create custom delete, but remembers that this method
511
+ # must return a boolean value
512
+ def delete_record
513
+ @record.delete
514
+ end
515
+
516
+ # This method is strict required by NiftyServices, each service must implement
517
+ def can_delete_record?
518
+ # Checking user before trying to create a post for this same user
519
+ unless valid_user?
520
+ return not_found_error!('users.not_found')
521
+ end
522
+
523
+ return user_has_permission?
524
+ end
525
+ end
526
+ ```
527
+
528
+ The code is much more readable and DRY now.
529
+
530
+ Now, you can share `PostCrudExtensions` will all others Post related CRUD services.
531
+
532
+ ### Next
533
+
534
+ See [I18n Configuration](./i18n.md)