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'.
Requests
Requests are enumerated using the request method and required immediately via the relative path specified via request_path.
class Foo::Client < Cistern::Service
request_path "my-foo/requests"
request :get_bar # require my-foo/requests/get_bar.rb
request :get_bars # require my-foo/requests/get_bars.rb
class Real
def request(url)
Net::HTTP.get(url)
end
end
end
A request is method defined within the context of service and mode (Real or Mock). Defining requests within the service mock class is optional.
# my-foo/requests/get_bar.rb
class Foo::Client
class Real
def ()
request("http://example.org/bar/#{bar_id}")
end
end # Real
# optional, but encouraged
class Mock
def
# do some mock things
end
end # Mock
end # Foo::client
All declared requests can be listed via Cistern::Service#requests.
Foo::Client.requests # => [:get_bar, :get_bars]
Models and Collections
Models and collections have declaration semantics similar to requests. Models and collections are enumerated via model and collection respectively.
class Foo::Client < Cistern::Service
model_path "my-foo/models"
model :bar # require my-foo/models/bar.rb
collection :bars # require my-foo/models/bars.rb
end
Initialization
Service initialization parameters are enumerated by requires and recognizes. recognizes parameters are optional.
class Foo::Client < Cistern::Service
requires :hmac_id, :hmac_secret
recognizes :url
end
# Acceptable
Foo::Client.new(hmac_id: "1", hmac_secret: "2") # Foo::Client::Real
Foo::Client.new(hmac_id: "1", hmac_secret: "2", url: "http://example.org") # Foo::Client::Real
# ArgumentError
Foo::Client.new(hmac_id: "1", url: "http://example.org")
Foo::Client.new(hmac_id: "1")
Mocking
Cistern strongly encourages you to generate mock support for service. Mocking can be enabled using mock!.
Foo::Client.mocking? # falsey
real = Foo::Client.new # Foo::Client::Real
Foo::Client.mock!
Foo::Client.mocking? # true
fake = Foo::Client.new # Foo::Client::Mock
Foo::Client.unmock!
Foo::Client.mocking? # false
real.is_a?(Foo::Client::Real) # true
fake.is_a?(Foo::Client::Mock) # true
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

