Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails JSON API parameter validation & error responses

I'm new to Rails and have a couple questions about validating parameters and returning error responses. I want to create a JSON API using the new Rails 5 API mode.

As far as I can tell, Rails recommends using "strong parameters" as a baseline for validating parameters. If I want to, for example, create a User class that requires either a phone number or email, I start with something like this in my UsersController.

def create
  @user = User.new(create_user_params)
end

def create_user_params
  params.require(:user).permit(:email, :phone)
end

Now if I want something a little more complex, I might add the following

def create
  arr_contains_at_least_one(params[:user], [:email, :phone])
  @user = User.new(create_user_params)
end

This brings us to my question.

What's the best way to return a "pretty" error response, both in the event of a default Rails error (ActionController::ParameterMissing) or a custom error? By pretty I mean, if I called the API endpoint in a browser, it'd return readable JSON with a descriptive message. If I run my server in production mode, and fail to provide a user parameter in the first example, rails returns:

An unhandled lowlevel error occurred. The application logs may have details.

This is obviously no good, especially if I wished to display the error to an end user. Here I assume the strong parameters pattern is for security and data integrity, not user experience. It would seem the same is true of model field validation. So I make the following adjustments.

def create
  return error_response("some error") unless arr_contains_at_least_one(params[:user], [:email, :phone])
  @user = User.new(create_user_params)
end

def error_response(msg, status = 400)
  render json: {"code":status, "message": msg}, :status => status 
end

This works, but now I'm forced to manually write parameter checks (to check for presence of mandatory parameters, and to validate parameters such as email addresses) and their corresponding error responses. If I'm using Rails's built-in field validation on models as well, this seems to violate the DRY principle. Further, designing a good error handling pattern requires quite a bit of custom implementation.

Am I missing out on some Rails magic or am I on the right track here?

EDIT: It looks like active record validations can be fairly easily wrapped in a JSON response (http://guides.rubyonrails.org/active_record_validations.html) but the question still remains for validating presence of parameters.

like image 382
CaptainStiggz Avatar asked Jul 18 '16 08:07

CaptainStiggz


1 Answers

I think you are confusing the use of strong_parameters with validation methods.

Strong parameters provides an interface for protecting attributes from end-user assignment. This makes Action Controller parameters forbidden to be used in Active Model mass assignment until they have been whitelisted.

http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html

If you whitelist parameters in strong-parameters, it means you will be able to mass-assign them to your ActiveRecord model. Or better, attributes which are not whitelisted will not get passed to your model.

They actually have nothing to do with validations itself. They are meant to protect your model for unpermitted parameter injection.

def create
  @user = User.new(create_user_params)
end

def create_user_params
  params.require(:user).permit(:email, :phone)
end

so your example:

POST /users body={user: {email: '[email protected]', phone: '1234', some_other: 'some other'}}

the some_other attribute will not get passed to your User.new

What you are looking for IS activerecord validations. In your specific case, I would write something like:

def create
  @user = User.new(create_user_params)
  if @user.save
    render json: @user
  else
    render @user.errors.full_messages.as_json, status: 400
  end
end

def create_user_params
  params.require(:user).permit(:email, :phone)
end

and then in your model:

class User < ActiveRecord::Base
  validates_precense_of :some_other #this will cause the user not to save, end thus, report an errer message
end
like image 184
Sander Garretsen Avatar answered Sep 20 '22 13:09

Sander Garretsen