cistern 1.0.1.pre6 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 14abaac482ac21a28c8397bc14d3f122f8a8f7b6
4
+ data.tar.gz: 1bd8bbc0c5f2c0a11596b5e420042d7a468c2ea6
5
+ SHA512:
6
+ metadata.gz: a30d8da5aac9ea437a76903c3183bfc0ae88ce295080e8703644eece9fa8c81656ef9df60e73d102384a7d9db9ea2e94bb96d39e30802b0e4238fc3d5647bcf0
7
+ data.tar.gz: 5bb8cf1014201e231ed9f51456369e410a3802b084f322c1f1d051b24124bdb20ba1048841a71c96b9952e89dd67e8628dd8b9533dc45481af9089ee5079d890
@@ -0,0 +1,3 @@
1
+ ---
2
+ bump:
3
+ tag: true
data/README.md CHANGED
@@ -11,68 +11,6 @@ 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
- #### Requests
15
-
16
- Requests are enumerated using the `request` method and required immediately via the relative path specified via `request_path`.
17
-
18
- ```ruby
19
- class Foo::Client < Cistern::Service
20
- request_path "my-foo/requests"
21
-
22
- request :get_bar # require my-foo/requests/get_bar.rb
23
- request :get_bars # require my-foo/requests/get_bars.rb
24
-
25
- class Real
26
- def request(url)
27
- Net::HTTP.get(url)
28
- end
29
- end
30
- end
31
- ```
32
-
33
-
34
- <!--todo move to a request section-->
35
- A request is method defined within the context of service and mode (Real or Mock). Defining requests within the service mock class is optional.
36
-
37
- ```ruby
38
- # my-foo/requests/get_bar.rb
39
- class Foo::Client
40
- class Real
41
- def get_bar(bar_id)
42
- request("http://example.org/bar/#{bar_id}")
43
- end
44
- end # Real
45
-
46
- # optional, but encouraged
47
- class Mock
48
- def get_bars
49
- # do some mock things
50
- end
51
- end # Mock
52
- end # Foo::client
53
- ```
54
-
55
- All declared requests can be listed via `Cistern::Service#requests`.
56
-
57
- ```ruby
58
- Foo::Client.requests # => [:get_bar, :get_bars]
59
- ```
60
-
61
- #### Models and Collections
62
-
63
- Models and collections have declaration semantics similar to requests. Models and collections are enumerated via `model` and `collection` respectively.
64
-
65
- ```ruby
66
- class Foo::Client < Cistern::Service
67
- model_path "my-foo/models"
68
-
69
- model :bar # require my-foo/models/bar.rb
70
- collection :bars # require my-foo/models/bars.rb
71
- end
72
- ```
73
-
74
- #### Initialization
75
-
76
14
  Service initialization parameters are enumerated by `requires` and `recognizes`. `recognizes` parameters are optional.
77
15
 
78
16
  ```ruby
@@ -90,6 +28,7 @@ Foo::Client.new(hmac_id: "1", url: "http://example.org")
90
28
  Foo::Client.new(hmac_id: "1")
91
29
  ```
92
30
 
31
+ Cistern will define for you two classes, `Mock` and `Real`.
93
32
 
94
33
  ### Mocking
95
34
 
@@ -107,114 +46,72 @@ real.is_a?(Foo::Client::Real) # true
107
46
  fake.is_a?(Foo::Client::Mock) # true
108
47
  ```
109
48
 
110
- #### Data
111
-
112
- A uniform interface for mock data is mixed into the `Mock` class by default.
49
+ ### Requests
113
50
 
114
- ```ruby
115
- Foo::Client.mock!
116
- client = Foo::Client.new # Foo::Client::Mock
117
- client.data # Cistern::Data::Hash
118
- client.data["bars"] += ["x"] # ["x"]
119
- ```
51
+ Requests are defined by subclassing `#{service}::Request`.
120
52
 
121
- Mock data is class-level by default
53
+ * `service` represents the associated `Foo::Client` instance.
122
54
 
123
55
  ```ruby
124
- Foo::Client::Mock.data["bars"] # ["x"]
125
- ```
56
+ class Foo::Client::GetBar < Foo::Client::Request
57
+ def real(params)
58
+ # make a real request
59
+ "i'm real"
60
+ end
126
61
 
