I've recently been working on a few RESTful API's using Rails. One of the

problems that I keep seeing with end users is that they usually don't read

the documentation very well and make simple mistakes when making specific

requests and queries. This is easily solved with error handling and

validation of the API. There are a few gems out there that will handle this

sort of situation for you, but there's already so much in Rails to help you

get this done out of the box.

First off, I like keeping my query validations separate by putting them into

app/controllers/validates and then including them at startup. Each controller

should then have their own validator.

Validating specific queries can be done with ActionController::Parameters.

By creating an ActionController::Parameters object and

passing in the request params, we can then #permit specific queries to be made.

For example, let's say you're building a location aware app that needs to query

via latitude and longitude. You can build an ActionController::Parameters

object to permit only the ?latitude and ?longitude queries:

module Validate class Location attr_accessor :latitude, :longitude def initialize(params={}) @latitude = params[:latitude] @longitude = params[:longitude] ActionController::Parameters.new(params).permit(:latitude,:longitude) end end end

If a query other than ?latitude=X&longitude=Y are passed, a

ActionController::UnpermittedParameters exception is returned. We'll need to

rescue_from this exception in order to return our error to the user. Our

Locations Controller would look something like:

class LocationsController < ApplicationController def index end ActionController::Parameters.action_on_unpermitted_parameters = :raise rescue_from(ActionController::UnpermittedParameters) do |pme| render json: { error: { unknown_parameters: pme.params } }, status: :bad_request end end

ActionController::Parameters.actiononunpermitted_parameters is set to :raise

so that errors are thrown instead of logged.

Now, a query such as /locations?latitude=47.60&longitude=-122.33 would return

a valid location for us, but a query containing anything else would return an

error. For example:

GET /locations?query=bad {"error":{"unknown_parameters":["query"]}}

So that's pretty awesome. Now end users of the API will know they can only use

specific queries. But which queries? It'd be nice if there was a way to let

an end user know which queries were needed. If only Rails had a way to

validate certain values...

Enter ActiveModel::Validations. The same validations we use on our

ActiveRecord Models can be used to validate queries we make to the API.

To do this, we need to include ActiveRecord::Validations in our API validations.

Then, you can validate queries just like you validate models.

module Validate class Activity include ActiveModel::Validations attr_accessor :latitude, :longitude validates :latitude, presence: true, numericality: true validates :longitude, presence: true, numericality: true def initialize(params={}) @latitude = params[:latitude] @longitude = params[:longitude] ActionController::Parameters.new(params).permit(:latitude,:longitude) end end end

Not only can we validate the presence of a certain query, we can validate that

it must be in a certain format, contain certain items, etc... Anything you can

validate with ActiveModel::Validations you can now validate in your API.

A few changes need to be made in our Locations Controller in order to make this

work:

class LocationsController < ApplicationController before_action :validate_params def index end rescue_from(ActionController::UnpermittedParameters) do |pme| render json: { error: { unknown_parameters: pme.params } }, status: :bad_request end private def validate_params location = Validate::Location.new(params) if !location.valid? render json: { error: location.errors } and return end end end

Now we get the following errors:

GET /locations?latitude=near&longitude=far {"error":{"latitude":["is not a number"],"longitude":["is not a number"]}}

There's so much more that you can do with this. I've built a demo app and put

it on Github to show off the usage described in this post. Check it out @

https://github.com/benwoody/validate_params