Resource Set

Resource Set provides tools to aid in making API Clients. Such as URL resolving, Request / Response layer, and more.

Installation

Add this line to your application's Gemfile:

gem 'resource_set'

And then execute:

$ bundle

Or install it yourself as:

$ gem install resource_set

Usage

This library recommends using Cartograph for representing and deserializing response bodies. You'll see it in the examples provided below.

Resource classes

Resource Set provides a comprehensive but intuitive DSL where you describe the remote resources capabilities. For example, where can I get a list of users? Where do I get a single user? How do I create a user?

When you're able to answer these questions, you can describe them in your resource class like this:

class DropletResource < ResourceSet::Resource
  resources do
    default_handler(422) { |response| ErrorMapping.extract_single(response.body, :read) }
    default_handler(:ok, :created) { |response| DropletMapping.extract_single(response.body, :read) }
    default_handler { |response| raise "Unexpected response status #{response.status}... #{response.body}" }

    # Defining actions will create instance methods on the resource class to call them.
    action :find do
      verb :get # get is assumed if this is omitted
      path '/droplets/:id'
      handler(200) { |response| DropletMapping.extract_single(response.body, :read) }
    end

    action :all do
      path '/droplets'
      handler(200) { |body| DropletMapping.extract_collection(body, :read) }
    end

    action :create do
      path '/droplets'
      verb :post
      body { |object| DropletMapping.representation_for(:create, object) } # Generate a response body from a passed object
      handler(202) { |response| DropletMapping.extract_single(response.body, :read) }
    end
  end
end

You also have the option to use a shorter version to describe actions like this:

class DropletResource < ResourceSet::Resource
  resources do
    action :all, 'GET /v2/droplets' do
      handler(:ok) { |response| DropletMapping.extract_collection(response.body, :read) }
    end
  end
end

Instead of using #action, you can use any of the supported HTTP verb methods including #get, #post, #put, #delete, #head, #patch, and #options. Thus, the above example can be also written as:

class DropletResource < ResourceSet::Resource
  resources do
    get :all, '/v2/droplets' do
      handler(:ok) { |response| DropletMapping.extract_collection(response.body, :read) }
    end
  end
end

Now that we've described our resources. We can instantiate our class with a connection object. ResourceSet relies on the interface that Faraday provides. For example:

conn = Faraday.new(url: 'http://api.digitalocean.com') do |req|
  req.adapter :net_http
end

resource = DropletResource.new(connection: conn)

Now that we've instantiated a resource with our class, we can call the actions we've defined on it.

all_droplets = resource.all
single_droplet = resource.find(id: 123)
create = resource.create(Droplet.new)

Scope

ResourceSet classes give you the option to pass in an optional scope object, so that you may interact with the resource with it that way.

For example, you may want to use this for nested resources:

class CommentResource < ResourceSet::Resource
  resources do
    action :all do
      path { "/users/#{user_id}/comments" }
      handler(200) { |resp| CommentMapping.extract_collection(resp.body, :read) }
    end
  end

  def user_id
    scope.user_id
  end
end

user = User.find(123)
resource = CommentResource.new(connection: conn, scope: user)
comments = resource.all #=> Will fetch from /users/123/comments

Test Helpers

ResourceSet supplys test helpers that assist in certain things you'd want your resource classes to do.

Make sure you:

require 'resource_set/testing'

Testing a certain action:

# Tag the spec with resource_set to bring in the helpers
RSpec.describe MyResourceClass, resource_set: true do
  it 'has an all action' do
    expect(MyResourceClass).to have_action(:all).that_handles(:ok, :no_content).at_path('/users')
  end

  it 'handles a 201 with response body' do
    expect(MyResourceClass).to handle_response(:create).with(status: 201, body: '{"users":[]}') do |handled|
      expect(handled).to all(be_kind_of(User))
    end
  end
end

Nice to have's

Things we've thought about but just haven't implemented are:

  • Pagination capabilities

Contributing

  1. Fork it ( https://github.com/kyrylo/resource_set/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request