JSON API

json-api is a simple library similar to rest-client and httparty, which helps with wrappers around APIs.

Installing

gem install json_api

require 'json-api'

Quick requests

If you want to do only a couple of requests, then you could call methods directly on JsonApi.
get, post, delete and put methods are available. Parameters are passed as a hash.
code, body, json, hash, message are the most noticeable attributes of responses.

res = JsonApi.get 'https://api.github.com/search/repositories', q: 'created:>2015-04-01 stars:>100'

puts res.code #=> '200'
puts res.body #=> "{\"total_count\":82,\"incomplete_results\":false,\"items\":[{\"id\":33538019..."
puts res.json #=> pretty json
puts res.hash #=> ruby hash object from parsed json

Writing a wrapper

When you work with an API probably you would like to specify base_path for all requests.
Also you may want to specify some default_params.
Now lets create a wrapper with a parametrized method.

class GitHubApi
  include JsonApi

  def initialize
    @base_path      = 'https://api.github.com'
    @default_params = { per_page: 12 }
  end

  def search(created, stars)
    res = get 'search/repositories', q: "created:>#{created} stars:>#{stars}"

    raise res.hash['message'] unless res.ok?
    res.hash['items']
  end
end

repos = GitHubApi.search('2015-04-01', 100) #=> array with 12 repositories

We've included JsonApi module.
As you can see we can specify @base_path and @default_params in initialization.
It will work for all requests.
Methods like get method are available now in the class,
and we've used get in search method body.
See "Quick requests" for details about http methods and response methods.

Parametrized initialization

Note we've used GitHubApi.search instead of GitHubApi.new.search variant.
Library allows you to do that.
But usually you need to configure an instance of wrapper.

class GitHubApi
  include JsonApi

  def initialize(per_page)
    @base_path      = 'https://api.github.com'
    @default_params = { per_page: per_page }
  end

  # ...
end

github_api = GitHubApi.new(12)

repos = github_api.search('2015-04-01', 100) #=> array with 12 repositories

Error handling

Usually an API have a consistent way of returning errors.
And usually you want to handle them consistently.

Suppose we need to handle errors as following:

raise 'GitHubApi: Error message: ' + res.hash['message'] unless res.ok?

It would be too bad to write such code everywhere.
So write just raise res.error unless res.ok?, and add next method:

def error(res)
  'GitHubApi: Error message: ' + res.hash['message']
end

error method will be generated automatically for responses.
ok? method which was used already several times returns true if code is "200".

Logging

You may specify logger for debugging. It should be a Proc object.
For example proc { |s| puts s } or just method(:puts).

class GitHubApi
  include JsonApi

  def initialize
    @base_path      = 'https://api.github.com'
    @default_params = { per_page: 12 }
    @logger         = method(:puts)
  end

  # ...
end

repos = GitHubApi.search('2015-04-01', 100)

Example with using ActiveSupport:

# standalone application
@logger = ActiveSupport::Logger.new(STDOUT).method(:info)
# in rails
@logger = logger.method(:info)

It will produce output:

[JsonApi#request begin]
# Request
Method - get
Path   - https://api.github.com/search/repositories
Params -
{:per_page=>12, :q=>"created:>2015-04-01 stars:>100"}
# Response
Code - 200
Body -
{
  "total_count": 82,
  "incomplete_results": false,
  "items": [
    {
      "id": 33538019,
      ...
[JsonApi#request end]

Configuring requests

If you want to provide headers, or prepare request in other way,
You may override method called configure_request.

def configure_request(req)
  req['Authorization'] = session[:token]
end

Routing

It's nice to have all routes in one single place.
The library provides routes class method, which accepts a hash.
For every named route #{name}_path method will be generated automatically.
Take a look at the code.

class GitHubApi
  include JsonApi

  def initialize
    @base_path      = 'https://api.github.com'
    @default_params = { per_page: 12 }
  end

  routes search: 'search/repositories',
           user: -> name { "users/#{name}" }

  def search(created, stars)
    res = get search_path, q: "created:>#{created} stars:>#{stars}"

    raise res.error unless res.ok?
    res.hash['items']
  end

  def user(name)
    res = get user_path(name)

    raise res.error unless res.ok?
    res.hash
  end

  def error(res)
    res.hash['message']
  end
end

repos = GitHubApi.search('2015-04-01', 100)
user = GitHubApi.user('dhh')

Author (Speransky Danil): Personal Page | LinkedIn | GitHub | StackOverflow