ruby-on-railsruby-on-rails-6active-model-serializers

Changing "render" key from "plain" to "json" causes server error


Consider the following code

class AuthenticatedController < ApplicationController
  rescue_from InvalidCredentials, with: :unauthenticated

  def current_user
    raise InvalidCredentials
  end

  private

  def unauthenticated(_error)
    render plain: { error: 'text' }.to_json, status: :unauthorized
  end
end

class UsersController < AuthenticatedController
  def show
    render json: current_user
  end
end

When I request Users#show it renders {"error":"text"} just fine.

But if I change unauthenticated to

  def unauthenticated(_error)
    render json: { error: 'text' }, status: :unauthorized
  end

And the resource responds with {"status":500,"error":"Internal Server Error"}.

The stack trace:

Started GET "/api/v1/user" for ::1 at 2021-08-25 12:24:30 +0300
Processing by Api::V1::UsersController#show as JSON
  Parameters: {"user"=>{}}
Completed 500 Internal Server Error in 1ms (ActiveRecord: 0.0ms | Allocations: 214)

InvalidCredentials (InvalidCredentials):
app/controllers/users_controller.rb:in `show'
app/controllers/authenticated_controller.rb:in `current_user'
app/controllers/authenticated_controller.rb:in `unauthenticated'

So calling render json: {} in the handler causes the same error to be raised again. But at the same time render plain: '' does not.

What happens when I change key from plain to json and how to make it respond with :unauthorized instead of server error when using JSON?


Solution

  • I was using active_model_serializers library for rendering JSON. It uses scope option of render as a method name to invoke and it is current_user by default. So the library invoked current_user method again when I called render json: ... leading to the error.

    I've changed unauthenticated method to

      def unauthenticated(_error)
        render json: { error: 'text' }, status: :unauthorized, scope: nil
      end
    

    and it began to work as expected.