Ada-C5 / ada-pets

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Creating an API in Rails

Let's build a small Rails app that will act as an API for providing data about our amazing, adorable pets to other applications. The app will have the following routes:

  • /pets show all pets
  • /pets/:id shows a pet with the provided id
  • /pets/search?query=<the search term> fuzzy searches pets by name, shows all matching pets

So the plan is to TDD a Rails app to act as our api. So we'll need a fresh sandbox to play in, plus a boilerplate minitest setup. Because we're friends and I value your time, I made this repo for you!

$ cd ~/C5/exercises
$ git clone git@github.com:AdaGold/ada-pets.git
$ bundle
$ rake db:migrate db:seed

Let's take a moment and walk through what we've got. [Minutes Pass]

Warmup: Let's get our model tests passing...

Now let's run our tests and start fixing stuff...

  • missing route
  • missing controller action

The next error we get is where we diverge from our standard approach. The error should be something like:

Missing template pets/index, application/index...

Since we are building a JSON api, we don't want to render an html template (or really use any of the view layer). Instead we want to return the requested data in a standard format. Using the render method in the controller, we can choose to return json:

# pets_controller.rb
def index
  @pets = Pet.all
  render json: { ready_for_lunch: "yassss" }
end

Notice that we didn't for realsies write any JSON. We provided a plain Ruby hash and let Rails do the conversion for us (with the render :json call. So to make progress on our tests, we could do something like:

# pets_controller.rb
def index
  pets = Pet.all
  render json: pets
end

Now curl gives us output that looks like:

[{"id"=>1,
  "name"=>"rosa",
  "age"=>0,
  "human"=>"jeremy",
  "created_at"=>Fri, 14 Aug 2015 20:55:22 UTC +00:00,
  "updated_at"=>Fri, 14 Aug 2015 20:56:26 UTC +00:00},
 {"id"=>2,
  "name"=>"rosa",
  "age"=>nil,
  "human"=>"jeremy2",
  "created_at"=>Fri, 14 Aug 2015 20:55:42 UTC +00:00,
  "updated_at"=>Fri, 14 Aug 2015 20:55:42 UTC +00:00}]

as_json

ActiveRecord provides a great method for presenting models as json objects. It's called, as you may have guessed, as_json. Let's give it a try:

# pets_controller.rb
class PetsController < ApplicationController

  def index
    pets = Pet.all
    render json: pets.as_json(except: [:created_at, :updated_at])
  end
end

Response Codes

We've built a simple API that responds with some data. We could let the consumer of our API parse that data to figure out if their request was successful or if there was an error of some sort, but that seems like cumbersome for them. Instead, we can use HTTP status codes to provide a quick and easy way for our API's users to see the status of their request.

To set status code in your controller, just pass :status to our render method.

def index
  pets = Pet.all
  render :json => pets.as_json(except: [:created_at, :updated_at]), :status => :ok
end

Notice in the example above, I used both :ok as well as the official numeric value of 200 to inform the consumer that the request was a success. I tend to use the built-in Rails symbols for this, as they're more explicit, however its good to know at least the most common HTTP status codes.

  • 200 - :ok
  • 204 - :no_content
  • 301 - :moved_permanently
  • 400 - :bad_request
  • 401 - :unauthorized
  • 403 - :forbidden
  • 404 - :not_found
  • 410 - :gone
  • 422 - :unprocessable_entity
  • 500 - :internal_server_error

show

Let's add this same approach for the show action, start by adding a route, then updating the controller:

# pets_controller.rb
def show
  pet = Pet.find(params[:id])
  render json: pet.as_json(except: [:created_at, :updated_at])
end

What if we can't find a pet?

What if we get params that don't match a pet? What do we do? How should our code change? At the very least, we should make sure that we don't throw an error. Also, we should return a status code that indicates to the consumer (which is another service) that we couldn't find any content to match their request. Fortuantely, the 204 status code exists for exactly this reason. Let's change our show method to:

Note that we switch from using the find method to the find_by method because find will return an error before getting to the conditional if the given Pet has not been found.

def show
  pet = Pet.find_by(id: params[:id])

  if pet
    render json: pet.as_json(except: [:created_at, :updated_at]), status: :ok
  else
    render json: [], status: 204
  end
end

Activity: Adding Search with your Chair Pair

  1. Write at least one positive (search found pets) and one negative (search didn't find pets) test.
  2. Implement the search feature in the Pet model
  3. Create a route and controller action for searching for a pet by name
  4. Make the action return a collection of pets as json

Resources

About


Languages

Language:Ruby 85.0%Language:HTML 15.0%