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.
@@ -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