127
- `reset!` dimisses the `data` object.
62
+ def mock(params)
63
+ # return a fake response
64
+ "imposter!"
65
+ end
66
+ end
128
67
 
129
- ```ruby
130
- client.data.object_id # 70199868585600
131
- client.reset!
132
- client.data["bars"] # []
133
- client.data.object_id # 70199868566840
68
+ Foo::Client.new.get_bar # "i'm real"
134
69
  ```
135
70
 
136
- `clear` removes existing keys and values but keeps the same object.
71
+ The `#service_method` function allows you to specific the name of generated method.
137
72
 
138
73
  ```ruby
139
- client.data["bars"] += ["y"] # ["y"]
140
- client.data.object_id # 70199868378300
141
- client.clear
142
- client.data["bars"] # []
143
-
144
- client.data.object_id # 70199868566840
145
- ```
146
-
147
- * `store` and `[]=` write
148
- * `fetch` and `[]` read
74
+ class Foo::Client::GetBars < Foo::Client::Request
75
+ service_method :get_all_the_bars
149
76
 
150
- You can make the service bypass Cistern's mock data structures by simply creating a `self.data` function in your service `Mock` declaration.
151
-
152
- ```ruby
153
- class Foo::Client < Cistern::Service
154
- class Mock
155
- def self.data
156
- @data ||= {}
157
- end
77
+ def real(params)
78
+ "all the bars"
158
79
  end
159
80
  end
160
- ```
161
-
162
81
 
163
- #### Requests
82
+ Foo::Client.new.respond_to?(:get_bars) # false
83
+ Foo::Client.new.get_all_the_bars # "all the bars"
164
84
 
165
- Mock requests should be defined within the contextual `Mock` module and interact with the `data` object directly.
85
+ All declared requests can be listed via `Cistern::Service#requests`.
166
86
 
167
87
  ```ruby
168
- # lib/foo/requests/create_bar.rb
169
- class Foo::Client
170
- class Mock
171
- def create_bar(options={})
172
- id = Foo.random_hex(6)
173
-
174
- bar = {
175
- "id" => id
176
- }.merge(options)
177
-
178
- self.data[:bars][id] = bar
179
-
180
- response(
181
- :body => {"bar" => bar},
182
- :status => 201,
183
- :path => '/bar',
184
- )
185
- end
186
- end # Mock
187
- end # Foo::Client
88
+ Foo::Client.requests # => [Foo::Client::GetBars, Foo::Client::GetBar]
188
89
  ```
189
90
 
190
- #### Storage
191
-
192
- Currently supported storage backends are:
91
+ ### Models
193
92
 
194
- * `:hash` : `Cistern::Data::Hash` (default)
195
- * `:redis` : `Cistern::Data::Redis`
93
+ * `service` represents the associated `Foo::Client` instance.
94
+ * `collection` represents the related collection (if applicable)
95
+ * `new_record?` checks if `identity` is present
96
+ * `requires(*requirements)` throws `ArgumentError` if an attribute matching a requirement isn't set
97
+ * `merge_attributes(attributes)` sets attributes for the current model instance
196
98
 
99
+ #### Attributes
197
100
 
198
- Backends can be switched by using `store_in`.
101
+ Attributes are designed to be a flexible way of parsing service request responses.
199
102
 
200
- ```ruby
201
- # use redis with defaults
202
- Patient::Mock.store_in(:redis)
203
- # use redis with a specific client
204
- Patient::Mock.store_in(:redis, client: Redis::Namespace.new("cistern", redis: Redis.new(host: "10.1.0.1"))
205
- # use a hash
206
- Patient::Mock.store_in(:hash)
207
- ```
103
+ `identity` is special but not required.
208
104
 
209
- ### Model
105
+ `attribute :flavor` makes `Foo::Client::Bar.new.respond_to?(:flavor)`
210
106
 
211
- * `service` represents the associated `Foo::Client` instance.
212
- * `collection` represents the related collection (if applicable)
107
+ * `:aliases` or `:alias` allows a attribute key to be different then a response key. `attribute :keypair_id, alias: "keypair"` with `merge_attributes("keypair" => 1)` sets `keypair_id` to `1`
108
+ * `:type` automatically casts the attribute do the specified type. `attribute :private_ips, type: :array` with `merge_attributes("private_ips" => 2)` sets `private_ips` to `[2]`
109
+ * `:squash` traverses nested hashes for a key. `attribute :keypair_id, aliases: "keypair", squash: "id"` with `merge_attributes("keypair" => {"id" => 3})` sets `keypair_id` to `3`
213
110
 
