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.
@@ -1,8 +1,9 @@
1
+ require File.expand_path('extensions/callbacks', File.dirname(__FILE__))
2
+ require 'i18n'
3
+
1
4
  module NiftyServices
2
5
  class BaseService
3
6
 
4
- include Extensions::CallbacksInterface
5
-
6
7
  attr_reader :response_status, :response_status_code
7
8
  attr_reader :options, :errors, :logger
8
9
 
@@ -23,12 +24,19 @@ module NiftyServices
23
24
  error!(status_code, message_key, options)
24
25
  end
25
26
  end
27
+
28
+ def concern(concern_module)
29
+ self.include(concern_module)
30
+ end
31
+
32
+ alias_method :include_concern, :concern
26
33
  end
27
34
 
28
35
  def initialize(options = {}, initial_response_status = 400)
29
- @options = default_options.to_options!.merge(options).to_options!
36
+ @options = with_default_options(options)
30
37
  @errors = []
31
- @logger = options[:logger] || default_logger
38
+ @logger = @options[:logger] || default_logger
39
+
32
40
  @executed = false
33
41
 
34
42
  with_before_and_after_callbacks(:initialize) do
@@ -41,7 +49,7 @@ module NiftyServices
41
49
  end
42
50
 
43
51
  def valid?
44
- return @errors.blank?
52
+ return @errors.empty?
45
53
  end
46
54
 
47
55
  def success?
@@ -56,20 +64,12 @@ module NiftyServices
56
64
  @response_status ||= :bad_request
57
65
  end
58
66
 
59
- def valid_user?
60
- user_class = NiftyServices.config.user_class
61
-
62
- raise 'Invalid User class. Use NitfyService.config.user_class = ClassName' if user_class.blank?
63
-
64
- valid_object?(@user, user_class)
65
- end
66
-
67
67
  def option_exists?(key)
68
68
  @options && @options.key?(key.to_sym)
69
69
  end
70
70
 
71
71
  def option_enabled?(key)
72
- option_exists?(key) && @options[key.to_sym] == true
72
+ option_exists?(key) && [true, 'true'].member?(@options[key.to_sym])
73
73
  end
74
74
 
75
75
  def option_disabled?(key)
@@ -94,6 +94,10 @@ module NiftyServices
94
94
  alias :runned? :executed?
95
95
 
96
96
  private
97
+ def with_default_options(options)
98
+ default_options.merge(options).symbolize_keys
99
+ end
100
+
97
101
  def default_options
98
102
  {}
99
103
  end
@@ -103,21 +107,15 @@ module NiftyServices
103
107
  end
104
108
 
105
109
  def execute_action(&block)
106
- begin
107
- return nil if executed?
110
+ return nil if executed?
108
111
 
109
- with_before_and_after_callbacks(:execute) do
110
- if can_execute?
111
- yield(block) if block_given?
112
- end
112
+ with_before_and_after_callbacks(:execute) do
113
+ if can_execute?
114
+ yield(block) if block_given?
113
115
  end
114
-
115
- @executed = true
116
- rescue Exception => e
117
- add_error(e)
118
116
  end
119
117
 
120
- self # allow chaining
118
+ @executed = true
121
119
  end
122
120
 
123
121
  def success_response(status = :ok)
@@ -148,12 +146,8 @@ module NiftyServices
148
146
 
149
147
  response_list = error_list.merge(success_list)
150
148
 
151
- selected_status = response_list.select do |status_key, status_code|
152
- if select_method == :key
153
- status_key == status
154
- else
155
- status_code == status
156
- end
149
+ response_list.select do |status_key, status_code|
150
+ status == (select_method == :key ? status_key : status_code)
157
151
  end
158
152
  end
159
153
 
@@ -188,17 +182,17 @@ module NiftyServices
188
182
  end
189
183
 
190
184
  def valid_object?(record, expected_class)
191
- record.present? && record.is_a?(expected_class)
185
+ record.class.to_s == expected_class.to_s
192
186
  end
193
187
 
194
- def filter_hash(hash, whitelist_keys = [])
195
- (hash || {}).symbolize_keys.slice(*whitelist_keys.map(&:to_sym))
188
+ def filter_hash(hash = {}, whitelist_keys = [])
189
+ hash.symbolize_keys.slice(*whitelist_keys.map(&:to_sym))
196
190
  end
197
191
 
198
192
  def changes(old, current, attributes = {})
199
193
  changes = []
200
194
 
201
- return changes if old.blank? || current.blank?
195
+ return changes if old.nil? || current.nil?
202
196
 
203
197
  old_attributes = old.attributes.slice(*attributes.map(&:to_s))
