Abusing method_missing
since 2013โข
Chain is a simple library that makes it (too) easy to interface with a (non)-RESTful web API. Inspired by Her, I needed a way to create something that mimics an ORM to communicate with a non-RESTful API. Chain uses Faraday as the client library to manage requests to API endpoints. As a result, you have full control of how the request and response are parsed out and mapped to an object in Ruby!
Simply instantiate the Url
class and then chain together a series of methods that represent the URL path. Finish off the chain with a bang to kick off the request. For example:
>> require 'chain'
>> site = Chain::Url("http://www.site.com")
>> item = site.items[1].group!
=> #Hashie::Mash of JSON from http://www.site.com/items/1/group
>> item.name
=> "..."
This opens up all sorts of clever ways to iterate through an endpoint. You can reuse objects and iterate through them multiple times.
# Create a Chain::Url instance that is associated with http://www.site.com/items, but do not fetch it yet.
items = site.items
# Assuming that /items returns a JSON object containing a list of items in the `data` attribute...
items._fetch.data.each do |item|
# ...re-use the "items" object to send a request to items/# and print out the `name` attribute
puts items[item.id]._fetch.name
end
You can specify query parameters via the []
, _fetch
, or _<insert your favorite http verb here>
. For example:
# Submit a GET request to http://www.site.com/users?name=Mark Corrigan
>> user = site.users[name: 'Mark Corrigan']
>> user = site[:users, name: 'Mark Corrigan']
>> user = site.users._fetch(name: 'Mark Corrigan`)
By default, the !
, []
, and _fetch
methods on a Url
object will map to a GET request. You can also use _put
, _post
, _head
, _delete
, etc.
To submit a request with a payload, send a request with the _body
parameter:
# Send a POST request to http://www.site.com/users with URL-encoded parameters in the payload
>> user = site.users._post(_body: {name: 'Mark Corrigan'})
You can also manually specify the HTTP verb via _method
and headers via _headers
:
>> user = site.users._fetch(_method: :post, _body: {name: 'Mark Corrigan'})
>> site.users._fetch(_method: :post, _headers: {"Accept" => "text/plain"})
By default, Chain will assume that the response is JSON and will render that object inside of a Hashie::Mash object. If you want to implement your own request/response middleware, simply pass in a block to configure the Faraday connection:
site = Chain.Url("http://www.site.com") do |connection|
connection.use Faraday::Request::UrlEncoded
connection.use MyResponseMiddleWare
connection.use Faraday::Adapter::NetHttp
end
Writing your own middleware is fairly easy. Chain uses something along the lines of:
class MyResponseMiddleWare < Faraday::Response::Middleware
def on_complete(env)
body = JSON.parse(env[:body])
headers = env[:response_headers]
env[:body] = Hashie::Mash.new(body).tap do |item|
item._headers = Hashie::Mash.new(headers)
item._status = env[:status]
end
end
end
-
You cannot follow the bracket notation by a bang, such as:
site.person["Mark Corrgian"]!
. Usesite.person["Mark Corrgian"]._fetch
. -
You cannot pass in a block to the bracket method, such as:
site.person["Mark Corrgian"]{|req| p req['url']}
. Use the_fetch
method as described above. -
For any urls that end with an extension, you will need to use the bracket notation. For example, site.users.json would render http://www.site.com/users/json.
-
Any portions of the url path that contain characters not supported by Ruby, you will need to use the bracket notation. This includes path segments that start with numerics, such as http://www.site.com/users/0. (It cannot be rendered as
site.users.0
, but rathersite.users[0]
.) -
You will not be able to access URL sub-paths that have names similar to methods on standard objects in Ruby. For example, site.users[1].methods (http://www.site.com/users/1/methods) will return you a list of methods on the Chain::Url object. Use bracket notation!