214
111
  Example
215
112
 
216
113
  ```ruby
217
- class Foo::Client::Bar < Cistern::Model
114
+ class Foo::Client::Bar < Foo::Client::Model
218
115
  identity :id
219
116
 
220
117
  attribute :flavor
@@ -249,40 +146,18 @@ class Foo::Client::Bar < Cistern::Model
249
146
  end
250
147
  ```
251
148
 
252
- #### Dirty
253
-
254
- Dirty attributes are tracked and cleared when `merge_attributes` is called.
255
-
256
- * `changed` returns a Hash of changed attributes mapped to there initial value and current value
257
- * `dirty_attributes` returns Hash of changed attributes with there current value. This should be used in the model `save` function.
258
-
259
-
260
- ```ruby
261
- bar = Foo::Client::Bar.new(id: 1, flavor: "x") # => <#Foo::Client::Bar>
262
-
263
- bar.dirty? # => false
264
- bar.changed # => {}
265
- bar.dirty_attributes # => {}
266
-
267
- bar.flavor = "y"
268
-
269
- bar.dirty? # => true
270
- bar.changed # => {flavor: ["x", "y"]}
271
- bar.dirty_attributes # => {flavor: "y"}
272
-
273
- bar.save
274
- bar.dirty? # => false
275
- bar.changed # => {}
276
- bar.dirty_attributes # => {}
277
- ```
278
-
279
149
  ### Collection
280
150
 
281
- `model` tells Cistern which class is contained within the collection. `Cistern::Collection` inherits from `Array` and lazy loads where applicable.
151
+ * `model` tells Cistern which class is contained within the collection.
152
+ * `service` is the associated `Foo::Client` instance
153
+ * `attribute` specifications on collections are allowed. use `merge_attributes`
154
+ * `load` consumes an Array of data and constructs matching `model` instances
282
155
 
283
156
  ```ruby
284
157
  class Foo::Client::Bars < Cistern::Collection
285
158
 
159
+ attribute :count, type: :integer
160
+
286
161
  model Foo::Client::Bar
287
162
 
288
163
  def all(params = {})
@@ -313,40 +188,103 @@ class Foo::Client::Bars < Cistern::Collection
313
188
  end
314
189
  ```
315
190
 
316
- ### Request
191
+ #### Data
192
+
193
+ A uniform interface for mock data is mixed into the `Mock` class by default.
194
+
195
+ ```ruby
196
+ Foo::Client.mock!
197
+ client = Foo::Client.new # Foo::Client::Mock
198
+ client.data # Cistern::Data::Hash
199
+ client.data["bars"] += ["x"] # ["x"]
200
+ ```
201
+
202
+ Mock data is class-level by default
203
+
204
+ ```ruby
205
+ Foo::Client::Mock.data["bars"] # ["x"]
206
+ ```
207
+
208
+ `reset!` dimisses the `data` object.
209
+
210
+ ```ruby
211
+ client.data.object_id # 70199868585600
212
+ client.reset!
213
+ client.data["bars"] # []
214
+ client.data.object_id # 70199868566840
215
+ ```
216
+
217
+ `clear` removes existing keys and values but keeps the same object.
218
+
219
+ ```ruby
220
+ client.data["bars"] += ["y"] # ["y"]
221
+ client.data.object_id # 70199868378300
222
+ client.clear
223
+ client.data["bars"] # []
224
+
225
+ client.data.object_id # 70199868566840
226
+ ```
227
+
228
+ * `store` and `[]=` write
229
+ * `fetch` and `[]` read
230
+
231
+ You can make the service bypass Cistern's mock data structures by simply creating a `self.data` function in your service `Mock` declaration.
232
+
233
+ ```ruby
234
+ class Foo::Client < Cistern::Service
235
+ class Mock
236
+ def self.data
237
+ @data ||= {}
238
+ end
239
+ end
240
+ end
241
+ ```
242
+
243
+ #### Storage
244
+
245
+ Currently supported storage backends are:
246
+
247
+ * `:hash` : `Cistern::Data::Hash` (default)
248
+ * `:redis` : `Cistern::Data::Redis`
249
+
250
+
251
+ Backends can be switched by using `store_in`.
252
+
253
+ ```ruby
254
+ # use redis with defaults
255
+ Patient::Mock.store_in(:redis)
256
+ # use redis with a specific client
257
+ Patient::Mock.store_in(:redis, client: Redis::Namespace.new("cistern", redis: Redis.new(host: "10.1.0.1"))
258
+ # use a hash
259
+ Patient::Mock.store_in(:hash)
260
+ ```
261
+
262
+
263
+ #### Dirty
264
+
265
+ Dirty attributes are tracked and cleared when `merge_attributes` is called.
266
+
267
+ * `changed` returns a Hash of changed attributes mapped to there initial value and current value
268
+ * `dirty_attributes` returns Hash of changed attributes with there current value. This should be used in the model `save` function.
269
+
317
270
 
