cistern 0.5.4 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +148 -123
- data/lib/cistern/attributes.rb +14 -0
- data/lib/cistern/coverage.rb +34 -0
- data/lib/cistern/formatter/awesome_print.rb +1 -1
- data/lib/cistern/service.rb +12 -1
- data/lib/cistern/version.rb +1 -1
- data/lib/cistern/wait_for.rb +2 -2
- data/spec/model_spec.rb +34 -0
- data/spec/spec_helper.rb +8 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83399a872cc26926353a8186ca5a2eeb5e35694f
|
4
|
+
data.tar.gz: a63f6fc98003b05aa829e18d976718a3698ba2e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a456bdd5e6b9578d3c56abe014e1dad0d8b1bb3371808198cb683797a95860997f17aabbea453a635462a25f71d0576181b02cb108d7bfd72521f717ea427dc
|
7
|
+
data.tar.gz: 094d70ac588adbf30f9430845e44ba80209149a96301822302d2f2bc044eb8e02cb2c569b8bd2c275b59adb01c1849331e228b2d664a55290992489b0236f6a5
|
data/README.md
CHANGED
@@ -11,172 +11,197 @@ Cistern helps you consistenly build your API clients and faciliates building moc
|
|
11
11
|
|
12
12
|
This represents the remote service that you are wrapping. If the service name is 'foo' then a good name is 'Foo::Client'.
|
13
13
|
|
14
|
-
|
14
|
+
#### Requests
|
15
15
|
|
16
|
-
|
16
|
+
Requests are enumerated using the ```request``` method and required immediately via the relative path specified via ```request_path```.
|
17
17
|
|
18
|
-
|
18
|
+
class Foo::Client < Cistern::Service
|
19
|
+
request_path "my-foo/requests"
|
19
20
|
|
20
|
-
|
21
|
+
request :get_bar # require my-foo/requests/get_bar.rb
|
22
|
+
request :get_bars # require my-foo/requests/get_bars.rb
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
+
class Real
|
25
|
+
def request(url)
|
26
|
+
Net::HTTP.get(url)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
24
30
|
|
25
|
-
model :bar
|
26
|
-
collection :bars
|
27
|
-
request :create_bar
|
28
|
-
request :get_bar
|
29
|
-
request :get_bars
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
<!--todo move to a request section-->
|
33
|
+
A request is method defined within the context of service and mode (Real or Mock). Defining requests within the service mock class is optional.
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
# my-foo/requests/get_bar.rb
|
36
|
+
class Foo::Client
|
37
|
+
class Real
|
38
|
+
def get_bar(bar_id)
|
39
|
+
request("http://example.org/bar/#{bar_id}")
|
40
|
+
end
|
41
|
+
end # Real
|
39
42
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
# optional, but encouraged
|
44
|
+
class Mock
|
45
|
+
def get_bars
|
46
|
+
# do some mock things
|
47
|
+
end
|
48
|
+
end # Mock
|
49
|
+
end # Foo::client
|
46
50
|
|
47
|
-
|
48
|
-
@data = nil
|
49
|
-
end
|
51
|
+
All declared requests can be listed via ```Cistern::Service#requests```.
|
50
52
|
|
51
|
-
|
52
|
-
self.class.data
|
53
|
-
end
|
54
|
-
def initialize(options={})
|
55
|
-
# setup mock data
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
53
|
+
Foo::Client.requests # => [:get_bar, :get_bars]
|
59
54
|
|
60
|
-
|
55
|
+
#### Models and Collections
|
61
56
|
|
62
|
-
|
57
|
+
Models and collections have declaration semantics similar to requests. Models and collections are enumerated via ```model``` and ```collection``` respectively.
|
63
58
|
|
64
|
-
|
59
|
+
class Foo::Client < Cistern::Service
|
60
|
+
model_path "my-foo/models"
|
65
61
|
|
66
|
-
|
62
|
+
model :bar # require my-foo/models/bar.rb
|
63
|
+
collection :bars # require my-foo/models/bars.rb
|
64
|
+
end
|
67
65
|
|
68
|
-
|
69
|
-
identity :id
|
66
|
+
#### Initialization
|
70
67
|
|
71
|
-
|
72
|
-
attribute :keypair_id, aliases: "keypair", squash: "id"
|
73
|
-
attribute :private_ips, type: :array
|
68
|
+
Service initialization parameters are enumerated by ```requires``` and ```recognizes```. ```recognizes``` parameters are optional.
|
74
69
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
self.connection.destroy_bar(params).body["request"]
|
80
|
-
end
|
70
|
+
class Foo::Client < Cistern::Service
|
71
|
+
requires :hmac_id, :hmac_secret
|
72
|
+
recognizes :url
|
73
|
+
end
|
81
74
|
|
82
|
-
|
83
|
-
|
75
|
+
# Acceptable
|
76
|
+
Foo::Client.new(hmac_id: "1", hmac_secret: "2") # Foo::Client::Real
|
77
|
+
Foo::Client.new(hmac_id: "1", hmac_secret: "2", url: "http://example.org") # Foo::Client::Real
|
84
78
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
"flavor" => self.flavor,
|
89
|
-
},
|
90
|
-
}
|
79
|
+
# ArgumentError
|
80
|
+
Foo::Client.new(hmac_id: "1", url: "http://example.org")
|
81
|
+
Foo::Client.new(hmac_id: "1")
|
91
82
|
|
92
|
-
if new_record?
|
93
|
-
merge_attributes(connection.create_bar(params).body["bar"])
|
94
|
-
else
|
95
|
-
requires :identity
|
96
83
|
|
97
|
-
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
84
|
+
#### Mocking
|
101
85
|
|
102
|
-
```
|
86
|
+
Cistern strongly encourages you to generate mock support for service. Mocking can be enabled using ```mock!```.
|
103
87
|
|
104
|
-
|
88
|
+
Foo::Client.mocking? # falsey
|
89
|
+
real = Foo::Client.new # Foo::Client::Real
|
90
|
+
Foo::Client.mock!
|
91
|
+
Foo::Client.mocking? # true
|
92
|
+
fake = Foo::Client.new # Foo::Client::Mock
|
93
|
+
Foo::Client.unmock!
|
94
|
+
Foo::Client.mocking? # false
|
95
|
+
real.is_a?(Foo::Client::Real) # true
|
96
|
+
fake.is_a?(Foo::Client::Mock) # true
|
105
97
|
|
106
|
-
```model``` tells Cistern which class is contained within the collection. ```Cistern::Collection``` inherits from ```Array``` and lazy loads where applicable.
|
107
98
|
|
108
|
-
|
99
|
+
### Model
|
109
100
|
|
110
|
-
|
101
|
+
```connection``` represents the associated ```Foo::Client``` instance.
|
111
102
|
|
112
|
-
|
103
|
+
class Foo::Client::Bar < Cistern::Model
|
104
|
+
identity :id
|
113
105
|
|
114
|
-
|
115
|
-
|
106
|
+
attribute :flavor
|
107
|
+
attribute :keypair_id, aliases: "keypair", squash: "id"
|
108
|
+
attribute :private_ips, type: :array
|
116
109
|
|
117
|
-
|
110
|
+
def destroy
|
111
|
+
params = {
|
112
|
+
"id" => self.identity
|
113
|
+
}
|
114
|
+
self.connection.destroy_bar(params).body["request"]
|
115
|
+
end
|
118
116
|
|
119
|
-
|
120
|
-
|
121
|
-
end
|
117
|
+
def save
|
118
|
+
requires :keypair_id
|
122
119
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
120
|
+
params = {
|
121
|
+
"keypair" => self.keypair_id,
|
122
|
+
"bar" => {
|
123
|
+
"flavor" => self.flavor,
|
124
|
+
},
|
125
|
+
}
|
128
126
|
|
129
|
-
|
130
|
-
|
127
|
+
if new_record?
|
128
|
+
merge_attributes(connection.create_bar(params).body["bar"])
|
129
|
+
else
|
130
|
+
requires :identity
|
131
131
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
else
|
136
|
-
nil
|
132
|
+
merge_attributes(connection.update_bar(params).body["bar"])
|
133
|
+
end
|
134
|
+
end
|
137
135
|
end
|
138
|
-
end
|
139
|
-
end
|
140
136
|
|
141
|
-
|
137
|
+
### Collection
|
142
138
|
|
143
|
-
|
139
|
+
```model``` tells Cistern which class is contained within the collection. ```Cistern::Collection``` inherits from ```Array``` and lazy loads where applicable.
|
144
140
|
|
145
|
-
|
146
|
-
|
147
|
-
module Foo
|
148
|
-
class Client
|
149
|
-
class Real
|
150
|
-
def create_bar(options={})
|
151
|
-
request(
|
152
|
-
:body => {"bar" => options},
|
153
|
-
:method => :post,
|
154
|
-
:path => '/bar'
|
155
|
-
)
|
156
|
-
end
|
157
|
-
end # Real
|
141
|
+
class Foo::Client::Bars < Cistern::Collection
|
158
142
|
|
159
|
-
|
160
|
-
def create_bar(options={})
|
161
|
-
id = Foo.random_hex(6)
|
143
|
+
model Foo::Client::Bar
|
162
144
|
|
163
|
-
|
164
|
-
|
165
|
-
}.merge(options)
|
145
|
+
def all(params = {})
|
146
|
+
response = connection.get_bars(params)
|
166
147
|
|
167
|
-
|
148
|
+
data = response.body
|
168
149
|
|
169
|
-
|
170
|
-
|
171
|
-
:status => 201,
|
172
|
-
:path => '/bar',
|
173
|
-
)
|
150
|
+
self.load(data["bars"]) # store bar records in collection
|
151
|
+
self.merge_attributes(data) # store any other attributes of the response on the collection
|
174
152
|
end
|
175
|
-
end # Mock
|
176
|
-
end # Client
|
177
|
-
end # Foo
|
178
153
|
|
179
|
-
|
154
|
+
def discover(provisioned_id, options={})
|
155
|
+
params = {
|
156
|
+
"provisioned_id" => provisioned_id,
|
157
|
+
}
|
158
|
+
params.merge!("location" => options[:location]) if options.key?(:location)
|
159
|
+
|
160
|
+
connection.requests.new(connection.discover_bar(params).body["request"])
|
161
|
+
end
|
162
|
+
|
163
|
+
def get(id)
|
164
|
+
if data = connection.get_bar("id" => id).body["bar"]
|
165
|
+
new(data)
|
166
|
+
else
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
### Request
|
173
|
+
|
174
|
+
module Foo
|
175
|
+
class Client
|
176
|
+
class Real
|
177
|
+
def create_bar(options={})
|
178
|
+
request(
|
179
|
+
:body => {"bar" => options},
|
180
|
+
:method => :post,
|
181
|
+
:path => '/bar'
|
182
|
+
)
|
183
|
+
end
|
184
|
+
end # Real
|
185
|
+
|
186
|
+
class Mock
|
187
|
+
def create_bar(options={})
|
188
|
+
id = Foo.random_hex(6)
|
189
|
+
|
190
|
+
bar = {
|
191
|
+
"id" => id
|
192
|
+
}.merge(options)
|
193
|
+
|
194
|
+
self.data[:bars][id]= bar
|
195
|
+
|
196
|
+
response(
|
197
|
+
:body => {"bar" => bar},
|
198
|
+
:status => 201,
|
199
|
+
:path => '/bar',
|
200
|
+
)
|
201
|
+
end
|
202
|
+
end # Mock
|
203
|
+
end # Client
|
204
|
+
end # Foo
|
180
205
|
|
181
206
|
## Examples
|
182
207
|
|
data/lib/cistern/attributes.rb
CHANGED
@@ -57,6 +57,17 @@ module Cistern::Attributes
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def attribute(_name, options = {})
|
60
|
+
if defined? Cistern::Coverage
|
61
|
+
attribute_call = Cistern::Coverage.find_caller_before("cistern/attributes.rb")
|
62
|
+
|
63
|
+
# Only use DSL attribute calls from within a model
|
64
|
+
if attribute_call and attribute_call.label.start_with? "<class:"
|
65
|
+
options[:coverage_file] = attribute_call.absolute_path
|
66
|
+
options[:coverage_line] = attribute_call.lineno
|
67
|
+
options[:coverage_hits] = 0
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
60
71
|
name = _name.to_s.to_sym
|
61
72
|
|
62
73
|
parser = Cistern::Attributes.parsers[options[:type]] ||
|
@@ -66,6 +77,9 @@ module Cistern::Attributes
|
|
66
77
|
Cistern::Attributes.default_transform
|
67
78
|
|
68
79
|
self.send(:define_method, name) do
|
80
|
+
# record the attribute was accessed
|
81
|
+
self.class.attributes[name.to_s.to_sym][:coverage_hits] += 1 rescue nil
|
82
|
+
|
69
83
|
attributes[name.to_s.to_sym]
|
70
84
|
end
|
71
85
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Cistern::Coverage
|
2
|
+
|
3
|
+
unless Kernel.respond_to? :caller_locations
|
4
|
+
abort <<-ABORT
|
5
|
+
Cannot enable Cistern coverage reporting
|
6
|
+
|
7
|
+
Your ruby version ruby is: #{RUBY_VERSION rescue 'unknown'}
|
8
|
+
`Kernel` does not have the required method `caller_locations`
|
9
|
+
|
10
|
+
Try a newer ruby (should be > 2.0)
|
11
|
+
ABORT
|
12
|
+
end
|
13
|
+
|
14
|
+
# returns the first caller_locations entry before entries in `file`
|
15
|
+
def self.find_caller_before(file)
|
16
|
+
enum = caller_locations.each
|
17
|
+
|
18
|
+
call = nil
|
19
|
+
|
20
|
+
# seek to the first entry from within `file`
|
21
|
+
while(call = enum.next) do
|
22
|
+
break if call.path.end_with? file
|
23
|
+
end
|
24
|
+
|
25
|
+
# seek to the first entry thats not within `file`
|
26
|
+
while(call = enum.next) do
|
27
|
+
break unless call.path.end_with? file
|
28
|
+
end
|
29
|
+
|
30
|
+
# the call location that called in to `file`
|
31
|
+
call
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -32,7 +32,7 @@ module AwesomePrint::Cistern
|
|
32
32
|
# Format Cistern::Model
|
33
33
|
#------------------------------------------------------------------------------
|
34
34
|
def awesome_cistern_collection(object)
|
35
|
-
"#{object.class.name} " << awesome_hash(attributes: object.attributes, records: object.
|
35
|
+
"#{object.class.name} " << awesome_hash(attributes: object.attributes, records: object.to_a)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
data/lib/cistern/service.rb
CHANGED
@@ -37,6 +37,10 @@ class Cistern::Service
|
|
37
37
|
klass.send(:const_set, :Timeout, Class.new(Cistern::Error))
|
38
38
|
end
|
39
39
|
|
40
|
+
def collection_path(collection_path)
|
41
|
+
@collection_path = collection_path
|
42
|
+
end
|
43
|
+
|
40
44
|
def model_path(model_path)
|
41
45
|
@model_path = model_path
|
42
46
|
end
|
@@ -127,7 +131,14 @@ class Cistern::Service
|
|
127
131
|
end
|
128
132
|
end
|
129
133
|
collections.each do |collection, options|
|
130
|
-
|
134
|
+
unless options[:require] == false
|
135
|
+
if @collection_path
|
136
|
+
require File.join(@collection_path, collection.to_s)
|
137
|
+
else
|
138
|
+
require File.join(@model_path, collection.to_s)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
131
142
|
class_name = collection.to_s.split("_").map(&:capitalize).join
|
132
143
|
self.const_get(:Collections).module_eval <<-EOS, __FILE__, __LINE__
|
133
144
|
def #{collection}(attributes={})
|
data/lib/cistern/version.rb
CHANGED
data/lib/cistern/wait_for.rb
CHANGED
@@ -2,8 +2,8 @@ require 'timeout'
|
|
2
2
|
|
3
3
|
module Cistern
|
4
4
|
module WaitFor
|
5
|
-
DEFAULT_TIMEOUT =
|
6
|
-
DEFAULT_POLL_INTERVAL = 10
|
5
|
+
DEFAULT_TIMEOUT = 180 # 3 minutes
|
6
|
+
DEFAULT_POLL_INTERVAL = 10 # 10 seconds
|
7
7
|
|
8
8
|
def timeout; @timeout || DEFAULT_TIMEOUT; end
|
9
9
|
def timeout=(timeout); @timeout = timeout; end
|
data/spec/model_spec.rb
CHANGED
@@ -129,4 +129,38 @@ describe "Cistern::Model" do
|
|
129
129
|
end
|
130
130
|
end
|
131
131
|
end
|
132
|
+
|
133
|
+
context "attribute coverage info collecting", :coverage do
|
134
|
+
class CoverageSpec < Cistern::Model
|
135
|
+
identity :id
|
136
|
+
|
137
|
+
attribute :used, type: :string
|
138
|
+
attribute :unused, type: :string
|
139
|
+
end
|
140
|
+
|
141
|
+
let!(:obj) { CoverageSpec.new(used: "foo", unused: "bar") }
|
142
|
+
|
143
|
+
before(:each) do
|
144
|
+
CoverageSpec.attributes[:used][:coverage_hits] = 0
|
145
|
+
obj.used.should == "foo" # once
|
146
|
+
obj.used.should == "foo" # twice
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should store the file path where the attribute was defined" do
|
150
|
+
CoverageSpec.attributes[:used][:coverage_file].should == __FILE__
|
151
|
+
CoverageSpec.attributes[:unused][:coverage_file].should == __FILE__
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should store the line number where the attribute was defined" do
|
155
|
+
src_lines = File.read(__FILE__).lines
|
156
|
+
|
157
|
+
src_lines[CoverageSpec.attributes[:used][:coverage_line] - 1].should match(/attribute :used/)
|
158
|
+
src_lines[CoverageSpec.attributes[:unused][:coverage_line] - 1].should match(/attribute :unused/)
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should store how many times an attribute's reader is called" do
|
162
|
+
CoverageSpec.attributes[:used][:coverage_hits].should == 2
|
163
|
+
CoverageSpec.attributes[:unused][:coverage_hits].should == 0
|
164
|
+
end
|
165
|
+
end
|
132
166
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,5 +2,12 @@ require File.expand_path('../../lib/cistern', __FILE__)
|
|
2
2
|
|
3
3
|
Bundler.require(:test)
|
4
4
|
|
5
|
-
RSpec.configure do
|
5
|
+
RSpec.configure do |c|
|
6
|
+
c.treat_symbols_as_metadata_keys_with_true_values = true
|
7
|
+
|
8
|
+
if Kernel.respond_to?(:caller_locations)
|
9
|
+
require File.expand_path('../../lib/cistern/coverage', __FILE__)
|
10
|
+
else
|
11
|
+
c.filter_run_excluding(:coverage)
|
12
|
+
end
|
6
13
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cistern
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Lane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-04-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: API client framework extracted from Fog
|
14
14
|
email:
|
@@ -29,6 +29,7 @@ files:
|
|
29
29
|
- lib/cistern.rb
|
30
30
|
- lib/cistern/attributes.rb
|
31
31
|
- lib/cistern/collection.rb
|
32
|
+
- lib/cistern/coverage.rb
|
32
33
|
- lib/cistern/formatter.rb
|
33
34
|
- lib/cistern/formatter/awesome_print.rb
|
34
35
|
- lib/cistern/formatter/formatador.rb
|
@@ -74,3 +75,4 @@ test_files:
|
|
74
75
|
- spec/model_spec.rb
|
75
76
|
- spec/spec_helper.rb
|
76
77
|
- spec/wait_for_spec.rb
|
78
|
+
has_rdoc:
|