Search code examples
ruby-on-railsrubyrefactoringraise

How to break Rails application and return 404 as JSON


Rails application working as API service. I use mongoid

In a controller i write now:

class UsersController < ApplicationController
  def show
    @user = User.find params[:user_id]
    unless @user
      render json: { error: I18n.t('user.messages.not_found') }, status: :not_found
      return
    end
    # ... some actions with @user object 
  end
end

I need make code cleaner and move this code in module. I assume that the code in controller may look like this:

class UsersController < ApplicationController
  def show
    @user = User.find params[:user_id]
    not_found? @user

    # ... some actions with @user object
  end
end

or like this

class UsersController < ApplicationController
  def show
    @user = found? User, params[:user_id]

    # ... some actions with @user object
  end
end

One of example how it can work is Pundit gem (https://github.com/elabs/pundit). In my controller i write:

...
def index
  authorize User, :index?
  @users = User.all
end
...

if user hasn't access to this page then Pundit raise exception and return to client error message with 401 http code.

What is better way to refactor my code to make controllers cleaner?


Solution

  • My solution:

    lib/myapp/exceptions.rb

    module MyApp
      module Exceptions
        class Error < StandardError; end
        class ModelNotFound < Error
    
          def initialize(model)
            # api_error - helper for create standart JSON-response
            @json = api_error("#{ model.model_name.plural }.#{ model.model_name.singular }_not_found")
            message = @json
            super(message)
          end
    
          def json
            @json
          end
        end
    
        def self.included(base)
          base.rescue_from MyApp::Exceptions::ModelNotFound do |e|
            render json: e.json, status: :not_found
          end
        end
      end
    end
    

    app/controllers/application_controller.rb

    class ApplicationController < ActionController::Base
    
      ...
      include MyApp::Exceptions
    
      def found?(model, id)
        @model = model.find id
        return @model if @model
        raise MyApp::Exceptions::ModelNotFound.new model
      end
    

    Sample of using:

    app/controllers/users_controller.rb

    class UsersController < ApplicationController
      def show
        @user = found? User, params[:user_id]
    
        # ... some actions with @user object
      end
    end
    

    when i execute GET request /users/77777 application returns:

    {
        'code': 'users_user_not_found',
        'error': 'User not found'
    }