API BEE
API Bee is a small client / spec for a particular style of JSON API.
These APIs must
- Expose resource collections as paginated lists, with entries and paging properties.
- Resource entities in collections must include an 'href' property pointing to the individual resource, so as to provide a degree of discoverability.
A single resource might look like:
{
'name': 'Foo',
'description': 'foo resoruce',
'href': 'http://api.myservice.com/resources/foo'
}
A resource collection looks like:
{
'href': 'http://api.myservice.com/resources',
'total_entries': 100,
'page': 1,
'per_page': 10,
'entries': [
{
'name': 'Foo',
'description': 'foo resoruce',
'href': 'http://api.myservice.com/resources/foo'
},
{
'name': 'Bar',
'description': 'bar resoruce',
'href': 'http://api.myservice.com/resources/bar'
},
...
]
}
Collection resources must include the fields 'href', 'total_entries', 'page', 'per_page' and 'entries'. This allows clients to paginate and fetch more pages.
Adapters
It is up to individual adapters to talk to different services, handle auth, etc. An adapter must at least implement 'get' for read-only APIs.
class ApiBee::Adapters::Special
def get(path, = {})
# fetch JSON from remote API, passing pagination options if available
end
end
The use it:
api = ApiBee.setup :special, optional_custom_data
api.get('/my/resources').each do |r|
r[:name]
end
Delegate to adapter
Lazy-loading and paginating resources is great for GET requests, but you might want to still use your adapter's other methods.
api = ApiBee.setup(MyCustomAdapter) do |config|
config.expose :delete, :post
end
# This still wraps your adapter's get() method and adds lazy-loading and pagination
api.get('/products').first[:title]
# This delegates directory to MyCustomAdapter#post()
api.post('/products', :title => 'Foo', :price => 100.0)
finding a single resource
There's a special get_one method that you can call on lists. It delegates to the adapter and it's useful for finding a single resource in the context of a paginated list.
resources = api.get('/my/resources')
resource = resources.get_one('foobar')
That delegates to Adapter#get_one passing 2 arguments: the list's href and the passed name or identifier, so:
class ApiBee::Adapters::Special
# ...
def get_one(href, id)
get "#{href}/#{id}"
end
end
Lazy loading
ApiBee wraps your adapters in lazy-loading objects. API calls will only be issued when accessing or iterating data.
The 'href' property in entities will be used to load more data. For example:
# /resources
[
{
'title': 'Foo bar',
'href': '/resources/foo-bar'
}
]
# /resources/foo-bar
{
'title': 'Foo bar',
'description': 'Foo description'
}
api = ApiBee.get(:some_adapter)
resources = api.get('/resources') # no API call is made
resource = resources.first # call to /resources is made
resource['title'] # => 'Foo bar', title data available
resource['description'] # => 'Foo description'. Makes internal new request to /resources/foo-bar
Hash adapter
ApiBee ships with an in-memory Hash adapter so it can be use with test/local data (for example a YAML file).
api = ApiBee.setup(:hash, YAML.load_file('./my_data.yml'))
products = api.get('/products') # => ApiBee::Node::List
products.first # => ApiBee::Node::Single
products.each() # iterate current page
products.current_page # => 1
products.paginate(:page => 2, :per_page => 4) # => ApiBee::Node::List # Next page
products.has_next_page? # => false
products.has_prev_page? # => true
products.prev_page # => 1
Examples
See examples/github_api.rb for an adapter that paginates Github's API by decorating it's results with ApiBee's required pagination properties