stas/jsonapi.rb

How to serialize errors from not valid Ruby Objects?

rafaltrojanowski opened this issue · 4 comments

Hey,

This may be a duplicate of #22

Here is my snippet:

# controller
class CountriesController < ApplicationController

  include JSONAPI::Errors

  def index
    # All good here:
    # non_valid_active_record_object = Country.new
    # render jsonapi_errors: non_valid_active_record_object.errors

    # Another example:
    service_object_with_errors = NonValidServiceObject.new
    service_object_with_errors.save
    puts service_object_with_errors.errors.to_h.inspect # => {:name=>"can't be blank"}

    render jsonapi_errors: service_object_with_errors.errors, status: 422
  end
end

# ruby object that uses ActiveModel validations
class NonValidServiceObject
  include ActiveModel::Model

  validates :name, presence: true
  attr_accessor :name

  def save
    if valid?

    else
      self
    end
  end
end

Response:

{"errors":[{"status":"500","source":null,"title":"Internal Server Error","detail":null}]}

Logs:

Started GET "/countries" for ::1 at 2020-07-04 22:04:01 +0200
Processing by CountriesController#index as HTML
#<NonValidServiceObject:0x00007fa83c3068d0>
___________________________________________
{:name=>"can't be blank"}
DEPRECATION WARNING: Rails 6.1 will return Content-Type header without modification. If you want just the MIME type, please use `#media_type` instead. (called from index at /Users/rafaltrojanowski/Projects/coffeehaunt/app/controllers/countries_controller.rb:36)
DEPRECATION WARNING: Rails 6.1 will return Content-Type header without modification. If you want just the MIME type, please use `#media_type` instead. (called from content_type at /Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_controller/metal.rb:150)
Completed 500 Internal Server Error in 5ms (Views: 0.4ms | ActiveRecord: 0.0ms | Allocations: 3079)


Started GET "/serviceworker.js" for ::1 at 2020-07-04 22:04:03 +0200

Expected Behavior

Actual Behavior

Steps to Reproduce the Problem

Specifications

  • Version:
  • Ruby version:
stas commented

Heya @rafaltrojanowski

Could you generate a full backtrace please. Either by writing a test (jsonapi.rb 500 error handling is disabled in the test environments) or by overwriting the error handling method

Generally speaking, you can generate any valid error response easily, here's an example:
https://github.com/stas/jsonapi.rb/blob/master/lib/jsonapi/errors.rb#L36-L39

Let me know if any of this helped!

Hey @stas

I went with passing errors explicitly for now as you suggested in a second link.

For the example that I posted above I'm getting:

  def render_jsonapi_internal_server_error(exception)
    binding.pry
    # Call your exception notifier here. Example:
    # Raven.capture_exception(exception)
    super(exception)
  end
 #<NameError: uninitialized constant NonValidServiceObjectSerializer>

Backtrace:

["/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:76:in `block in load_missing_constant'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:8:in `without_bootsnap_cache'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:76:in `rescue in load_missing_constant'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:58:in `load_missing_constant'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/inflector/methods.rb:282:in `const_get'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/inflector/methods.rb:282:in `block in constantize'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/inflector/methods.rb:280:in `each'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/inflector/methods.rb:280:in `inject'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/inflector/methods.rb:280:in `constantize'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/core_ext/string/inflections.rb:68:in `constantize'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/jsonapi.rb-1.5.7/lib/jsonapi/rails.rb:127:in `serializer_class'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/jsonapi.rb-1.5.7/lib/jsonapi/rails.rb:54:in `block in add_errors_renderer!'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_controller/metal/renderers.rb:150:in `block in _render_to_body_with_renderer'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/2.6.0/set.rb:338:in `each_key'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/2.6.0/set.rb:338:in `each'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_controller/metal/renderers.rb:146:in `_render_to_body_with_renderer'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_controller/metal/renderers.rb:142:in `render_to_body'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/abstract_controller/rendering.rb:25:in `render'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_controller/metal/rendering.rb:36:in `render'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_controller/metal/instrumentation.rb:44:in `block (2 levels) in render'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/core_ext/benchmark.rb:14:in `block in ms'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/2.6.0/benchmark.rb:308:in `realtime'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/core_ext/benchmark.rb:14:in `ms'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_controller/metal/instrumentation.rb:44:in `block in render'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_controller/metal/instrumentation.rb:85:in `cleanup_view_runtime'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/railties/controller_runtime.rb:34:in `cleanup_view_runtime'",
 "/Users/rafaltrojanowski/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_controller/metal/instrumentation.rb:43:in `render'",
stas commented

#<NameError: uninitialized constant NonValidServiceObjectSerializer>

@rafaltrojanowski this looks like like an error related to not providing a serialization class for the NonValidServiceObject that you defined.

Which makes sense if the errors you're trying to render are not ActiveModel::Errors:
https://github.com/stas/jsonapi.rb/blob/master/lib/jsonapi/rails.rb#L45-L46

Why not just manually provide the error handling based on the example I already shared with you?!

Generally speaking, you can generate any valid error response easily, here's an example:
https://github.com/stas/jsonapi.rb/blob/master/lib/jsonapi/errors.rb#L36-L39

Thanks :)