204
198
  new_attributes = current.attributes.slice(*attributes.map(&:to_s))
@@ -218,13 +212,20 @@ module NiftyServices
218
212
  "#{i18n_namespace}.errors"
219
213
  end
220
214
 
221
- def process_error_message_for_key(message_key, options)
222
- if message_key.class.to_s == 'ActiveModel::Errors'
223
- message = message_key.messages
224
- elsif message_key.is_a?(Array) && message_key.first.is_a?(Hash)
225
- message = message_key
215
+ def process_error_message_for_key(message_object, options)
216
+ if message_object.respond_to?(:messages)
217
+ message = message_object.messages
218
+ elsif message_object.respond_to?(:message)
219
+ message = message_object.message
220
+ elsif message_object.is_a?(Array) && message_object.first.is_a?(Hash)
221
+ message = message_object
222
+ elsif message_object.is_a?(String)
223
+ # if message_object is a string, use it as key to I18n or use pure string content
224
+ message = options[:translate].nil? || options[:translate] == true ?
225
+ translate("#{i18n_errors_namespace}.#{message_object}", options) :
226
+ message_object
226
227
  else
227
- message = I18n.t("#{i18n_errors_namespace}.#{message_key}", options)
228
+ message = message_object
228
229
  end
229
230
 
230
231
  message
@@ -236,7 +237,16 @@ module NiftyServices
236
237
 
237
238
  protected
238
239
  def not_implemented_exception(method_name)
239
- raise NotImplementedError, "#{method_name} must be implemented in subclass"
240
+ message = "##{method_name} method must be implemented in #{self.class} class"
241
+ raise NotImplementedError, message
242
+ end
243
+
244
+ def translate(key, options = {})
245
+ begin
246
+ I18n.t(key, options)
247
+ rescue => error
248
+ "Can't fecth key #{key} - #{error.message}"
249
+ end
240
250
  end
241
251
  end
242
252
  end
@@ -7,13 +7,13 @@ module NiftyServices
7
7
  if can_execute_action?
8
8
  duplicate_records_before_update
9
9
 
10
- @record = update_record
10
+ @record = with_before_and_after_callbacks(:update_record) { update_record }
11
11
 
12
12
  if success_updated?
13
13
  success_response
14
14
  else
15
15
  errors = update_errors
16
- bad_request_error(errors) if errors.present?
16
+ unprocessable_entity_error!(errors) unless errors.empty?
17
17
  end
18
18
  end
19
19
  end
@@ -40,7 +40,16 @@ module NiftyServices
40
40
  end
41
41
 
42
42
  def update_record
43
- @record.class.send(:update, @record.id, record_allowed_attributes)
43
+ update_method = NiftyServices.configuration.update_record_method
44
+
45
+ if update_method.respond_to?(:call)
46
+ update_method.call(@record, record_allowed_attributes)
47
+ else
48
+ @record.public_send(update_method, record_allowed_attributes)
49
+ end
50
+
51
+ # initialize @temp_record to be used in after_update_record callback
52
+ @temp_record = @record
44
53
  end
45
54
 
46
55
  def can_execute?
@@ -48,29 +57,19 @@ module NiftyServices
48
57
  return not_found_error!(invalid_record_error_key)
49
58
  end
50
59
 
51
- if validate_user? && !valid_user?
52
- return not_found_error!(invalid_user_error_key)
53
- end
54
-
55
60
  return true
56
61
  end
57
62
 
58
63
  def can_update_record?
59
- unless user_can_update_record?
60
- return (valid? ? forbidden_error!(user_cant_update_error_key) : false)
61
- end
62
-
63
- return true
64
+ not_implemented_exception(__method__)
64
65
  end
65
66
 
66
67
  def can_execute_action?
67
- return can_update_record?
68
- end
69
-
70
- def user_can_update_record?
71
- return not_implemented_exception(__method__) unless @record.respond_to?(:user_can_update?)
68
+ unless can_update_record?
69
+ return (valid? ? forbidden_error!(cant_update_error_key) : false)
70
+ end
72
71
 
73
- @record.user_can_update?(@user)
72
+ return true
74
73
  end
75
74
 
76
75
  def duplicate_records_before_update
@@ -81,8 +80,8 @@ module NiftyServices
81
80
  "#{record_error_key}.not_found"
82
81
  end
83
82
 
84
- def user_cant_update_error_key
85
- "#{record_error_key}.user_cant_update"
83
+ def cant_update_error_key
84
+ "#{record_error_key}.cant_update"
86
85
  end
87
86
 
88
87
  end
@@ -1,10 +1,7 @@
1
- require 'logger'
2
-
3
1
  module NiftyServices
