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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MGQzMzcyMzc1ZDdjMTQ2NTk2NzBhZGUwMDQzOTM3ZTIyMWU0MGEzNA==
4
+ ZjgzMjdmNjljNDk3NmVjMGY1MGY4ZWIxMTdhNzQ5OTM1MTA5YTJkYQ==
5
5
  data.tar.gz: !binary |-
6
- MDNkZmFjZDdkOGQ4YzkxMWJkNzc1NjBlZmYwYjcwODhkODBkNjMxNw==
6
+ ODgwNzg5ODUwMTQwNjBhOTIyZjNlNDNlY2U0ZWY1ZDYxNjE5NmU5Mw==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MTgyMjc3OGMzYzZiZjE4Y2FkMjJlYjk4Mzc1ZmU0YjIzMDdiYjBmZWYzZmYw
10
- MDFlMGQzZWU0Y2VhNjdkNzNiYTBhNzZhYWY3YzE1OTZiNTgzMmZkYmQyMDk1
11
- NzQ1ZmY1ZmVkOTI0MTRjZWZiODRiNzJhYzU1MzI5YWFkN2FkOTE=
9
+ OGYwZjQ5ZjljOWJmZTUzN2RlYTM0OGNmNGI5ZDViNDIzNDMxOTFlNjQ5YmMx
10
+ NzY5MTdhMzk0YmQ0MGNmZmJkMjQwMDMwNGYzYjllNDg0OWM3Y2ZmZjkyMDI2
11
+ NjVlN2ZhMTRhMTljZGE3NWI3YzNjYWYxMjBhODhlNTk3MTYwZmQ=
12
12
  data.tar.gz: !binary |-
13
- OWNhMDhhODdhZjU4ZTA0MDMzNjRlOGM3NGEzYTlkNmQzNmFiMGZjMzYxZDM0
14
- NjY3ZjNmOGM3YjgzMDQ5N2Y2ZjQ5YjE0ZWZjYWNhYzlhMWNlODk4ZGNkYWE3
15
- NTAxNTI4YTAzOTAyOWNmNGE1NGI1ZGVjYzYzMDA1NTVjZjI3ZmI=
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module RestfulJson
2
- VERSION = '3.3.4'
2
+ VERSION = '3.4.0'
3
3
  end
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.3.4
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 00:00:00.000000000 Z
12
+ date: 2013-04-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler