Code Monkey home page Code Monkey logo

resource_kit's Introduction

Resource Kit

Resource Kit 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_kit'

And then execute:

$ bundle

Or install it yourself as:

$ gem install resource_kit

Usage

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

Resource classes

Resource Kit 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 < ResourceKit::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 < ResourceKit::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 < ResourceKit::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. ResourceKit 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

ResourceKit 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 < ResourceKit::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

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

Make sure you:

require 'resource_kit/testing'

Testing a certain action:

# Tag the spec with resource_kit to bring in the helpers
RSpec.describe MyResourceClass, resource_kit: 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/digitaloceancloud/resource_kit/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

Releasing

  1. Update the version
  2. Commit the version and push it to master
  3. Run rake release

Note: In order to run rake release you must be authenticated w/ rubygems.org. To do this, you need to locate the rubygems account information contained within DOs lastpass account (search for rubygems). Once you have done this, do the following:

  1. gem push
  2. Follow the prompts and add the required information
  3. Run rake release

resource_kit's People

Contributors

activefx avatar andrewsomething avatar bentranter avatar bobbytables avatar davidcelis avatar firefishy avatar graywh avatar lsiv568 avatar mauricio avatar nanzhong avatar phillbaker avatar vjustov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

resource_kit's Issues

Use Sawyer instead/alongside of Faraday

Resource Kit is really cool, great job on it guys!

I thought I'd suggest a change that would be quite useful โ€” the ability to use Sawyer instead/alongside of, Faraday. Sawyer is built on top of Faraday and provides some really helpful features โ€” these features can be achieved with Faraday but require a lot of additional middleware. The Octokit gem uses Sawyer in a really great way in which Octokit doesn't have to worry about parsing JSON or anything like that. I just wanted to know whether you think this change would be something you'd like to see in Resource Kit?

Add failure message to the tests matchers

When using the test helpers, rspec complains that the HaveActionMatchers and the ActionHandlerMatchers neither have a failure_message method defined. This is a bit misleading and requires rspec knowledge to understand whats going on.

Failure/Error: expect(described_class).to have_action(:find).that_handles(:ok).at_path('/api/v1/shoppers')
     NoMethodError:
       undefined method `failure_message' for #<ResourceKit::Testing::HaveActionMatchers:0x007f89334343d8>

Mapping parameter names passed to ResourceKit::ActionInvoker

I'm still noodling the most elegant way to add this functionality, but I wanted to create an issue in case you had any ideas. I would like to map Action argument names in the same way that Kartograph allows mapping of key values for API responses to more idiomatic ruby-style names.

For example, in our mapping:
Kartograph::DSL populates Customer.customer_id based on an JSON key of customerID thus making the Customer object more ruby friendly.

Sadly, the API endpoints I am working with use these non-ruby style keys as query_params which requires the following:

client.customers.find(customerID: 1) rather than client.customers.find(customer_id: 1)

Consider this a more general issue than supporting a singular find on a resource that could be performed by constructing the path directly. Just a generic case where you have *args you want to map to query_keys with different symbol names. For example:

resources do
  action :all do
  verb :get
  path '/API/Account/:account_id/Customer.json'
  param_mapping { :customer_type_id => :customerTypeID, :time_stamp => :timeStamp }
  query_keys :limit, :offset, :customerTypeID, :timeStamp
  handler(200) {|response| CustomerMapping.extract_collection(response.body, :read) }
end

param_mapping would be fed to a helper method on ActionInvoker to rekey the passed in @options. Using something like Facets would make implementation easy, but it still feels hacky to me. I will submit a pull request if I come up with anything good.

Allow the definition of default handlers on a class level

Right now when I try to do the following

class GenericResource < ResourceKit::Resource
  class InvalidCredentials < StandardError; end

  default_handler(401) { |response| raise InvalidCredentials, response.status }
end

I get the error undefined method default_handler for GenericResource. This behavior doesn't comply with the issue #7

We could add a default_handler method to resource so that it could store the values of the handlers and then pass them to the resource_collection so that the initializer could look something like this:

module ResourceKit
  class ResourceCollection
    ...
    def initialize(default_handlers = {})
      @collection = []
      @default_handlers = default_handlers
    end
    ...

    private
    attr_reader :default_handlers
  end
end

Default Connection Factory

It would be great if we could add a way to generate a default connection. Something like:

ResourceKit.configure do |config|
  config.default_connection { Faraday.new(url: 'hello.com') }
end

Confusing documentation of body method for action

I notices the documentation for how to use the body method of an action had a comment about generating a response body. Looking at the code this method appears to be used to generate the request body.

 body { |object| DropletMapping.representation_for(:create, object) } # Generate a response body from a passed object

Is this comment correct or should it say something more on the lines of Generate a request body...

handle_response matchers doesnt handle exceptions

When using the ActionHandlerMatchers helper to test for a handler that throws an exception, the only way to capture the excaption is encapsulating the test in a begin/rescue block.

the resource:

class FakeResource < ResourceKit::Resource
  resources do
    default_handler(410) { |response| fail ShopperExpiredException }
    default_handler(404) { |response| fail RecordNotFound }

    action :find do
      path '/api/v1/fake_model/:id'

      handler(200) do |response|
        FakeMapping.extract_single(response.body, :read)
      end
    end
  end
end

and the spec:

describe '404' do
  it 'raises a RecordNotFound error' do
    expect(described_class).to handle_response(:find).with(status: 404, body: '{"users":[]}') { |handled_response| 
      expect { handled_response }.to raise_error
    }
  end
end

i can "accomplish" what i want by going:

describe '404' do
  it 'raises a RecordNotFound error' do
    begin
      expect(described_class).to handle_response(:find).with(status: 404, body: '{"users":[]}') { |handled_response| 
        expect { handled_response }.to raise_error
      }
    rescue => e
      expect(e).to be_a(RecordNotFound)
    end
  end
end

Although what's happening makes sense. I think there should be a way to catch any exception that trigger from within the handler.

How to handle a 302.

What's the recommended way to handle a 302 redirection with resource_kit? it's not following and I'd have to manually make a get request to the url in the location header?

Default Handlers for all status codes

When we receive a status code that we haven't defined a handler for, nasty things can happen for a user.

class UserResource < ResourceKit::Resource
  default_handler { |response| DefaultHandler.handle(response) }
end

This will handle all status codes that do not have a handler defined.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.