318
271
  ```ruby
319
- module Foo
320
- class Client
321
- class Real
322
- def create_bar(options={})
323
- request(
324
- :body => {"bar" => options},
325
- :method => :post,
326
- :path => '/bar'
327
- )
328
- end
329
- end # Real
330
-
331
- class Mock
332
- def create_bar(options={})
333
- id = Foo.random_hex(6)
334
-
335
- bar = {
336
- "id" => id
337
- }.merge(options)
338
-
339
- self.data[:bars][id]= bar
340
-
341
- response(
342
- :body => {"bar" => bar},
343
- :status => 201,
344
- :path => '/bar',
345
- )
346
- end
347
- end # Mock
348
- end # Client
349
- end # Foo
272
+ bar = Foo::Client::Bar.new(id: 1, flavor: "x") # => <#Foo::Client::Bar>
273
+
274
+ bar.dirty? # => false
275
+ bar.changed # => {}
276
+ bar.dirty_attributes # => {}
277
+
278
+ bar.flavor = "y"
279
+
280
+ bar.dirty? # => true
281
+ bar.changed # => {flavor: ["x", "y"]}
282
+ bar.dirty_attributes # => {flavor: "y"}
283
+
284
+ bar.save
285
+ bar.dirty? # => false
286
+ bar.changed # => {}
287
+ bar.dirty_attributes # => {}
350
288
  ```
351
289
 
352
290
  ## Examples
@@ -26,10 +26,12 @@ module Cistern::Attributes
26
26
 
27
27
  travel.call(v, squash.dup)
28
28
  elsif v.is_a?(::Hash)
29
- if v.key?(squash.to_s.to_sym)
30
- v[squash.to_s.to_sym]
31
- elsif v.has_key?(squash.to_s)
32
- v[squash.to_s]
29
+ squash_s = squash.to_s
30
+
31
+ if v.key?(key = squash_s.to_sym)
32
+ v[key]
33
+ elsif v.has_key?(squash_s)
34
+ v[squash_s]
33
35
  else
34
36
  v
35
37
  end
@@ -92,7 +94,7 @@ module Cistern::Attributes
92
94
  self.attributes[name] = options
93
95
  end
94
96
 
95
- options[:aliases] = Array(options[:aliases]).map { |a| a.to_s.to_sym }
97
+ options[:aliases] = Array(options[:aliases] || options[:alias]).map { |a| a.to_s.to_sym }
96
98
 
97
99
  options[:aliases].each do |new_alias|
98
100
  aliases[new_alias] << name.to_s.to_sym
@@ -173,26 +175,39 @@ module Cistern::Attributes
173
175
  end
174
176
 
175
177
  def merge_attributes(new_attributes = {})
178
+ protected_methods = (Cistern::Model.instance_methods - [:service, :identity, :collection])
179
+ ignored_attributes = self.class.ignored_attributes
180
+ class_attributes = self.class.attributes
181
+ class_aliases = self.class.aliases
182
+
176
183
  new_attributes.each do |_key, value|
177
- key = _key.to_s.to_sym
184
+ string_key = _key.kind_of?(String) ? _key : _key.to_s
185
+ symbol_key = case _key
186
+ when String
187
+ _key.to_sym
188
+ when Symbol
189
+ _key
190
+ else
191
+ string_key.to_sym
192
+ end
193
+
178
194
  # find nested paths
179
- value.is_a?(::Hash) && self.class.attributes.each do |name, options|
180
- if (options[:squash] || []).first == key.to_s
181
- send("#{name}=", {key => value})
195
+ value.is_a?(::Hash) && class_attributes.each do |name, options|
196
+ if options[:squash] && options[:squash].first == string_key
197
+ send("#{name}=", {symbol_key => value})
182
198
  end
