Как спастись от ActionDispatch::ParamsParser::ParseError в Rails 4

Rails 4 добавляет исключение ActionDispatch::ParamsParser::ParseError, но, поскольку оно находится в стеке промежуточного программного обеспечения, оно не может быть спасено в обычной среде контроллера. В приложении json API я хочу ответить стандартным форматом ошибки.

Этот суть показывает стратегию вставки промежуточного программного обеспечения для перехвата и ответа. По этому шаблону у меня есть:

application.rb:

module Traphos
  class Application < Rails::Application
    ....
    config.middleware.insert_before ActionDispatch::ParamsParser, "JSONParseError"
 end
end

А промежуточное ПО:

class JSONParseError
  def initialize(app)
    @app = app
  end

  def call(env)
    begin
      @app.call(env)
    rescue ActionDispatch::ParamsParser::ParseError => e
      [422, {}, ['Parse Error']]
    end
  end
end

Если я запускаю свой тест без промежуточного программного обеспечения, я получаю (спецификация):

Failures:

  1) Photo update attributes with non-parseable json
     Failure/Error: patch update_url, {:description => description}, "CONTENT_TYPE" => content_type, "HTTP_ACCEPT" => accepts, "HTTP_AUTHORIZATION" => @auth
     ActionDispatch::ParamsParser::ParseError:
       399: unexpected token at 'description=Test+New+Description]'

Это именно то, чего я ожидал (ParseError, от которого я не могу save_from).

Теперь с единственным изменением, которое нужно добавить в промежуточное программное обеспечение выше:

  2) Photo update attributes with non-parseable json
     Failure/Error: response.status.should eql(422)

       expected: 422
            got: 200

И журнал показывает, что стандартное действие контроллера выполняется и возвращает нормальный ответ (хотя, поскольку он не получил никаких параметров, он ничего не обновлял).

Мои вопросы:

  1. Как спастись от ParseError и вернуть собственный ответ. Кажется, я на правильном пути, но не совсем там.

  2. Я не могу понять, почему, когда возникает и спасается исключение, действие контроллера все еще продолжается.

Помощь очень ценится, --Кип


person Kip    schedule 17.03.2013    source источник
comment
есть ли какое-то другое промежуточное программное обеспечение, которое изменяет статус возврата? Вы делали отладку с помощью pry или что-то в этом роде?   -  person phoet    schedule 17.03.2013


Ответы (2)


Оказывается, в стеке промежуточного программного обеспечения ActionDispatch::ShowExceptions можно настроить с помощью приложения для обработки исключений.

module Traphos
  class Application < Rails::Application
    # For the exceptions app
    require "#{config.root}/lib/exceptions/public_exceptions"
    config.exceptions_app = Traphos::PublicExceptions.new(Rails.public_path)
  end
end

В значительной степени основанный на Rails, который я сейчас использую:

module Traphos
  class PublicExceptions
    attr_accessor :public_path

    def initialize(public_path)
      @public_path = public_path
    end

    def call(env)
      exception    = env["action_dispatch.exception"]
      status       = code_from_exception(env["PATH_INFO"][1..-1], exception)
      request      = ActionDispatch::Request.new(env)
      content_type = request.formats.first
      body         = {:status => { :code => status, :exception => exception.class.name, :message => exception.message }}
      render(status, content_type, body)
    end

    private

    def render(status, content_type, body)
      format = content_type && "to_#{content_type.to_sym}"
      if format && body.respond_to?(format)
        render_format(status, content_type, body.public_send(format))
      else
        render_html(status)
      end
    end

    def render_format(status, content_type, body)
      [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
                'Content-Length' => body.bytesize.to_s}, [body]]
    end

    def render_html(status)
      found = false
      path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
      path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))

      if found || File.exist?(path)
        render_format(status, 'text/html', File.read(path))
      else
        [404, { "X-Cascade" => "pass" }, []]
      end
    end

    def code_from_exception(status, exception)
      case exception
      when ActionDispatch::ParamsParser::ParseError
        "422"
      else
        status
      end
    end
  end
end

Чтобы использовать его в тестовой среде, необходимо установить переменные конфигурации (в противном случае вы получите стандартную обработку исключений при разработке и тестировании). Итак, чтобы проверить, что у меня есть (отредактировано, чтобы иметь только ключевые части):

describe Photo, :type => :api do
  context 'update' do
    it 'attributes with non-parseable json' do 

      Rails.application.config.consider_all_requests_local = false
      Rails.application.config.action_dispatch.show_exceptions = true

      patch update_url, {:description => description}
      response.status.should eql(422)
      result = JSON.parse(response.body)
      result['status']['exception'].should match(/ParseError/)

      Rails.application.config.consider_all_requests_local = true
      Rails.application.config.action_dispatch.show_exceptions = false
    end
  end
end

Который работает так, как мне нужно, в общедоступном API и адаптируется для любых других исключений, которые я могу настроить.

person Kip    schedule 18.03.2013

Эта статья (также из 2013 г.) thoughtbot также охватывает Эта тема. Они помещают свой ответ в эту службу промежуточного программного обеспечения, только если вы запросили json

if env['HTTP_ACCEPT'] =~ /application\/json/
    error_output = "There was a problem in the JSON you submitted: #{error}"
    return [
      400, { "Content-Type" => "application/json" },
      [ { status: 400, error: error_output }.to_json ]
    ]
else
 raise error
end
person theDrifter    schedule 10.08.2016