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.
- 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
@@ -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 =
|
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.
|
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]
|
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
|
-
|
107
|
-
return nil if executed?
|
110
|
+
return nil if executed?
|
108
111
|
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
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
|
-
|
152
|
-
|
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.
|
185
|
+
record.class.to_s == expected_class.to_s
|
192
186
|
end
|
193
187
|
|
194
|
-
def filter_hash(hash, whitelist_keys = [])
|
195
|
-
|
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.
|
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(
|
222
|
-
if
|
223
|
-
message =
|
224
|
-
elsif
|
225
|
-
message =
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
68
|
-
|
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
|
-
|
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
|
85
|
-
"#{record_error_key}.
|
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
|
-
|
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,
|
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
|
-
@
|
41
|
-
@
|
42
|
-
@
|
43
|
-
@
|
44
|
-
|
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
|
52
|
-
|
50
|
+
def fetch(option_key, default = nil)
|
51
|
+
@options[option_key] || default
|
53
52
|
end
|
54
53
|
|
55
|
-
def
|
56
|
-
|
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
|
-
|
6
|
+
Configuration::ERROR_RESPONSE_STATUS.each do |error, status|
|
7
7
|
class_eval <<-END
|
8
|
-
class #{error.to_s.
|
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
|