183
199
  end
184
200
 
185
- unless self.class.ignored_attributes.include?(key)
186
- if self.class.aliases.has_key?(key)
187
- self.class.aliases[key].each do |aliased_key|
201
+ unless ignored_attributes.include?(symbol_key)
202
+ if class_aliases.has_key?(symbol_key)
203
+ class_aliases[symbol_key].each do |aliased_key|
188
204
  send("#{aliased_key}=", value)
189
205
  end
190
206
  end
191
207
 
192
- protected_methods = Cistern::Model.instance_methods - [:service, :identity, :collection]
193
-
194
- if !protected_methods.include?(key) && self.respond_to?("#{key}=", true)
195
- send("#{key}=", value)
208
+ assignment_method = "#{string_key}="
209
+ if !protected_methods.include?(symbol_key) && self.respond_to?(assignment_method, true)
210
+ send(assignment_method, value)
196
211
  end
197
212
  end
198
213
  end
@@ -6,11 +6,9 @@ module Cistern::Collection
6
6
  :keep_if, :pop, :shift, :delete_at, :compact
7
7
  ].to_set # :nodoc:
8
8
 
9
- def self.service_collection(service, klass)
10
- plural_name = service.collection_method(klass)
11
-
9
+ def self.service_collection(service, klass, name)
12
10
  service.const_get(:Collections).module_eval <<-EOS, __FILE__, __LINE__
13
- def #{plural_name}(attributes={})
11
+ def #{name}(attributes={})
14
12
  #{klass.name}.new(attributes.merge(service: self))
15
13
  end
16
14
  EOS
@@ -20,7 +18,11 @@ module Cistern::Collection
20
18
 
21
19
  module ClassMethods
22
20
  def model(new_model=nil)
23
- @model ||= new_model
21
+ @_model ||= new_model
22
+ end
23
+
24
+ def service_method(name=nil)
25
+ @_service_method ||= name
24
26
  end
25
27
  end
26
28
 
@@ -69,7 +71,7 @@ module Cistern::Collection
69
71
  end
70
72
 
71
73
  def model
72
- self.class.instance_variable_get('@model')
74
+ self.class.model
73
75
  end
74
76
 
75
77
  def new(attributes = {})
@@ -4,6 +4,21 @@ module Cistern::Model
4
4
  def self.included(klass)
5
5
  klass.send(:extend, Cistern::Attributes::ClassMethods)
6
6
  klass.send(:include, Cistern::Attributes::InstanceMethods)
7
+ klass.send(:extend, Cistern::Model::ClassMethods)
8
+ end
9
+
10
+ def self.service_model(service, klass, name)
11
+ service.const_get(:Collections).module_eval <<-EOS, __FILE__, __LINE__
12
+ def #{name}(attributes={})
13
+ #{klass.name}.new(attributes.merge(service: self))
14
+ end
15
+ EOS
16
+ end
17
+
18
+ module ClassMethods
19
+ def service_method(name=nil)
20
+ @_service_method ||= name
21
+ end
7
22
  end
8
23
 
9
24
  attr_accessor :collection, :service
@@ -1,20 +1,16 @@
1
1
  module Cistern::Request
2
- def self.service_request(service, klass)
3
- request = klass.request_name || Cistern::String.camelize(Cistern::String.demodulize(klass.name))
4
-
2
+ def self.service_request(service, klass, name)
5
3
  service::Mock.module_eval <<-EOS, __FILE__, __LINE__
6
- def #{request}(*args)
4
+ def #{name}(*args)
7
5
  #{klass}.new(self)._mock(*args)
8
6
  end
9
7
  EOS
10
8
 
11
9
  service::Real.module_eval <<-EOS, __FILE__, __LINE__
12
- def #{request}(*args)
10
+ def #{name}(*args)
13
11
  #{klass}.new(self)._real(*args)
14
12
  end
15
13
  EOS
16
-
17
- request
18
14
  end
19
15
 
20
16
  attr_reader :service
@@ -24,7 +20,8 @@ module Cistern::Request
24
20
  end
25
21
 
26
22
  module ClassMethods
27
- def request_name
23
+ def service_method(name=nil)
24
+ @_service_method ||= name
28
25
  end
