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.
@@ -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)
@@ -1,7 +1,8 @@
1
+ require 'logger'
2
+
1
3
  require 'nifty_services/version'
2
- require 'active_support/core_ext/object/blank'
3
- require 'active_support/core_ext/hash/keys'
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(&block)
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 user_can_execute_action?
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 user_can_execute_action?
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(user, options = {})
5
- @user = user
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 save_record
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
- @record.save
36
+ save_record
30
37
  rescue => e
31
38
  on_save_record_error(e)
32
- return false
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.present?
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
- if record_type.present?
54
- return build_from_record_type(record_allowed_attributes)
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
- return not_implemented_exception(__method__)
66
+ @temp_record = record_type.public_send(:new, record_allowed_attributes)
58
67
  end
59
68
 
60
- def build_from_record_type(params)
61
- if !build_record_scope.nil? && build_record_scope.respond_to?(:build)
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 can_execute?
69
- if validate_ip_on_create? && !can_create_with_ip?
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 validate_user? && !valid_user?
74
- return not_found_error!(invalid_user_error_key)
76
+ if !build_record_scope.nil? && build_record_scope.respond_to?(:new)
77
+ scope = build_record_scope
75
78
  end
76
79
 
77
- return true
80
+ scope.new(attributes)
78
81
  end
79
82
 
80
- def can_create_record?
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 user_can_create_record?
87
+ def can_create_record?
89
88
  not_implemented_exception(__method__)
90
89
  end
91
90
 
92
91
  def can_execute_action?
93
- return can_create_record?
94
- end
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
- def build_record_scope
106
- nil
96
+ return true
107
97
  end
108
98
 
109
- def user_cant_create_error_key
110
- "#{record_error_key}.user_cant_create"
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 include_concern(namespace, concern_type)
21
- _module = "#{services_concern_namespace}::#{namespace.to_s.camelize}::#{concern_type.to_s.camelize}"
17
+ def whitelist_attributes(*attrs)
18
+ raise "Invalid whitelist attributes(#{attrs}) array" unless attrs.is_a?(Array)
22
19
 
23
- self.include(_module.constantize)
24
- end
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
- def services_concern_namespace
27
- NiftyServices.config.service_concerns_namespace
30
+ attributes
28
31
  end
29
32
 
30
- alias_method :concern, :include_concern
33
+ def get_whitelist_attributes
34
+ []
35
+ end
31
36
  end
32
37
 
33
- def initialize(record, user, options = {})
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
- record_type.to_s.pluralize.underscore
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
- if destroy_record
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
- bad_request_error(@record.errors)
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 destroy_record
20
- @record.try(:destroy) || @record.try(:delete)
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
- unless user_can_delete_record?
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
- return can_delete_record?
45
- end
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
- @record.user_can_delete?(@user)
51
+ return true
51
52
  end
52
53
 
53
- def user_cant_delete_error_key
54
- "#{record_error_key}.user_cant_delete"
54
+ def cant_delete_error_key
55
+ "#{record_error_key}.cant_delete"
55
56
  end
56
57
  end
57
58
  end