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