29
26
  end
30
27
  end
@@ -9,10 +9,6 @@ class Cistern::Service
9
9
  service.collections
10
10
  end
11
11
 
12
- def mocked_requests
13
- service.mocked_requests
14
- end
15
-
16
12
  def requests
17
13
  service.requests
18
14
  end
@@ -46,6 +42,10 @@ class Cistern::Service
46
42
  class Model
47
43
  include Cistern::Model
48
44
 
45
+ def self.inherited(klass)
46
+ service.models << klass
47
+ end
48
+
49
49
  def self.service
50
50
  #{klass.name}
51
51
  end
@@ -59,7 +59,7 @@ class Cistern::Service
59
59
  klass.send(:extend, Cistern::Collection::ClassMethods)
60
60
  klass.send(:include, Cistern::Attributes::InstanceMethods)
61
61
 
62
- Cistern::Collection.service_collection(service, klass)
62
+ service.collections << klass
63
63
  end
64
64
 
65
65
  def self.service
@@ -73,9 +73,7 @@ class Cistern::Service
73
73
  def self.inherited(klass)
74
74
  klass.extend(Cistern::Request::ClassMethods)
75
75
 
76
- name = Cistern::Request.service_request(service, klass)
77
-
78
- service.requests[name.to_sym] = klass
76
+ service.requests << klass
79
77
  end
80
78
 
81
79
  def self.service
@@ -109,26 +107,20 @@ class Cistern::Service
109
107
  @collections ||= []
110
108
  end
111
109
 
112
- def collection_method(klass)
113
- relative_demodulized = klass.name.gsub("#{self.name}::", "").gsub("::", "")
114
-
115
- Cistern::String.underscore(relative_demodulized)
116
- end
117
-
118
110
  def models
119
- @models ||= []
111
+ @_models ||= []
120
112
  end
121
113
 
122
114
  def recognized_arguments
123
- @recognized_arguments ||= []
115
+ @_recognized_arguments ||= []
124
116
  end
125
117
 
126
118
  def required_arguments
127
- @required_arguments ||= []
119
+ @_required_arguments ||= []
128
120
  end
129
121
 
130
122
  def requests
131
- @requests ||= {}
123
+ @_requests ||= []
132
124
  end
133
125
 
134
126
  def requires(*args)
@@ -139,10 +131,6 @@ class Cistern::Service
139
131
  self.recognized_arguments.concat(args)
140
132
  end
141
133
 
142
- def mocked_requests
143
- @mocked_requests ||= {}
144
- end
145
-
146
134
  def validate_options(options={})
147
135
  required_options = Cistern::Hash.slice(options, *required_arguments)
148
136
 
@@ -159,7 +147,35 @@ class Cistern::Service
159
147
  end
160
148
  end
161
149
 
150
+ def setup
151
+ return true if @_setup
152
+
153
+ requests.each do |klass|
154
+ name = klass.service_method ||
155
+ Cistern::String.camelize(Cistern::String.demodulize(klass.name))
156
+
157
+ Cistern::Request.service_request(self, klass, name)
158
+ end
159
+
160
+ collections.each do |klass|
161
+ name = klass.service_method ||
162
+ Cistern::String.underscore(klass.name.gsub("#{self.name}::", "").gsub("::", ""))
163
+
164
+ Cistern::Collection.service_collection(self, klass, name)
165
+ end
166
+
167
+ models.each do |klass|
168
+ name = klass.service_method ||
169
+ Cistern::String.underscore(klass.name.gsub("#{self.name}::", "").gsub("::", ""))
170
+
171
+ Cistern::Model.service_model(self, klass, name)
172
+ end
173
+
174
+ @_setup = true
175
+ end
176
+
162
177
  def new(options={})
178
+ setup
163
179
  validate_options(options)
164
180
 
165
181
  self.const_get(self.mocking? ? :Mock : :Real).new(options)
@@ -1,3 +1,3 @@
1
1
  module Cistern
2
- VERSION = "1.0.1.pre6"
2
+ VERSION = "2.0.1"
3
3
  end
@@ -1,51 +1,67 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "Cistern::Collection" do
4
- class SampleCollectionModel < Sample::Model
4
+ class SampleService < Cistern::Service
5
+ end
6
+
7
+ class Drug < SampleService::Model
5
8
  identity :id
6
9
  attribute :name
7
10
  end
