Reputation: 1
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
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