Harpy
Client for REST API with HATEOAS
Dependencies
- Ruby >= 2.4.
- gem "typhoeus", ">= 0.6.5"
- gem "activesupport", ">= 5.2.0"
- gem "activemodel", ">= 5.2.0"
- gem "hash-deep-merge", ">= 0.1.1"
Usage
-
Set entry_point url:
Harpy.entry_point_url = "http://localhost"
-
Include
Harpy::Resource
in your model:class MyModel include Harpy::Resource end # Mass assignment model = MyModel.new "firstname" => "Anthony", "lastname" => "Stark" model.attributes = {"company" => "Stark Enterprises"} model.firstname # => "Anthony" model.company # => "Stark Enterprises" # Because model is not persisted you can read any attribute, allowing # to use form_for on new resources to which the client doesn't know the # existing attributes yet model.email # => nil # Fetch by url MyModel.from_url "http://localhost/mymodel/1" # => instance of MyModel with attributes filled in on 200 # => nil on 404 # => raises Harpy::ClientTimeout on timeout # => raises Harpy::ClientError on Curl error # => raises Harpy::InvalidResponseCode on other response codes # Fetch multiple by url in parallel MyModel.from_url ["http://localhost/mymodel/1", "http://localhost/mymodel/2"] # Get index MyModel.search # will call GET http://localhost/mymodel given the following entry_point response: { "link": [ {"rel": "my_model", "href": "http://localhost/mymodel"} ] } # => return an array of MyModel instances on 200 # => raises Harpy::ClientTimeout on timeout # => raises Harpy::ClientError on Curl error # => raises Harpy::InvalidResponseCode on other response codes # Search by first_name MyModel.search :firstname => "Anthony" # GET http://localhost/mymodel?firstname=Anthony # Create (POST) model = MyModel.new "firstname" => "Anthony" model.save # POST http://localhost/mymodel with {"firstname":"Anthony"} # Get an existing resource by url: model = MyModel.from_url "http://localhost/mymodel/1" # if the service returns the following response: { "firstname": "Anthony", "lastname": null, "urn": "urn:mycompany:mymodel:1" "link" => [ {"rel" => "self", "href" => "http://localhost/mymodel/1"}, {"rel" => "accounts", "href" => "http://localhost/mymodel/1/accounts"} ] } # we can then do: model.firstname # => "Anthony" model.link "self" # => "http://localhost/mymodel/1" model.link :accounts # => "http://localhost/mymodel/1/accounts" # Update (PUT) requires resource to have both urn and link to self model.attributes = {"firstname" => "Tony"} model.save # PUT http://localhost/mymodel/1 # The resource is persisted once it has an urn: model.persisted? # => true # If persisted you can no longer read undefined attributes: model.lastname # => nil model.email # => will raise NoMethodError
-
To find a resource by id you need to define
.urn
:class MyModel include Harpy::Resource def self.urn(id) "urn:mycompany:mymodel:#{id}" end end model = MyModel.from_id 1 # will GET http://localhost/urn:mycompany:mymodel:1 # expecting a permanent redirect (301) to follow or not found (404)
-
Rel name to search for in entry_point when getting index can be overridden:
class MyCustomModel include Harpy::Resource def self.resource_name "custom_model" end end
-
or you can use
.with_url(url)
for getting index of nested resources:class Account include Harpy::Resource def users User.with_url(link "user") do User.search end end end class User include Harpy::Resource end
-
you can override
#url_collection
to create nested resources:class Account include Harpy::Resource end class User include Harpy::Resource attr_accessor :account def url_collection account ? account.link("user") : super end end
-
Fetch multiple resources in parallel:
class FirstModel include Harpy::Resource end class SecondModel include Harpy::Resource end Harpy::Resource.from_url({ FirstModel => ["http://localhost/firstmodel/1", "http://localhost/firstmodel/2"], SecondModel => ["http://localhost/secondmodel/1"], }) # => {FirstModel => [...], SecondModel => [...]}
License
harpy is Copyright © 2011 TalentBox SA. It is free software, and may be redistributed under the terms specified in the LICENSE file.