8
11
 
9
- class SampleCollection < Sample::Collection
10
- model SampleCollectionModel
12
+ class Drugs < SampleService::Collection
13
+ model Drug
11
14
 
12
15
  def all
13
16
  self.load([{id: 1}, {id: 3, name: "tom"}, {id: 2}])
14
17
  end
15
18
  end
16
19
 
20
+ class Tacs < SampleService::Collection
21
+ service_method :toes
22
+ end
23
+
24
+ it "should generate a default collection method" do
25
+ expect(SampleService.new.drugs).not_to be_empty
26
+ end
27
+
28
+ it "should allow for a specific collection name" do
29
+ expect(SampleService.new).to respond_to(:toes)
30
+ expect(SampleService.new).not_to respond_to(:tacs)
31
+ end
32
+
17
33
  it "should give to_s" do
18
- collection = SampleCollection.new
34
+ collection = Drugs.new
19
35
  expect(collection.to_s).not_to eq "[]"
20
36
  expect(collection.to_s.gsub(/:[^>]*/,'')).to eq(collection.all.to_s.gsub(/:[^>]*/,''))
21
37
  end
22
38
 
23
39
  it "should give size and count" do
24
- expect(SampleCollection.new.size).to eq(3)
25
- expect(SampleCollection.new.count).to eq(3)
40
+ expect(Drugs.new.size).to eq(3)
41
+ expect(Drugs.new.count).to eq(3)
26
42
  end
27
43
 
28
44
  it "should give first" do
29
- expect(SampleCollection.new.first).to eq(SampleCollectionModel.new(id: 1))
45
+ expect(Drugs.new.first).to eq(Drug.new(id: 1))
30
46
  end
31
47
 
32
48
  it "should give last" do
33
- expect(SampleCollection.new.last).to eq(SampleCollectionModel.new(id: 2))
49
+ expect(Drugs.new.last).to eq(Drug.new(id: 2))
34
50
  end
35
51
 
36
52
  it "should reject" do
37
- expect(SampleCollection.new.reject{|m| m.id == 2}).to eq([SampleCollectionModel.new(id: 1), SampleCollectionModel.new(id: 3)])
53
+ expect(Drugs.new.reject{|m| m.id == 2}).to eq([Drug.new(id: 1), Drug.new(id: 3)])
38
54
  end
39
55
 
40
56
  it "should select" do
41
- expect(SampleCollection.new.select{|m| m.id == 2}).to eq([SampleCollectionModel.new(id: 2)])
57
+ expect(Drugs.new.select{|m| m.id == 2}).to eq([Drug.new(id: 2)])
42
58
  end
43
59
 
44
60
  it "should slice" do
45
- expect(SampleCollection.new.slice(0,2)).to eq([SampleCollectionModel.new(id: 1), SampleCollectionModel.new(id: 3, name: "tom")])
61
+ expect(Drugs.new.slice(0,2)).to eq([Drug.new(id: 1), Drug.new(id: 3, name: "tom")])
46
62
  end
47
63
 
48
64
  it "should ==" do
49
- SampleCollection.new.all == SampleCollection.new.all
65
+ Drugs.new.all == Drugs.new.all
50
66
  end
51
67
  end
@@ -15,6 +15,20 @@ describe "Cistern::Hash" do
15
15
  end
16
16
  end
17
17
 
18
+ describe "#except" do
19
+ let(:input) do
20
+ { one: "one", two: "two", three: "three" }
21
+ end
22
+
23
+ it "returns a new hash without the specified keys" do
24
+ expect(Cistern::Hash.except(input, :one, :two)).to eq({three: "three"})
25
+ end
26
+
27
+ it "skips keys that aren't in the original hash" do
28
+ expect(Cistern::Hash.except(input, :four)).to eq(input)
29
+ end
30
+ end
31
+
18
32
  describe "#stringify_keys" do
19
33
  let(:input) do
20
34
  { one: "one", two: "two" }
@@ -20,6 +20,30 @@ describe "Cistern::Model" do
20
20
  end
21
21
  end
22
22
 
