CodingNewbie
CodingNewbie

Reputation: 1

How can I get a more specific 400 validation error instead of a 500 error when using params.require?

I'm working through a full-stack project using Rails on the back end and React on the front end. I have a Sign Up form designed to accept a new user's information and avatar and pass it to the Users controller on the back end. For whatever reason though, if the form is empty or incomplete, it throws a 500 Internal Server Error instead of a more informative 400 error.

User Controller is :

`` class UsersController < ApplicationController rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response

def index users = User.all render json: users end

def create user = User.create(user_params) session[:user_id] = user.id render json: user, status: :created end

  

    private
    
      def user_params
        params.require(:user).permit(:first_name, :last_name, :email, :birthday, :username, :password, :password_confirmation, :avatar) 
      end
    
      def render_unprocessable_entity_response(invalid)
        render json: { errors: invalid.record.errors.full_messages }, status: :unprocessable_entity
      end
    
    end

Sign Up Form in React (specifically, my Submit function)

  function handleSubmit(event) {
    event.preventDefault();
    const data = new FormData();

    data.append("[user][first_name]", firstName);
    data.append("[user][last_name]", lastName);
    data.append("[user][email]", email);
    data.append("[user][username]", username);
    data.append("[user][password]", password);
    data.append("[user][password_confirmation]", passwordConfirmation);
    data.append("[user][avatar]", avatar);

    fetch("/signup", {
      method: "POST",
      body: data
    })
    .then((response) => {
      if (response.ok) {
        response.json().then((user) => onLogin(user));
      } else {
        response.json().then((errorData) => contextData.setErrors(errorData.errors));
      }
    })
  };

Response I'm getting in the console when I submit an empty form:

  Parameters: {"user"=>{"first_name"=>"", "last_name"=>"", "email"=>"", "username"=>"", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "avatar"=>""}}
Completed 500 Internal Server Error in 97ms (ActiveRecord: 49.6ms | Allocations: 9234)

When I remove the "params.require(:user)" portion, it gives me the validation errors I was expecting, but then I'm unable to create a new user due to have ActiveStorage works and requires data.

Upvotes: 0

Views: 682

Answers (1)

max
max

Reputation: 102036

There is a lot wrong in the premise of this question so please bear with me.

First lets look at the docs for ActionController::Parameters#require:

Ensures that a parameter is present. If it's present, returns the parameter at the given key, otherwise raises an ActionController::ParameterMissing error.

Rails will rescue this error on the framework level but you can rescue it on a per controller level with rescue_from 'ActionController::ParameterMissing'. But remember that the purpose of require isn't validation. Its just to bail early if the request is missing the correct structure and should not be processed anyways - because classical Rails applications nest their parameters in a key with the same name as the resource by default.

In an API application you don't even have to follow that pattern - you could just as well use "flat parameters" or standards like JSONAPI.org.

Then there is the use of:

rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response

This pops up from time to time as a "clever" way to DRY out API's. In your case it won't even work because User.create does not raise - User.create! does.

It's also not a good use of exceptions. Exceptions should be used for exceptional events that should cause an immediate halt of the execution. A user passing invalid input is not exceptional in any way - in fact its completely mundane.

If you want to DRY your API in a good way instead do it the way that the Responders gem does:

class UsersController < ApplicationController

  def create
    user = User.create(user_params)
    session[:user_id] = user.id 
    respond_with(user)
  end
end
class ApplicationController
  # ...
  private

  def respond_with(resource)
    if resource.valid?
      resource.new_record? ? render_created_response(resource) : render_updated_response(resource)
    else
      render_unprocessable_entity_response(resource)
    end
  end

  def render_created_response(resource)
    render json: resource, status: :created
  end

  def render_updated_response(resource)
    render json: resource, status: :ok
  end
end

This creates an explicit flow of methods that can be overridden in subclasses. Instead of an implicit flow created by what is actually a callback.

class UsersController < ApplicationController
  private

  def render_updated_response
    # do something wild and craazy
  end
end

Reserve the use of create! for contexts where creating the record should not be expected to fail (like in a seed file or non-interactive contexts) or to cause rollbacks inside of a transaction.

Upvotes: 0

Related Questions