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 +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
@@ -0,0 +1,145 @@
|
|
1
|
+
# NiftyServices documentation
|
2
|
+
|
3
|
+
---
|
4
|
+
|
5
|
+
## Web Frameworks Integrations
|
6
|
+
|
7
|
+
### Rails <a name="frameworks-rails"></a>
|
8
|
+
|
9
|
+
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.
|
10
|
+
|
11
|
+
First thing to do is add `lib/` folder in `autoload` path, place the following in your `config/application.rb`
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# config/application.rb
|
15
|
+
config.paths.add(File.join(Rails.root, 'lib'), glob: File.join('**', '*.rb'))
|
16
|
+
|
17
|
+
config.autoload_paths << Rails.root.join('lib')
|
18
|
+
```
|
19
|
+
|
20
|
+
Second, create `lib/services` directory:
|
21
|
+
|
22
|
+
`$ mkdir -p lib/services/v1/users`
|
23
|
+
|
24
|
+
Next, configure:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
NiftyServices.configure do |config|
|
28
|
+
config.i18n_namespace = 'my_app'
|
29
|
+
end
|
30
|
+
```
|
31
|
+
**Note**: See [Configurations](./configuration.md) section to see all available configs
|
32
|
+
|
33
|
+
Create your first service:
|
34
|
+
|
35
|
+
```
|
36
|
+
$ touch lib/services/v1/users/create_service.rb
|
37
|
+
```
|
38
|
+
|
39
|
+
Use in your controller:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class UsersController < BaseController
|
43
|
+
def create
|
44
|
+
service = Services::V1::Users::CreateService.new(params).execute
|
45
|
+
|
46
|
+
default_response = { status: service.response_status, status_code: service.response_status_code }
|
47
|
+
|
48
|
+
if service.success?
|
49
|
+
response = { user: service.user, subscription: service.subscription }
|
50
|
+
else
|
51
|
+
response = { error: true, errors: service.errors }
|
52
|
+
end
|
53
|
+
|
54
|
+
render json: default_response.merge(response), status: service.response_status
|
55
|
+
end
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
This can be even better if you move response code to a helper:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# helpers/users_helper.rb
|
63
|
+
module UsersHelper
|
64
|
+
include GenericHelpers
|
65
|
+
|
66
|
+
def response_for_user_create_service(service)
|
67
|
+
success_response = { user: service.user, subscription: service.subscription }
|
68
|
+
generic_response_for_service(service, success_response)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
# helpers/generic_helper.rb
|
76
|
+
module GenericHelper
|
77
|
+
|
78
|
+
# THIS IS GREAT, you can use this method to standardize ALL of your
|
79
|
+
# endpoints responses, THIS IS SO FUCKING COOL!
|
80
|
+
def generic_response_for_service(service, success_response)
|
81
|
+
default_response = {
|
82
|
+
status: service.response_status,
|
83
|
+
status_code: service.response_status_code,
|
84
|
+
success: service.success?
|
85
|
+
}
|
86
|
+
|
87
|
+
if service.success?
|
88
|
+
response = success_response
|
89
|
+
else
|
90
|
+
response = {
|
91
|
+
error: true,
|
92
|
+
errors: service.errors
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
default_response.merge(response)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
Changing controller again: (looks so readable now <3)
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
# controllers/users_controller.rb
|
104
|
+
class UsersController < BaseController
|
105
|
+
def create
|
106
|
+
service = Services::V1::Users::CreateService.new(params).execute
|
107
|
+
|
108
|
+
render json: response_for_user_create_service(service), status: service.response_status
|
109
|
+
end
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
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)
|
114
|
+
|
115
|
+
---
|
116
|
+
|
117
|
+
### Grape/Sinatra/Padrino/Hanami/Rack <a name="frameworks-rack"></a>
|
118
|
+
|
119
|
+
Well, the integration here don't variate too much from Rails, just follow the steps:
|
120
|
+
|
121
|
+
**1 -** Decide where you'll put your services
|
122
|
+
|
123
|
+
**2 -** Code that dam amazing services!
|
124
|
+
|
125
|
+
**3 -** Instantiate the service in your framework entry point
|
126
|
+
|
127
|
+
**4 -** Create helpers to handle service response
|
128
|
+
|
129
|
+
**5 -** Be happy and go party!
|
130
|
+
|
131
|
+
---
|
132
|
+
|
133
|
+
## Integration Examples
|
134
|
+
|
135
|
+
Need examples of integrations fully working? Check out one of the following repositories:
|
136
|
+
|
137
|
+
[NiftyServices - Sinatra Sample](https://github.com/fidelisrafael/nifty_services-sinatra_example)
|
138
|
+
NiftyServices - Grape Sample
|
139
|
+
NiftyServices - Rails Sample
|
140
|
+
|
141
|
+
---
|
142
|
+
|
143
|
+
### Next
|
144
|
+
|
145
|
+
See [Basic Services class Markups](./services_markup.md)
|
data/lib/nifty_services.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
1
3
|
require 'nifty_services/version'
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require 'active_support/core_ext/string/inflections'
|
4
|
+
require 'nifty_services/support/hash'
|
5
|
+
require 'nifty_services/support/string'
|
5
6
|
|
6
7
|
module NiftyServices
|
7
8
|
|
@@ -16,12 +17,8 @@ module NiftyServices
|
|
16
17
|
autoload :Errors, 'nifty_services/errors'
|
17
18
|
autoload :Util, 'nifty_services/util'
|
18
19
|
|
19
|
-
module Extensions
|
20
|
-
autoload :CallbacksInterface, 'nifty_services/extensions/callbacks_interface'
|
21
|
-
end
|
22
|
-
|
23
20
|
class << self
|
24
|
-
def configuration
|
21
|
+
def configuration
|
25
22
|
@configuration ||= Configuration.new
|
26
23
|
|
27
24
|
yield(@configuration) if block_given?
|
@@ -11,25 +11,22 @@ module NiftyServices
|
|
11
11
|
execute_action do
|
12
12
|
with_before_and_after_callbacks(:action) do
|
13
13
|
# here user can
|
14
|
-
execute_service_action
|
15
|
-
|
16
|
-
if valid?
|
17
|
-
success_response
|
18
|
-
else
|
19
|
-
errors = action_errors
|
20
|
-
bad_request_error(errors) if errors.present?
|
14
|
+
with_before_and_after_callbacks(:execute_service_action) do
|
15
|
+
execute_service_action
|
21
16
|
end
|
17
|
+
|
18
|
+
success_response if valid?
|
22
19
|
end
|
23
20
|
end
|
24
21
|
end
|
25
22
|
|
26
23
|
private
|
27
24
|
def action_errors
|
28
|
-
|
25
|
+
@errors
|
29
26
|
end
|
30
27
|
|
31
28
|
def can_execute?
|
32
|
-
unless
|
29
|
+
unless can_execute_action?
|
33
30
|
return (valid? ? unprocessable_entity_error!(invalid_action_error_key) : false)
|
34
31
|
end
|
35
32
|
|
@@ -40,7 +37,7 @@ module NiftyServices
|
|
40
37
|
"#{record_error_key}.cant_execute_#{action_name}"
|
41
38
|
end
|
42
39
|
|
43
|
-
def
|
40
|
+
def can_execute_action?
|
44
41
|
not_implemented_exception(__method__)
|
45
42
|
end
|
46
43
|
|
@@ -1,18 +1,17 @@
|
|
1
1
|
module NiftyServices
|
2
2
|
class BaseCreateService < BaseCrudService
|
3
3
|
|
4
|
-
def initialize(
|
5
|
-
|
6
|
-
super(nil, user, options)
|
4
|
+
def initialize(options = {})
|
5
|
+
super(nil, options)
|
7
6
|
end
|
8
7
|
|
9
8
|
def execute
|
10
9
|
execute_action do
|
11
10
|
with_before_and_after_callbacks(:create) do
|
12
11
|
if can_execute_action?
|
13
|
-
@record = build_record
|
12
|
+
@record = with_before_and_after_callbacks(:build_record) { build_record }
|
14
13
|
|
15
|
-
if
|
14
|
+
if try_to_save_record
|
16
15
|
after_execute_success_response
|
17
16
|
else
|
18
17
|
errors = create_error_response(@record)
|
@@ -25,11 +24,20 @@ module NiftyServices
|
|
25
24
|
|
26
25
|
private
|
27
26
|
def save_record
|
27
|
+
save_method = NiftyServices.configuration.save_record_method
|
28
|
+
|
29
|
+
return save_method.call(@record) if save_method.respond_to?(:call)
|
30
|
+
|
31
|
+
@record.public_send(save_method)
|
32
|
+
end
|
33
|
+
|
34
|
+
def try_to_save_record
|
28
35
|
begin
|
29
|
-
|
36
|
+
save_record
|
30
37
|
rescue => e
|
31
38
|
on_save_record_error(e)
|
32
|
-
|
39
|
+
ensure
|
40
|
+
return @record.valid?
|
33
41
|
end
|
34
42
|
end
|
35
43
|
|
@@ -42,7 +50,7 @@ module NiftyServices
|
|
42
50
|
end
|
43
51
|
|
44
52
|
def after_error_response(errors)
|
45
|
-
unprocessable_entity_error(errors) if errors.
|
53
|
+
unprocessable_entity_error(errors) if @errors.empty?
|
46
54
|
end
|
47
55
|
|
48
56
|
def after_execute_success_response
|
@@ -50,64 +58,46 @@ module NiftyServices
|
|
50
58
|
end
|
51
59
|
|
52
60
|
def build_record
|
53
|
-
|
54
|
-
|
61
|
+
unless record_type.nil?
|
62
|
+
# initialize @temp_record to be used in after_build_record callback
|
63
|
+
return @temp_record = build_from_record_type(record_allowed_attributes)
|
55
64
|
end
|
56
65
|
|
57
|
-
|
66
|
+
@temp_record = record_type.public_send(:new, record_allowed_attributes)
|
58
67
|
end
|
59
68
|
|
60
|
-
def
|
61
|
-
|
62
|
-
return build_record_scope.send(:build, params)
|
63
|
-
end
|
64
|
-
|
65
|
-
record_type.send(:new, params)
|
69
|
+
def build_record_scope
|
70
|
+
nil
|
66
71
|
end
|
67
72
|
|
68
|
-
def
|
69
|
-
|
70
|
-
return forbidden_error!(%s(users.ip_temporarily_blocked))
|
71
|
-
end
|
73
|
+
def build_from_record_type(attributes)
|
74
|
+
scope = record_type
|
72
75
|
|
73
|
-
if
|
74
|
-
|
76
|
+
if !build_record_scope.nil? && build_record_scope.respond_to?(:new)
|
77
|
+
scope = build_record_scope
|
75
78
|
end
|
76
79
|
|
77
|
-
|
80
|
+
scope.new(attributes)
|
78
81
|
end
|
79
82
|
|
80
|
-
def
|
81
|
-
unless user_can_create_record?
|
82
|
-
return (valid? ? forbidden_error!(user_cant_create_error_key) : false)
|
83
|
-
end
|
84
|
-
|
83
|
+
def can_execute?
|
85
84
|
return true
|
86
85
|
end
|
87
86
|
|
88
|
-
def
|
87
|
+
def can_create_record?
|
89
88
|
not_implemented_exception(__method__)
|
90
89
|
end
|
91
90
|
|
92
91
|
def can_execute_action?
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
def can_create_with_ip?
|
97
|
-
true
|
98
|
-
end
|
99
|
-
|
100
|
-
def validate_ip_on_create?
|
101
|
-
# TODO: Use NiftyService.config.validate_ip_on_create?
|
102
|
-
false
|
103
|
-
end
|
92
|
+
unless can_create_record?
|
93
|
+
return (valid? ? forbidden_error!(cant_create_error_key) : false)
|
94
|
+
end
|
104
95
|
|
105
|
-
|
106
|
-
nil
|
96
|
+
return true
|
107
97
|
end
|
108
98
|
|
109
|
-
def
|
110
|
-
"#{record_error_key}.
|
99
|
+
def cant_create_error_key
|
100
|
+
"#{record_error_key}.cant_create"
|
111
101
|
end
|
112
102
|
end
|
113
103
|
end
|
@@ -1,7 +1,4 @@
|
|
1
1
|
module NiftyServices
|
2
|
-
module Concerns
|
3
|
-
end
|
4
|
-
|
5
2
|
class BaseCrudService < BaseService
|
6
3
|
|
7
4
|
attr_reader :record
|
@@ -17,22 +14,29 @@ module NiftyServices
|
|
17
14
|
alias_method record_alias.to_sym, :record
|
18
15
|
end
|
19
16
|
|
20
|
-
def
|
21
|
-
|
17
|
+
def whitelist_attributes(*attrs)
|
18
|
+
raise "Invalid whitelist attributes(#{attrs}) array" unless attrs.is_a?(Array)
|
22
19
|
|
23
|
-
|
24
|
-
|
20
|
+
attributes = *attrs.flatten.map(&:to_sym)
|
21
|
+
|
22
|
+
define_method :record_attributes_whitelist do
|
23
|
+
attributes
|
24
|
+
end
|
25
|
+
|
26
|
+
define_singleton_method :get_whitelist_attributes do
|
27
|
+
attributes
|
28
|
+
end
|
25
29
|
|
26
|
-
|
27
|
-
NiftyServices.config.service_concerns_namespace
|
30
|
+
attributes
|
28
31
|
end
|
29
32
|
|
30
|
-
|
33
|
+
def get_whitelist_attributes
|
34
|
+
[]
|
35
|
+
end
|
31
36
|
end
|
32
37
|
|
33
|
-
def initialize(record,
|
38
|
+
def initialize(record, options = {})
|
34
39
|
@record = record
|
35
|
-
@user = user
|
36
40
|
|
37
41
|
super(options)
|
38
42
|
end
|
@@ -64,38 +68,9 @@ module NiftyServices
|
|
64
68
|
alias :record_safe_attributes :record_allowed_attributes
|
65
69
|
|
66
70
|
private
|
67
|
-
def array_values_from_hash(options, key, root = nil)
|
68
|
-
options = options.symbolize_keys
|
69
|
-
|
70
|
-
if root.present?
|
71
|
-
options = (options[root.to_sym] || {}).symbolize_keys
|
72
|
-
end
|
73
|
-
|
74
|
-
return [] unless options.key?(key.to_sym)
|
75
|
-
|
76
|
-
values = options[key.to_sym]
|
77
|
-
|
78
|
-
return values if values.is_a?(Array)
|
79
|
-
|
80
|
-
array_values_from_string(values)
|
81
|
-
end
|
82
|
-
|
83
|
-
def invalid_user_error_key
|
84
|
-
%s(users.not_found)
|
85
|
-
end
|
86
|
-
|
87
|
-
def validate_user?
|
88
|
-
true
|
89
|
-
end
|
90
|
-
|
91
|
-
alias :array_values_from_params :array_values_from_hash
|
92
|
-
|
93
|
-
def array_values_from_string(string)
|
94
|
-
string.to_s.split(/\,/).map(&:squish)
|
95
|
-
end
|
96
|
-
|
97
71
|
def record_error_key
|
98
|
-
|
72
|
+
# very simple and poor way to pluralize string
|
73
|
+
record_type.to_s.underscore + "s"
|
99
74
|
end
|
100
75
|
|
101
76
|
def valid_record?
|
@@ -5,10 +5,12 @@ module NiftyServices
|
|
5
5
|
execute_action do
|
6
6
|
with_before_and_after_callbacks(:delete) do
|
7
7
|
if can_execute_action?
|
8
|
-
|
8
|
+
deleted_record = with_before_and_after_callbacks(:delete_record) { delete_record }
|
9
|
+
|
10
|
+
if deleted_record
|
9
11
|
success_response
|
10
12
|
else
|
11
|
-
|
13
|
+
unprocessable_entity_error!(@record.errors)
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
@@ -16,8 +18,17 @@ module NiftyServices
|
|
16
18
|
end
|
17
19
|
|
18
20
|
private
|
19
|
-
def
|
20
|
-
|
21
|
+
def delete_record
|
22
|
+
delete_method = NiftyServices.configuration.delete_record_method
|
23
|
+
|
24
|
+
if delete_method.respond_to?(:call)
|
25
|
+
delete_method.call(@record)
|
26
|
+
else
|
27
|
+
@record.public_send(delete_method)
|
28
|
+
end
|
29
|
+
|
30
|
+
# initialize @temp_record to be used in after_delete_record callback
|
31
|
+
@temp_record = @record
|
21
32
|
end
|
22
33
|
|
23
34
|
def can_execute?
|
@@ -25,33 +36,23 @@ module NiftyServices
|
|
25
36
|
return not_found_error!("#{record_error_key}.not_found")
|
26
37
|
end
|
27
38
|
|
28
|
-
if validate_user? && !valid_user?
|
29
|
-
return not_found_error!(invalid_user_error_key)
|
30
|
-
end
|
31
|
-
|
32
39
|
return true
|
33
40
|
end
|
34
41
|
|
35
42
|
def can_delete_record?
|
36
|
-
|
37
|
-
return (valid? ? forbidden_error!(user_can_delete_error_key) : false)
|
38
|
-
end
|
39
|
-
|
40
|
-
return true
|
43
|
+
not_implemented_exception(__method__)
|
41
44
|
end
|
42
45
|
|
43
46
|
def can_execute_action?
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
def user_can_delete_record?
|
48
|
-
return not_implemented_exception(__method__) unless @record.respond_to?(:user_can_delete?)
|
47
|
+
unless can_delete_record?
|
48
|
+
return (valid? ? forbidden_error!(cant_delete_error_key) : false)
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
+
return true
|
51
52
|
end
|
52
53
|
|
53
|
-
def
|
54
|
-
"#{record_error_key}.
|
54
|
+
def cant_delete_error_key
|
55
|
+
"#{record_error_key}.cant_delete"
|
55
56
|
end
|
56
57
|
end
|
57
58
|
end
|