4
2
  class Configuration
5
3
 
6
4
  DEFAULT_I18N_NAMESPACE = "nifty_services"
7
- DEFAULT_SERVICE_CONCERN_NAMESPACE = 'NitfyServices::Concerns'
8
5
 
9
6
  ERROR_RESPONSE_STATUS = {
10
7
  :bad_request => 400,
@@ -12,6 +9,10 @@ module NiftyServices
12
9
  :forbidden => 403,
13
10
  :not_found => 404,
14
11
  :unprocessable_entity => 422,
12
+ # internal_server_error_error!
13
+ :internal_server_error => 500,
14
+ # keeping compatibility
15
+ # internal_server_error!
15
16
  :internal_server => 500,
16
17
  :not_implemented => 501
17
18
  }
@@ -27,33 +28,31 @@ module NiftyServices
27
28
  end
28
29
 
29
30
  def add_response_error_method(reason, status_code)
30
- response_errors_list[reason.to_sym] = status_code.to_i
31
+ ERROR_RESPONSE_STATUS[reason.to_sym] = status_code.to_i
31
32
  end
32
33
  end
33
34
 
34
35
  attr_reader :options
35
36
 
36
- attr_accessor :logger, :i18n_namespace, :user_class, :service_concerns_namespace
37
+ attr_accessor :logger, :i18n_namespace,
38
+ :delete_record_method, :update_record_method, :save_record_method
37
39
 
38
40
  def initialize(options = {})
39
41
  @options = options
40
- @service_concerns_namespace = default_service_concerns_namespace
41
- @user_class = options[:user_class] || default_user_class
42
- @i18n_namespace = @options[:i18n_namespace] || default_i18n_namespace
43
- @logger = options[:logger] || default_logger
44
- end
45
-
46
- def default_i18n_namespace
47
- DEFAULT_I18N_NAMESPACE
42
+ @i18n_namespace = fetch(:i18n_namespace, default_i18n_namespace)
43
+ @delete_record_method = :delete
44
+ @update_record_method = :update
45
+ @save_record_method = :save
46
+ @logger = fetch(:logger, default_logger)
48
47
  end
49
48
 
50
49
  private
51
- def default_service_concerns_namespace
52
- DEFAULT_SERVICE_CONCERN_NAMESPACE
50
+ def fetch(option_key, default = nil)
51
+ @options[option_key] || default
53
52
  end
54
53
 
55
- def default_user_class
56
- nil
54
+ def default_i18n_namespace
55
+ DEFAULT_I18N_NAMESPACE
57
56
  end
58
57
 
59
58
  def default_logger
@@ -3,9 +3,9 @@ module NiftyServices
3
3
  end
4
4
 
5
5
  module Errors
6
- BaseService::ERROR_RESPONSE_METHODS.each do |error, status|
6
+ Configuration::ERROR_RESPONSE_STATUS.each do |error, status|
7
7
  class_eval <<-END
8
- class #{error.to_s.camelize} < Error
8
+ class #{error.to_s.camel_case} < Error
9
9
  end
10
10
  END
11
11
  end
