restful_json 3.3.4 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +70 -0
- data/lib/restful_json/config.rb +16 -3
- data/lib/restful_json/controller.rb +88 -0
- data/lib/restful_json/default_controller.rb +6 -0
- data/lib/restful_json/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZjgzMjdmNjljNDk3NmVjMGY1MGY4ZWIxMTdhNzQ5OTM1MTA5YTJkYQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ODgwNzg5ODUwMTQwNjBhOTIyZjNlNDNlY2U0ZWY1ZDYxNjE5NmU5Mw==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OGYwZjQ5ZjljOWJmZTUzN2RlYTM0OGNmNGI5ZDViNDIzNDMxOTFlNjQ5YmMx
|
10
|
+
NzY5MTdhMzk0YmQ0MGNmZmJkMjQwMDMwNGYzYjllNDg0OWM3Y2ZmZjkyMDI2
|
11
|
+
NjVlN2ZhMTRhMTljZGE3NWI3YzNjYWYxMjBhODhlNTk3MTYwZmQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YTVjM2YyNjNhYjAyMDdiMzM3M2JlNGExMWYwODRjYWExZGM5NWQxZDU1MmM5
|
14
|
+
NzAyYTU3MjY3MWFiYzM1NjYzYTUyNTlmNWRjNjEzODljYzNkZmU4NzRkYTI1
|
15
|
+
MDE1MTFkNGYxMDNiZDc1ZWU5ZTc0YjYzZDk2MzczNDVlMmU2MDE=
|
data/README.md
CHANGED
@@ -701,6 +701,74 @@ You would get the response:
|
|
701
701
|
|
702
702
|
For more realistic use that takes advantage of existing configuration in the controller, take a look at the controller in `lib/restful_json/controller.rb` to see how the actions are defined, and just copy/paste into your controller or module, etc.
|
703
703
|
|
704
|
+
### Error Handling
|
705
|
+
|
706
|
+
#### Properly Handling Non-controller-action Errors
|
707
|
+
|
708
|
+
Some things restful_json can't do in the controller, like responding with json for a json request when the route is not setup correctly or an action is missing.
|
709
|
+
|
710
|
+
Rails 4 has basic error handling defined in the [public_exceptions][public_exceptions] and [show_exceptions][show_exceptions] Rack middleware.
|
711
|
+
|
712
|
+
Rails 3.2.x has support for `config.exceptions_app` which can be defined as the following to simulate Rails 4 exception handling:
|
713
|
+
|
714
|
+
config.exceptions_app = lambda do |env|
|
715
|
+
exception = env["action_dispatch.exception"]
|
716
|
+
status = env["PATH_INFO"][1..-1]
|
717
|
+
request = ActionDispatch::Request.new(env)
|
718
|
+
content_type = request.formats.first
|
719
|
+
body = { :status => status, :error => exception.message }
|
720
|
+
format = content_type && "to_#{content_type.to_sym}"
|
721
|
+
if format && body.respond_to?(format)
|
722
|
+
formatted_body = body.public_send(format)
|
723
|
+
[status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
|
724
|
+
'Content-Length' => body.bytesize.to_s}, [formatted_body]]
|
725
|
+
else
|
726
|
+
found = false
|
727
|
+
path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
|
728
|
+
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
|
729
|
+
|
730
|
+
if found || File.exist?(path)
|
731
|
+
[status, {'Content-Type' => "text/html; charset=#{ActionDispatch::Response.default_charset}",
|
732
|
+
'Content-Length' => body.bytesize.to_s}, [File.read(path)]]
|
733
|
+
else
|
734
|
+
[404, { "X-Cascade" => "pass" }, []]
|
735
|
+
end
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
That is just a collapsed version of the behavior of [public_exceptions][public_exceptions] as of April 2013, pre-Rails 4.0.0, so please look at the latest version and adjust accordingly. Use at your own risk, obviously.
|
740
|
+
|
741
|
+
Unfortunately, this doesn't work for Rails 3.1.x. However, in many scenarios there is the chance at a rare situation when the proper format is not returned to the client, even if everything is controlled as much as possible on the server. So, the client really needs to be able to handle such a case of unexpected format with a generic error.
|
742
|
+
|
743
|
+
But, if you can make Rack respond a little better for some errors, that's great.
|
744
|
+
|
745
|
+
#### Controller Error Handling Configuration
|
746
|
+
|
747
|
+
The default configuration will rescue StandardError in each action method and will render as 404 for ActiveRecord::RecordNotFound or 500 for all other StandardError (and ancestors, like a normal rescue).
|
748
|
+
|
749
|
+
There are a few options to customize the rescue and error rendering behavior.
|
750
|
+
|
751
|
+
The `rescue_class` config option specifies what to rescue. Set to StandardError to behave like a normal rescue. Set to nil to just reraise everything rescued (to disable handling).
|
752
|
+
|
753
|
+
The `rescue_handlers` config option is like a minimalist set of rescue blocks that apply to every action method. For example, the following would effectively `rescue => e` (rescuing `StandardError`) and then for `ActiveRecord::RecordNotFound`, it would uses response status `:not_found` (HTTP 404). Otherwise it uses status `:internal_server_error` (HTTP 500). In both cases the error message is `e.message`:
|
754
|
+
|
755
|
+
self.rescue_class = StandardError
|
756
|
+
self.rescue_handlers = [
|
757
|
+
{exception_classes: [ActiveRecord::RecordNotFound], status: :not_found},
|
758
|
+
{status: :internal_server_error}
|
759
|
+
]
|
760
|
+
|
761
|
+
In a slightly more complicated case, this configuration would catch all exceptions raised with each actinon method that had `ActiveRecord::RecordNotFound` as an ancestor and use the error message defined by i18n key 'api.not_found'. All other exceptions would use status `:internal_server_error` (because it is a default, and doesn't have to be specified) but would use the error message defined by i18n key 'api.internal_server_error':
|
762
|
+
|
763
|
+
self.rescue_class = Exception
|
764
|
+
self.rescue_handlers = [
|
765
|
+
{exception_ancestor_classes: [ActiveRecord::RecordNotFound], status: :not_found, i18n_key: 'api.not_found'.freeze},
|
766
|
+
{i18n_key: 'api.internal_server_error'.freeze}
|
767
|
+
]
|
768
|
+
|
769
|
+
|
770
|
+
The `return_error_data` config option will not only return a response with `status` and `error` but also an `error_data` containing the `e.class.name`, `e.message`, and cleaned `e.backtrace`.
|
771
|
+
|
704
772
|
### Release Notes
|
705
773
|
|
706
774
|
#### restful_json v3.3
|
@@ -758,4 +826,6 @@ Copyright (c) 2013 Gary S. Weaver, released under the [MIT license][lic].
|
|
758
826
|
[ar]: http://api.rubyonrails.org/classes/ActiveRecord/Relation.html
|
759
827
|
[rails-api]: https://github.com/rails-api/rails-api
|
760
828
|
[railscast320]: http://railscasts.com/episodes/320-jbuilder
|
829
|
+
[public_exceptions]: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
|
830
|
+
[show_exceptions]: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
|
761
831
|
[lic]: http://github.com/rubyservices/restful_json/blob/master/LICENSE
|
data/lib/restful_json/config.rb
CHANGED
@@ -9,7 +9,10 @@ module RestfulJson
|
|
9
9
|
:return_resource,
|
10
10
|
:render_enabled,
|
11
11
|
:use_permitters,
|
12
|
-
:avoid_respond_with
|
12
|
+
:avoid_respond_with,
|
13
|
+
:return_error_data,
|
14
|
+
:rescue_class,
|
15
|
+
:rescue_handlers
|
13
16
|
]
|
14
17
|
|
15
18
|
class << self
|
@@ -22,12 +25,22 @@ end
|
|
22
25
|
RestfulJson.configure do
|
23
26
|
self.can_filter_by_default_using = [:eq]
|
24
27
|
self.debug = false
|
25
|
-
self.filter_split = ','
|
28
|
+
self.filter_split = ','.freeze
|
26
29
|
self.formats = :json, :html
|
27
30
|
self.number_of_records_in_a_page = 15
|
28
|
-
self.predicate_prefix = '!'
|
31
|
+
self.predicate_prefix = '!'.freeze
|
29
32
|
self.return_resource = false
|
30
33
|
self.render_enabled = true
|
31
34
|
self.use_permitters = true
|
32
35
|
self.avoid_respond_with = true
|
36
|
+
self.return_error_data = true
|
37
|
+
# Set to nil to reraise StandardError in rescue vs. calling render_error(e) to render using restful_json
|
38
|
+
self.rescue_class = StandardError
|
39
|
+
# Ordered array of handlers to handle rescue of self.rescue_class or sub types. Can use optional i18n_key for message, but will default to e.message if not found.
|
40
|
+
# Eventually may support [DataMapper::ObjectNotFoundError], [MongoMapper::DocumentNotFound], etc.
|
41
|
+
# If no exception_classes or exception_ancestor_classes provided, it will always match self.rescue_class.
|
42
|
+
self.rescue_handlers = [
|
43
|
+
{exception_classes: [ActiveRecord::RecordNotFound], status: :not_found, i18n_key: 'api.not_found'.freeze},
|
44
|
+
{status: :internal_server_error, i18n_key: 'api.internal_server_error'.freeze}
|
45
|
+
]
|
33
46
|
end
|
@@ -197,6 +197,80 @@ module RestfulJson
|
|
197
197
|
value && NILS.include?(value) ? nil : value
|
198
198
|
end
|
199
199
|
|
200
|
+
def safe_i18n_t(i18n_key)
|
201
|
+
I18n.t(i18n_key)
|
202
|
+
rescue => e
|
203
|
+
logger.debug "failed to get i18n message for #{i18n_key.inspect}: #{e.message}\n#{e.backtrace.join('\n')}" if self.debug
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns self.return_error_data by default. To only return error_data in dev and test, use this:
|
207
|
+
# `def enable_long_error?; Rails.env.development? || Rails.env.test?; end`
|
208
|
+
def include_error_data?
|
209
|
+
self.return_error_data
|
210
|
+
end
|
211
|
+
|
212
|
+
# Searches through self.rescue_handlers for appropriate handler.
|
213
|
+
# self.rescue_handlers is an array of hashes where there is key :exception_classes and/or :exception_ancestor_classes
|
214
|
+
# along with :i18n_key and :status keys.
|
215
|
+
# :exception_classes contains an array of classes to exactly match the exception.
|
216
|
+
# :exception_ancestor_classes contains an array of classes that can match an ancestor of the exception.
|
217
|
+
# If exception handled, returns hash, hopefully containing keys :i18n_key and :status.
|
218
|
+
# Otherwise, returns nil which indicates that this exception should not be handled.
|
219
|
+
def exception_handling_data(e)
|
220
|
+
self.rescue_handlers.each do |handler|
|
221
|
+
return handler if (handler.key?(:exception_classes) && handler[:exception_classes].include?(e.class))
|
222
|
+
if handler.key?(:exception_ancestor_classes)
|
223
|
+
handler[:exception_ancestor_classes].each do |ancestor|
|
224
|
+
return handler if e.class.ancestors.include?(ancestor)
|
225
|
+
end
|
226
|
+
elsif !handler.key?(:exception_classes) && !handler.key?(:exception_ancestor_classes)
|
227
|
+
return handler
|
228
|
+
end
|
229
|
+
end
|
230
|
+
nil
|
231
|
+
end
|
232
|
+
|
233
|
+
def handle_or_raise(e)
|
234
|
+
raise e if self.rescue_class.nil?
|
235
|
+
handling_data = exception_handling_data(e)
|
236
|
+
raise e unless handling_data
|
237
|
+
# this is something we intended to rescue, so log it
|
238
|
+
logger.error(e)
|
239
|
+
# render error only if we haven't rendered response yet
|
240
|
+
render_error(e, handling_data) unless @performed_render
|
241
|
+
end
|
242
|
+
|
243
|
+
# Renders error using handling data options (where options are probably hash from self.rescue_handlers that was matched).
|
244
|
+
#
|
245
|
+
# If include_error_data? is true, it returns something like the following (with the appropriate HTTP status code via setting appropriate status in respond_do:
|
246
|
+
# {"status": "not_found",
|
247
|
+
# "error": "Internationalized error message or e.message",
|
248
|
+
# "error_data": {"type": "ActiveRecord::RecordNotFound", "message": "Couldn't find Bar with id=23423423", "trace": ["backtrace line 1", ...]}
|
249
|
+
# }
|
250
|
+
#
|
251
|
+
# If include_error_data? is false, it returns something like:
|
252
|
+
# {"status": "not_found", "error", "Couldn't find Bar with id=23423423"}
|
253
|
+
#
|
254
|
+
# It handles any format in theory that is supported by respond_to and has a `to_(some format)` method.
|
255
|
+
def render_error(e, handling_data)
|
256
|
+
i18n_key = handling_data[:i18n_key]
|
257
|
+
msg = i18n_key ? safe_i18n_t(i18n_key) : e.message
|
258
|
+
status = handling_data[:status] || :internal_server_error
|
259
|
+
if include_error_data?
|
260
|
+
respond_to do |format|
|
261
|
+
format.html { render notice: msg }
|
262
|
+
format.any { render request.format.to_sym => {:status => status, error: msg, error_data: {type: e.class.name, message: e.message, trace: Rails.backtrace_cleaner.clean(e.backtrace)}}, status: status }
|
263
|
+
end
|
264
|
+
else
|
265
|
+
respond_to do |format|
|
266
|
+
format.html { render notice: msg }
|
267
|
+
format.any { render request.format.to_sym => {:status => status, error: msg}, status: status }
|
268
|
+
end
|
269
|
+
end
|
270
|
+
# return exception so we know it was handled
|
271
|
+
e
|
272
|
+
end
|
273
|
+
|
200
274
|
def render_or_respond(read_only_action, success_code = :ok)
|
201
275
|
if self.render_enabled
|
202
276
|
# 404/not found is just for update (not destroy, because idempotent destroy = no 404)
|
@@ -341,6 +415,8 @@ module RestfulJson
|
|
341
415
|
@value = value
|
342
416
|
instance_variable_set(@model_at_plural_name_sym, @value)
|
343
417
|
render_or_respond(true)
|
418
|
+
rescue self.rescue_class => e
|
419
|
+
handle_or_raise(e)
|
344
420
|
end
|
345
421
|
|
346
422
|
# The controller's show (get) method to return a resource.
|
@@ -350,6 +426,8 @@ module RestfulJson
|
|
350
426
|
@value = @model_class.where(id: params[:id].to_s).first # don't raise exception if not found
|
351
427
|
instance_variable_set(@model_at_singular_name_sym, @value)
|
352
428
|
render_or_respond(true, @value.nil? ? :not_found : :ok)
|
429
|
+
rescue self.rescue_class => e
|
430
|
+
handle_or_raise(e)
|
353
431
|
end
|
354
432
|
|
355
433
|
# The controller's new method (e.g. used for new record in html format).
|
@@ -358,6 +436,8 @@ module RestfulJson
|
|
358
436
|
@value = @model_class.new
|
359
437
|
instance_variable_set(@model_at_singular_name_sym, @value)
|
360
438
|
render_or_respond(true)
|
439
|
+
rescue self.rescue_class => e
|
440
|
+
handle_or_raise(e)
|
361
441
|
end
|
362
442
|
|
363
443
|
# The controller's edit method (e.g. used for edit record in html format).
|
@@ -367,6 +447,8 @@ module RestfulJson
|
|
367
447
|
@value = @model_class.where(id: params[:id].to_s).first! # raise exception if not found
|
368
448
|
instance_variable_set(@model_at_singular_name_sym, @value)
|
369
449
|
@value
|
450
|
+
rescue self.rescue_class => e
|
451
|
+
handle_or_raise(e)
|
370
452
|
end
|
371
453
|
|
372
454
|
# The controller's create (post) method to create a resource.
|
@@ -386,6 +468,8 @@ module RestfulJson
|
|
386
468
|
@value.save
|
387
469
|
instance_variable_set(@model_at_singular_name_sym, @value)
|
388
470
|
render_or_respond(false, :created)
|
471
|
+
rescue self.rescue_class => e
|
472
|
+
handle_or_raise(e)
|
389
473
|
end
|
390
474
|
|
391
475
|
# The controller's update (put) method to update a resource.
|
@@ -406,6 +490,8 @@ module RestfulJson
|
|
406
490
|
@value.update_attributes(allowed_params) unless @value.nil?
|
407
491
|
instance_variable_set(@model_at_singular_name_sym, @value)
|
408
492
|
render_or_respond(true, @value.nil? ? :not_found : :ok)
|
493
|
+
rescue self.rescue_class => e
|
494
|
+
handle_or_raise(e)
|
409
495
|
end
|
410
496
|
|
411
497
|
# The controller's destroy (delete) method to destroy a resource.
|
@@ -423,6 +509,8 @@ module RestfulJson
|
|
423
509
|
else
|
424
510
|
render_or_respond(false)
|
425
511
|
end
|
512
|
+
rescue self.rescue_class => e
|
513
|
+
handle_or_raise(e)
|
426
514
|
end
|
427
515
|
end
|
428
516
|
end
|
@@ -7,6 +7,12 @@ module RestfulJson
|
|
7
7
|
include ::ActionController::StrongParameters
|
8
8
|
include ::TwinTurbo::Controller
|
9
9
|
include ::RestfulJson::Controller
|
10
|
+
|
11
|
+
rescue_from Exception, :with => :render_error
|
12
|
+
rescue_from ActiveRecord::RecordNotFound, :with => :render_not_found
|
13
|
+
rescue_from ActionController::RoutingError, :with => :render_not_found
|
14
|
+
rescue_from ActionController::UnknownController, :with => :render_not_found
|
15
|
+
rescue_from AbstractController::ActionNotFound, :with => :render_not_found
|
10
16
|
end
|
11
17
|
end
|
12
18
|
end
|
data/lib/restful_json/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restful_json
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gary S. Weaver
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
12
|
+
date: 2013-04-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|