Cistern
Cistern helps you consistenly build your API clients and faciliates building mock support.
Usage
Service
This represents the remote service that you are wrapping. If the service name is 'foo' then a good name is 'Foo::Client'.
Service initialization will only accept parameters enumerated by requires and recognizes. model, collection, and request enumerate supported features and require them directly within the context of the model_path and request_path.
Mock.data is commonly used to store mock data. It is often easiest to use identity to raw response mappings within the Mock.data hash.
class Foo::Client < Cistern::Service
model_path "foo/models"
request_path "foo/requests"
model :bar
collection :bars
request :create_bar
request :get_bar
request :get_bars
requires :hmac_id, :hmac_secret
recognizes :host
class Real
def initialize(={})
# setup connection
end
end
class Mock
def self.data
@data ||= {
:bars => {},
}
end
def self.reset!
@data = nil
end
def data
self.class.data
end
def initialize(={})
# setup mock data
end
end
end
Model
connection represents the associated Foo::Client instance.
class Foo::Client::Bar < Cistern::Model
identity :id
attribute :flavor
attribute :keypair_id, aliases: "keypair", squash: "id"
attribute :private_ips, type: :array
def destroy
params = {
"id" => self.identity
}
self.connection.(params).body["request"]
end
def save
requires :keypair_id
params = {
"keypair" => self.keypair_id,
"bar" => {
"flavor" => self.flavor,
},
}
if new_record?
merge_attributes(connection.(params).body["bar"])
else
requires :identity
merge_attributes(connection.(params).body["bar"])
end
end
end
Collection
model tells Cistern which class is contained within the collection. Cistern::Collection inherits from Array and lazy loads where applicable.
class Foo::Client::Bars < Cistern::Collection
model Foo::Client::Bar
def all(params = {})
response = connection.(params)
data = response.body
self.load(data["bars"]) # store bar records in collection
self.merge_attributes(data) # store any other attributes of the response on the collection
end
def discover(provisioned_id, ={})
params = {
"provisioned_id" => provisioned_id,
}
params.merge!("location" => [:location]) if .key?(:location)
connection.requests.new(connection.(params).body["request"])
end
def get(id)
if data = connection.("id" => id).body["bar"]
new(data)
else
nil
end
end
end
Request
module Foo
class Client
class Real
def (={})
request(
:body => {"bar" => },
:method => :post,
:path => '/bar'
)
end
end # Real
class Mock
def (={})
id = Foo.random_hex(6)
= {
"id" => id
}.merge()
self.data[:bars][id]=
response(
:body => {"bar" => },
:status => 201,
:path => '/bar',
)
end
end # Mock
end # Client
end # Foo
Examples
Releasing
$ gem bump -trv (major|minor|patch)
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Added some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request