@@ -0,0 +1,173 @@
1
+ module NiftyServices
2
+ class BaseService
3
+
4
+ @@registered_callbacks = Hash.new {|k,v| k[v] = Hash.new }
5
+
6
+ CALLBACKS = [
7
+ :before_execute_service_action,
8
+ :after_execute_service_action,
9
+ :before_delete_record,
10
+ :after_delete_record,
11
+ :before_update_record,
12
+ :after_update_record,
13
+ :before_build_record,
14
+ :after_build_record,
15
+ :before_initialize,
16
+ :after_initialize,
17
+ :before_execute,
18
+ :after_execute,
19
+ :before_success,
20
+ :after_success,
21
+ :before_error,
22
+ :after_error,
23
+ :before_create,
24
+ :after_create,
25
+ :before_update,
26
+ :after_update,
27
+ :before_delete,
28
+ :after_delete,
29
+ :before_action,
30
+ :after_action
31
+ ].freeze
32
+
33
+ class << self
34
+ def register_callback(callback_name, method_name, &block)
35
+ method_name = Util.normalized_callback_name(method_name)
36
+
37
+ @@registered_callbacks[self.name.to_sym][callback_name] ||= []
38
+ @@registered_callbacks[self.name.to_sym][callback_name] << method_name
39
+
40
+ register_callback_action(method_name, &block)
41
+ end
42
+
43
+ def register_callback_action(callback_name, &block)
44
+ define_method(callback_name, &block)
45
+ end
46
+
47
+ CALLBACKS.each do |callback_name|
48
+ define_method callback_name do |&block|
49
+ register_callback_action(callback_name, &block)
50
+ end
51
+ end
52
+ end
53
+
54
+ CALLBACKS.each do |callback_name|
55
+ # empty method call (just returns nil)
56
+ define_method callback_name, -> {}
57
+ end
58
+
59
+ def callback_fired?(callback_name)
60
+ return (
61
+ callback_fired_in?(@fired_callbacks, callback_name) ||
62
+ callback_fired_in?(@custom_fired_callbacks, callback_name) ||
63
+ callback_fired_in?(@custom_fired_callbacks, "#{callback_name}_callback")
64
+ )
65
+ end
66
+
67
+ alias :callback_called? :callback_fired?
68
+
69
+ def register_callback(callback_name, method_name, &block)
70
+ method_name = normalized_callback_name(method_name).to_sym
71
+
72
+ @registered_callbacks[callback_name.to_sym] << method_name
73
+ register_callback_action(callback_name, &block)
74
+ end
75
+
76
+ def register_callback_action(callback_name, &block)
77
+ cb_name = normalized_callback_name(callback_name).to_sym
78
+ @callbacks_actions[cb_name.to_sym] = block
79
+ end
80
+
81
+ private
82
+ def callbacks_setup
83
+ return nil if @callbacks_setup
84
+
85
+ @fired_callbacks, @custom_fired_callbacks = {}, {}
86
+ @callbacks_actions = {}
87
+ @registered_callbacks = Hash.new {|k,v| k[v] = [] }
88
+
89
+ @callbacks_setup = true
90
+ end
91
+
92
+ def call_callback(callback_name)
93
+ callback_name = callback_name.to_s.underscore.to_sym
94
+
95
+ if has_callback?(callback_name)
96
+ @fired_callbacks[callback_name.to_sym] = true
97
+
98
+ invoke_callback(method(callback_name))
99
+ call_registered_callbacks_for(callback_name)
100
+ end
101
+
102
+ # allow chained methods
103
+ self
104
+ end
105
+
106
+ def has_callback?(callback_name)
107
+ _callback_name = normalized_callback_name(callback_name).to_sym
108
+ # include private methods
109
+ respond_to?(callback_name, true) || respond_to?(_callback_name, true)
110
+ end
111
+
112
+ def with_before_and_after_callbacks(callback_basename, &block)
113
+ callbacks_setup
114
+
115
+ call_callback(:"before_#{callback_basename}")
116
+
117
+ block_response = yield(block) if block_given?
118
+
119
+ call_callback(:"after_#{callback_basename}")
120
+
121
+ block_response
122
+ end
123
+
124
+ def call_registered_callbacks_for(callback_name)
125
+ instance_call_all_custom_registered_callbacks_for(callback_name)
126
+ class_call_all_custom_registered_callbacks_for(callback_name)
127
+ end
128
+
129
+ def instance_call_all_custom_registered_callbacks_for(callback_name)
130
+ @fired_callbacks[callback_name] = true
131
+
132
+ callbacks = @registered_callbacks[callback_name.to_sym]
133
+
134
+ callbacks.each do |cb|
135
+ if callback = @callbacks_actions[cb.to_sym]
136
+ @custom_fired_callbacks[cb.to_sym] = true
137
+ invoke_callback(callback)
138
+ end
139
+ end
140
+ end
141
+
142
+ def class_call_all_custom_registered_callbacks_for(callback_name)
143
+ classes_chain = self.class.ancestors.map(&:to_s).grep /\ANiftyServices/
144
+ klasses = @@registered_callbacks.keys.map(&:to_s) & classes_chain
145
+
146
+ klasses.each do |klass|
147
+ class_call_all_custom_registered_callbacks_for_class(klass, callback_name)
148
+ end
149
+ end
150
+
151
+ def class_call_all_custom_registered_callbacks_for_class(class_name, callback_name)
152
+ class_callbacks = @@registered_callbacks[class_name.to_sym]
153
+ callbacks = class_callbacks[callback_name.to_sym] || []
154
+
155
+ callbacks.each do |cb|
156
+ @custom_fired_callbacks[cb.to_sym] = true
157
+ invoke_callback(method(cb))
158
+ end
159
+ end
160
+
161
+ def callback_fired_in?(callback_list, callback_name)
162
+ return callback_list.key?(callback_name.to_sym)
163
+ end
164
+
165
+ def normalized_callback_name(callback_name, prefix = '_callback')
166
+ Util.normalized_callback_name(callback_name, prefix)
167
+ end
168
+
169
+ def invoke_callback(method)
170
+ method.call
171
+ end
172
+ end
173
+ end