23
+ it "should set singular resource service method" do
24
+ class ModelService < Cistern::Service
25
+ end
26
+
27
+ class ModelService::Jimbob < ModelService::Model
28
+ end
29
+
30
+ expect(ModelService.new).to respond_to(:jimbob)
31
+ expect(ModelService.new.jimbob).to be_a(ModelService::Jimbob)
32
+ end
33
+
34
+ it "should set specific singular resource service method" do
35
+ class SpecificModelService < Cistern::Service
36
+ end
37
+
38
+ class SpecificModelService::Jimbob < SpecificModelService::Model
39
+ service_method :john_boy
40
+ end
41
+
42
+ expect(SpecificModelService.new).not_to respond_to(:jimbob)
43
+ expect(SpecificModelService.new).to respond_to(:john_boy)
44
+ expect(SpecificModelService.new.john_boy).to be_a(SpecificModelService::Jimbob)
45
+ end
46
+
23
47
  it "should duplicate a model" do
24
48
  class DupSpec < Sample::Model
25
49
  identity :id
@@ -15,8 +15,8 @@ describe "Cistern::Request" do
15
15
 
16
16
  # @todo Sample::Service.request
17
17
  class ListSamples < SampleService::Request
18
- # @todo name
19
- # name :list_all_samples
18
+ service_method :list_all_samples
19
+
20
20
  def real(*args)
21
21
  service.service_args + args + ["real"]
22
22
  end
@@ -27,11 +27,11 @@ describe "Cistern::Request" do
27
27
  end
28
28
 
29
29
  it "should execute a new-style request" do
30
- expect(SampleService.new.list_samples("sample1")).to eq([{}, "sample1", "real"])
31
- expect(SampleService::Real.new.list_samples("sample2")).to eq(["sample2", "real"])
32
- expect(SampleService::Mock.new.list_samples("sample3")).to eq(["sample3", "mock"])
30
+ expect(SampleService.new.list_all_samples("sample1")).to eq([{}, "sample1", "real"])
31
+ expect(SampleService::Real.new.list_all_samples("sample2")).to eq(["sample2", "real"])
32
+ expect(SampleService::Mock.new.list_all_samples("sample3")).to eq(["sample3", "mock"])
33
33
 
34
34
  # service access
35
- expect(SampleService.new(:key => "value").list_samples("stat")).to eq([{:key => "value"}, "stat", "real"])
35
+ expect(SampleService.new(:key => "value").list_all_samples("stat")).to eq([{:key => "value"}, "stat", "real"])
36
36
  end
37
37
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cistern
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1.pre6
5
- prerelease: 6
4
+ version: 2.0.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Josh Lane
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2015-02-28 00:00:00.000000000 Z
11
+ date: 2015-03-05 00:00:00.000000000 Z
13
12
  dependencies: []
14
13
  description: API client framework extracted from Fog
15
14
  email:
@@ -18,8 +17,9 @@ executables: []
18
17
  extensions: []
19
18
  extra_rdoc_files: []
20
19
  files:
21
- - .gitignore
22
- - .travis.yml
20
+ - ".gemrelease"
21
+ - ".gitignore"
22
+ - ".travis.yml"
23
23
  - Gemfile
24
24
  - Guardfile
25
25
  - LICENSE.txt
@@ -62,27 +62,26 @@ files:
62
62
  homepage: http://joshualane.com/cistern
63
63
  licenses:
64
64
  - MIT
65
+ metadata: {}
65
66
  post_install_message:
66
67
  rdoc_options: []
67
68
  require_paths:
68
69
  - lib
69
70
  required_ruby_version: !ruby/object:Gem::Requirement
70
- none: false
71
71
  requirements:
72
- - - ! '>='
72
+ - - ">="
73
73
  - !ruby/object:Gem::Version
74
74
  version: '0'
75
75
  required_rubygems_version: !ruby/object:Gem::Requirement
76
- none: false
77
76
  requirements:
78
- - - ! '>'
77
+ - - ">="
79
78
  - !ruby/object:Gem::Version
80
- version: 1.3.1
79
+ version: '0'
81
80
  requirements: []
82
81
  rubyforge_project:
83
- rubygems_version: 1.8.23.2
82
+ rubygems_version: 2.2.2
84
83
  signing_key:
85
- specification_version: 3
84
+ specification_version: 4
86
85
  summary: API client framework
87
86
  test_files:
88
87
  - spec/collection_spec.rb
@@ -97,3 +96,4 @@ test_files:
97
96
  - spec/spec_helper.rb
98
97
  - spec/support/service.rb
99
98
  - spec/wait_for_spec.rb
99
+ has_rdoc: