Skip to content

Instantly share code, notes, and snippets.

@Jpuelpan
Last active December 5, 2016 19:06
Show Gist options
  • Save Jpuelpan/ccf89b533b6bf0da1b0c3445ff920707 to your computer and use it in GitHub Desktop.
Save Jpuelpan/ccf89b533b6bf0da1b0c3445ff920707 to your computer and use it in GitHub Desktop.
Abstract REST API Wrapper
require 'json'
require 'faraday'
require 'hashie/mash'
class AbstractApi
BASE_URL = ENV['ABSTRACT_API_BASE_URL'] || 'https://myapp.com/api'
API_VERSION = ENV['ABSTRACT_API_BASE_URL_VERSION'] || 'v1'
class Client
attr_reader :access_token, :base_url, :apiver
def initialize(**options)
@base_url = options[:base_url] || AbstractApi::BASE_URL
@apiver = options[:apiver] || AbstractApi::API_VERSION
@access_token = options[:access_token]
end
def method_missing(name, *params, &block)
AbstractApi::Request.new(name.to_s, self)
end
end
class Request
attr_accessor :path, :filters, :chain
METHODS_MAP = {
'all' => 'get',
'head' => 'head',
'find' => 'get',
'create' => 'post',
'update' => 'put',
'destroy' => 'delete'
}.freeze
def initialize(*options)
@client = options[1]
@path = []
@chain = []
@filters = Hashie::Mash.new
@params = Hashie::Mash.new
method_name = options[0]
unless method_name.nil?
@path << method_name
@chain << method_name
end
end
def method_missing(name, *params, &block)
method_name = name.to_s
return self if chain.last == method_name
chain.push(method_name)
case method_name
when 'all'
filters.merge!(params.first || {})
when 'head'
filters.merge!(params.first || {})
when 'find'
path.push(params.first.to_s)
when 'create'
@params = Hashie::Mash.new(params.first || {})
when 'update'
if chain[-2] == 'find'
@params = Hashie::Mash.new(params.first || {})
end
when 'destroy'
if chain[-2] == 'find'
path.push(params.first.to_s)
end
else
path.push(method_name)
end
self
end
def run
method = METHODS_MAP[@chain.last] || 'get'
params = @params.any? ? @params.to_json : nil
request = Faraday.send(method, endpoint, params, headers)
response = AbstractApi::Response.new(request)
# Reset variables
path = []
chain = []
filters = Hashie::Mash.new
params = Hashie::Mash.new
response.body
end
def headers
{
'Authorization' => "token #{@client.access_token}",
'Content-Type' => 'application/json'
}
end
def endpoint
# Here is a Query String API versioning with the `apiver` param
# You can change this to whatever your API versioning system is
filters.apiver = @client.apiver
query_string = filters.map { |k, v| "#{k}=#{v}" }.join('&')
"#{@client.base_url}/#{path.join('/')}?#{query_string}"
end
end
class Response
def initialize(request)
@request = request
@parsed_body = if (request.body.nil? || request.body.empty?)
[]
else
JSON.parse(request.body)
end
end
def body
if @parsed_body.is_a?(Hash)
AbstractApi::Response::Resource.new(@parsed_body, @request)
elsif @parsed_body.is_a?(Array)
collection = @parsed_body.map do |item|
AbstractApi::Response::Resource.new(item)
end
AbstractApi::Response::Collection.new(collection, @request)
end
end
class Resource < Hashie::Mash
attr_reader :request
def initialize(*options)
@request = options[1] if options[1]
super(options[0])
end
end
class Collection < Array
attr_reader :request
def initialize(*options)
@request = options[1] if options[1]
super(options[0])
end
def headers
@headers ||= @request.headers
end
def pagination
@pagination ||= Hashie::Mash.new(
next_page: headers['x-next-page'].to_i,
offset: headers['x-offset'].to_i,
page: headers['x-page'].to_i,
per_page: headers['x-per-page'].to_i,
prev_page: headers['prev_page'].to_i,
total: headers['x-total'].to_i,
total_pages: headers['x-total-pages'].to_i
)
end
end
end
end
@Jpuelpan
Copy link
Author

Jpuelpan commented Dec 5, 2016

Usage

An example with the basecamp API

# Modify the `endpoint` method appending the `.json` extension to all requests
def endpoint
  query_string   = filters.map { |k, v| "#{k}=#{v}" }.join('&')
  "#{@client.base_url}/#{path.join('/')}.json?#{query_string}"
end
ACCESS_TOKEN = "YOUR ACCESS TOKEN"
client = AbstractApi::Client.new(access_token: ACCESS_TOKEN, base_url: 'https://launchpad.37signals.com')
authorizations = client.authorization.all.run
# => <AbstractApi::Response::Resource accounts=#<Hashie::Array [#<AbstractApi::Response::Resource app_href="https://basecamp.com/xxxxxxx" href="https://basecamp.com/xxxxxxx/api/v1" id=xxxxxxx name="Your Company" product="bcx">, #<AbstractApi::Response::Resource app_href="https://basecamp.com/xxxxxxx" href="https://basecamp.com/xxxxxxx/api/v1" id=xxxxxxx name="Another company" product="bcx">]> expires_at="2016-12-19T18:13:52.000Z" identity=#<AbstractApi::Response::Resource email_address="[email protected]" first_name="Juan" id=xxxxxx last_name="Puelpan">>

account = authorizations.accounts.first
client = AbstractApi::Client.new(access_token: ACCESS_TOKEN, base_url: account.href)
projects = client.projects.all.run
puts projects
puts projects.pagination

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment