cistern 0.5.4 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c39b9ac9a702497cd0fd82279e908697d2d8e65
4
- data.tar.gz: 7b9aae264e94c4dd5c5d883a697cf038659be657
3
+ metadata.gz: 83399a872cc26926353a8186ca5a2eeb5e35694f
4
+ data.tar.gz: a63f6fc98003b05aa829e18d976718a3698ba2e7
5
5
  SHA512:
6
- metadata.gz: 8441ed13d9b55f0f1cf988ec91a47914174388bed261811fa9c4a5d65cea727c8252a65fc1af22b75187f5c044fa1a058c109ed9693efb4ef1606fbfa5039ad1
7
- data.tar.gz: 258a085c417051866346609af569a7269d173d8652fae7906bb365884c4d60fb493a9d2d5f9feaab336566a5efc7f9dcc8c2749a5644be336381c28b858932b6
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
- 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```.
14
+ #### Requests
15
15
 
16
- ```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.
16
+ Requests are enumerated using the ```request``` method and required immediately via the relative path specified via ```request_path```.
17
17
 
18
- ```ruby
18
+ class Foo::Client < Cistern::Service
19
+ request_path "my-foo/requests"
19
20
 
20
- class Foo::Client < Cistern::Service
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
- model_path "foo/models"
23
- request_path "foo/requests"
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
- requires :hmac_id, :hmac_secret
32
- recognizes :host
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
- class Real
35
- def initialize(options={})
36
- # setup connection
37
- end
38
- end
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
- class Mock
41
- def self.data
42
- @data ||= {
43
- :bars => {},
44
- }
45
- end
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
- def self.reset!
48
- @data = nil
49
- end
51
+ All declared requests can be listed via ```Cistern::Service#requests```.
50
52
 
51
- def data
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
- ### Model
57
+ Models and collections have declaration semantics similar to requests. Models and collections are enumerated via ```model``` and ```collection``` respectively.
63
58
 
64
- ```connection``` represents the associated ```Foo::Client``` instance.
59
+ class Foo::Client < Cistern::Service
60
+ model_path "my-foo/models"
65
61
 
66
- ```ruby
62
+ model :bar # require my-foo/models/bar.rb
63
+ collection :bars # require my-foo/models/bars.rb
64
+ end
67
65
 
68
- class Foo::Client::Bar < Cistern::Model
69
- identity :id
66
+ #### Initialization
70
67
 
71
- attribute :flavor
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
- def destroy
76
- params = {
77
- "id" => self.identity
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
- def save
83
- requires :keypair_id
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
- params = {
86
- "keypair" => self.keypair_id,
87
- "bar" => {
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
- merge_attributes(connection.update_bar(params).body["bar"])
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
- ### Collection
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
- ```ruby
99
+ ### Model
109
100
 
110
- class Foo::Client::Bars < Cistern::Collection
101
+ ```connection``` represents the associated ```Foo::Client``` instance.
111
102
 
112
- model Foo::Client::Bar
103
+ class Foo::Client::Bar < Cistern::Model
104
+ identity :id
113
105
 
114
- def all(params = {})
115
- response = connection.get_bars(params)
106
+ attribute :flavor
107
+ attribute :keypair_id, aliases: "keypair", squash: "id"
108
+ attribute :private_ips, type: :array
116
109
 
117
- data = response.body
110
+ def destroy
111
+ params = {
112
+ "id" => self.identity
113
+ }
114
+ self.connection.destroy_bar(params).body["request"]
115
+ end
118
116
 
119
- self.load(data["bars"]) # store bar records in collection
120
- self.merge_attributes(data) # store any other attributes of the response on the collection
121
- end
117
+ def save
118
+ requires :keypair_id
122
119
 
123
- def discover(provisioned_id, options={})
124
- params = {
125
- "provisioned_id" => provisioned_id,
126
- }
127
- params.merge!("location" => options[:location]) if options.key?(:location)
120
+ params = {
121
+ "keypair" => self.keypair_id,
122
+ "bar" => {
123
+ "flavor" => self.flavor,
124
+ },
125
+ }
128
126
 
129
- connection.requests.new(connection.discover_bar(params).body["request"])
130
- end
127
+ if new_record?
128
+ merge_attributes(connection.create_bar(params).body["bar"])
129
+ else
130
+ requires :identity
131
131
 
132
- def get(id)
133
- if data = connection.get_bar("id" => id).body["bar"]
134
- new(data)
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
- ### Request
139
+ ```model``` tells Cistern which class is contained within the collection. ```Cistern::Collection``` inherits from ```Array``` and lazy loads where applicable.
144
140
 
145
- ```ruby
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
- class Mock
160
- def create_bar(options={})
161
- id = Foo.random_hex(6)
143
+ model Foo::Client::Bar
162
144
 
163
- bar = {
164
- "id" => id
165
- }.merge(options)
145
+ def all(params = {})
146
+ response = connection.get_bars(params)
166
147
 
167
- self.data[:bars][id]= bar
148
+ data = response.body
168
149
 
169
- response(
170
- :body => {"bar" => bar},
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
 
@@ -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.records)
35
+ "#{object.class.name} " << awesome_hash(attributes: object.attributes, records: object.to_a)
36
36
  end
37
37
  end
38
38
 
@@ -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
- require File.join(@model_path, collection.to_s) unless options[:require] == false
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={})
@@ -1,3 +1,3 @@
1
1
  module Cistern
2
- VERSION = "0.5.4"
2
+ VERSION = "0.5.6"
3
3
  end
@@ -2,8 +2,8 @@ require 'timeout'
2
2
 
3
3
  module Cistern
4
4
  module WaitFor
5
- DEFAULT_TIMEOUT = 3 * 60 # 3 minutes
6
- DEFAULT_POLL_INTERVAL = 10 # seconds
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
@@ -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
@@ -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
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-03-03 00:00:00.000000000 Z
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: