cistern 2.2.7 → 2.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db9f936d47b541eb4361e967bffc5d80b4ec5b92
4
- data.tar.gz: 2d07fb4e39fcdc9fad56a97f526331454a972deb
3
+ metadata.gz: a4ab124cf563b30d3d4a15c1b8b1ada69a30b434
4
+ data.tar.gz: e52206d15a0da143a293cfacd1ed105f13dcda2b
5
5
  SHA512:
6
- metadata.gz: b38503613ce1b339b64dc5ea3168f0f327a4d93669098d2690d4050d2f60088416255993148e7d3d089d823535ae9f0b10ebdfa97fb638603ad59f98d98a8d19
7
- data.tar.gz: 62472c37f3d596489c2e1bcae14b0b6934d8363459f8af0ae607de1272cb5ccbbe7eec03e9fb963cb8990fbd1dffa27da1602eb500b0d457783ef255e521ab35
6
+ metadata.gz: 189206346dc0a43b654527bf72d31fba3accb7327b0355ede31a713e3b94a27a05d92e153e84489cd79afb78d99d5d0060b1f4dac44754f5daad58956e42b0c8
7
+ data.tar.gz: 3440beaa800a967faadca537eee39661294ff10ba681a1d7805733e16714627990d00d4bee942b60f58870873b451bc70577cc1fd7827bcac2098a77f07113f9
data/.travis.yml CHANGED
@@ -8,9 +8,7 @@ before_install:
8
8
  - gem install bundler -v "~> 1.10"
9
9
  script: bundle exec rake --trace
10
10
  notifications:
11
- email:
12
- on_success: never
13
- on_failure: change
11
+ email: false
14
12
  sudo: false
15
13
  services:
16
14
  - redis-server
data/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Change Log
2
2
 
3
+ ## [Unreleased](https://github.com/lanej/cistern/tree/HEAD)
4
+
5
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.2.7...HEAD)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - 'requires' function should return a hash of matching requirements [\#45](https://github.com/lanej/cistern/issues/45)
10
+
11
+ **Closed issues:**
12
+
13
+ - rename `service` to `cistern` [\#50](https://github.com/lanej/cistern/issues/50)
14
+
15
+ **Merged pull requests:**
16
+
17
+ - add return values for \#requires and \#requires\_one [\#55](https://github.com/lanej/cistern/pull/55) ([lanej](https://github.com/lanej))
18
+ - officially deprecate class interface [\#54](https://github.com/lanej/cistern/pull/54) ([lanej](https://github.com/lanej))
19
+ - use \#stage\_attributes to make \#dirty\_attributes available on \#update [\#53](https://github.com/lanej/cistern/pull/53) ([lanej](https://github.com/lanej))
20
+ - deprecate \#service, use \#cistern [\#52](https://github.com/lanej/cistern/pull/52) ([lanej](https://github.com/lanej))
21
+
22
+ ## [v2.2.7](https://github.com/lanej/cistern/tree/v2.2.7) (2016-05-13)
23
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.2.6...v2.2.7)
24
+
25
+ **Merged pull requests:**
26
+
27
+ - service is not required to determine \#missing\_attributes [\#51](https://github.com/lanej/cistern/pull/51) ([lanej](https://github.com/lanej))
28
+
29
+ ## [v2.2.6](https://github.com/lanej/cistern/tree/v2.2.6) (2016-02-28)
30
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.2.5...v2.2.6)
31
+
32
+ ## [v2.2.5](https://github.com/lanej/cistern/tree/v2.2.5) (2016-01-14)
33
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.2.4...v2.2.5)
34
+
35
+ ## [v2.2.4](https://github.com/lanej/cistern/tree/v2.2.4) (2015-11-27)
36
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.2.3...v2.2.4)
37
+
38
+ **Closed issues:**
39
+
40
+ - Optional coverage feature creates too many NoMethodErrors [\#49](https://github.com/lanej/cistern/issues/49)
41
+
42
+ ## [v2.2.3](https://github.com/lanej/cistern/tree/v2.2.3) (2015-10-27)
43
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.2.2...v2.2.3)
44
+
45
+ ## [v2.2.2](https://github.com/lanej/cistern/tree/v2.2.2) (2015-10-27)
46
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.2.1...v2.2.2)
47
+
48
+ ## [v2.2.1](https://github.com/lanej/cistern/tree/v2.2.1) (2015-10-02)
49
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.2.0...v2.2.1)
50
+
51
+ ## [v2.2.0](https://github.com/lanej/cistern/tree/v2.2.0) (2015-10-02)
52
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.1.0...v2.2.0)
53
+
54
+ ## [v2.1.0](https://github.com/lanej/cistern/tree/v2.1.0) (2015-09-29)
55
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.0.5...v2.1.0)
56
+
57
+ ## [v2.0.5](https://github.com/lanej/cistern/tree/v2.0.5) (2015-09-21)
58
+ [Full Changelog](https://github.com/lanej/cistern/compare/v2.0.4...v2.0.5)
59
+
3
60
  ## [v2.0.4](https://github.com/lanej/cistern/tree/v2.0.4) (2015-09-10)
4
61
  [Full Changelog](https://github.com/lanej/cistern/compare/v0.12.2...v2.0.4)
5
62
 
data/README.md CHANGED
@@ -11,7 +11,7 @@ Cistern helps you consistently build your API clients and faciliates building mo
11
11
 
12
12
  ### Custom Architecture
13
13
 
14
- By default a service's `Request`, `Collection`, and `Model` are all classes. In Cistern ~> 3.0, the default will be modules.
14
+ By default a service's `Request`, `Collection`, and `Model` are all classes. In cistern `~> 3.0`, the default will be modules.
15
15
 
16
16
  You can modify your client's architecture to be forwards compatible by using `Cistern::Client.with`
17
17
 
@@ -58,7 +58,7 @@ while living on a `Prayer`
58
58
  ```ruby
59
59
  class Foo::GetBar < Foo::Prayer
60
60
  def real
61
- service.request.get("/wing")
61
+ cistern.request.get("/wing")
62
62
  end
63
63
  end
64
64
  ```
@@ -109,7 +109,7 @@ fake.is_a?(Foo::Client::Mock) # true
109
109
 
110
110
  Requests are defined by subclassing `#{service}::Request`.
111
111
 
112
- * `service` represents the associated `Foo::Client` instance.
112
+ * `cistern` represents the associated `Foo::Client` instance.
113
113
 
114
114
  ```ruby
115
115
  class Foo::Client::GetBar < Foo::Client::Request
@@ -127,11 +127,11 @@ end
127
127
  Foo::Client.new.get_bar # "i'm real"
128
128
  ```
129
129
 
130
- The `#service_method` function allows you to specify the name of the generated method.
130
+ The `#cistern_method` function allows you to specify the name of the generated method.
131
131
 
132
132
  ```ruby
133
133
  class Foo::Client::GetBars < Foo::Client::Request
134
- service_method :get_all_the_bars
134
+ cistern_method :get_all_the_bars
135
135
 
136
136
  def real(params)
137
137
  "all the bars"
@@ -150,7 +150,7 @@ Foo::Client.requests # => [Foo::Client::GetBars, Foo::Client::GetBar]
150
150
 
151
151
  ### Models
152
152
 
153
- * `service` represents the associated `Foo::Client` instance.
153
+ * `cistern` represents the associated `Foo::Client` instance.
154
154
  * `collection` represents the related collection (if applicable)
155
155
  * `new_record?` checks if `identity` is present
156
156
  * `requires(*requirements)` throws `ArgumentError` if an attribute matching a requirement isn't set
@@ -182,7 +182,7 @@ class Foo::Client::Bar < Foo::Client::Model
182
182
  params = {
183
183
  "id" => self.identity
184
184
  }
185
- self.service.destroy_bar(params).body["request"]
185
+ self.cistern.destroy_bar(params).body["request"]
186
186
  end
187
187
 
188
188
  def save
@@ -196,11 +196,11 @@ class Foo::Client::Bar < Foo::Client::Model
196
196
  }
197
197
 
198
198
  if new_record?
199
- merge_attributes(service.create_bar(params).body["bar"])
199
+ merge_attributes(cistern.create_bar(params).body["bar"])
200
200
  else
201
201
  requires :identity
202
202
 
203
- merge_attributes(service.update_bar(params).body["bar"])
203
+ merge_attributes(cistern.update_bar(params).body["bar"])
204
204
  end
205
205
  end
206
206
  end
@@ -209,7 +209,7 @@ end
209
209
  ### Collection
210
210
 
211
211
  * `model` tells Cistern which class is contained within the collection.
212
- * `service` is the associated `Foo::Client` instance
212
+ * `cistern` is the associated `Foo::Client` instance
213
213
  * `attribute` specifications on collections are allowed. use `merge_attributes`
214
214
  * `load` consumes an Array of data and constructs matching `model` instances
215
215
 
@@ -221,7 +221,7 @@ class Foo::Client::Bars < Foo::Client::Collection
221
221
  model Foo::Client::Bar
222
222
 
223
223
  def all(params = {})
224
- response = service.get_bars(params)
224
+ response = cistern.get_bars(params)
225
225
 
226
226
  data = response.body
227
227
 
@@ -235,11 +235,11 @@ class Foo::Client::Bars < Foo::Client::Collection
235
235
  }
236
236
  params.merge!("location" => options[:location]) if options.key?(:location)
237
237
 
238
- service.requests.new(service.discover_bar(params).body["request"])
238
+ cistern.requests.new(cistern.discover_bar(params).body["request"])
239
239
  end
240
240
 
241
241
  def get(id)
242
- if data = service.get_bar("id" => id).body["bar"]
242
+ if data = cistern.get_bar("id" => id).body["bar"]
243
243
  new(data)
244
244
  else
245
245
  nil
@@ -191,69 +191,63 @@ module Cistern::Attributes
191
191
  end
192
192
  end
193
193
 
194
+ # Update model's attributes. New attributes take precedence over existing attributes.
195
+ #
196
+ # This is bst called within a {Cistern::Model#save}, when {#new_attributes} represents a recently presented remote
197
+ # resource. {#dirty_attributes} is cleared after merging.
198
+ #
199
+ # @param new_attributes [Hash] attributes to merge with current attributes
194
200
  def merge_attributes(new_attributes = {})
195
- protected_methods = (Cistern::Model.instance_methods - PROTECTED_METHODS)
196
- ignored_attributes = self.class.ignored_attributes
197
- class_attributes = self.class.attributes
198
- class_aliases = self.class.aliases
199
-
200
- new_attributes.each do |_key, value|
201
- string_key = _key.is_a?(String) ? _key : _key.to_s
202
- symbol_key = case _key
203
- when String
204
- _key.to_sym
205
- when Symbol
206
- _key
207
- else
208
- string_key.to_sym
209
- end
210
-
211
- # find nested paths
212
- value.is_a?(::Hash) && class_attributes.each do |name, options|
213
- if options[:squash] && options[:squash].first == string_key
214
- send("#{name}=", symbol_key => value)
215
- end
216
- end
217
-
218
- next if ignored_attributes.include?(symbol_key)
219
-
220
- if class_aliases.key?(symbol_key)
221
- class_aliases[symbol_key].each do |aliased_key|
222
- send("#{aliased_key}=", value)
223
- end
224
- end
225
-
226
- assignment_method = "#{string_key}="
227
-
228
- if !protected_methods.include?(symbol_key) && self.respond_to?(assignment_method, true)
229
- send(assignment_method, value)
230
- end
231
- end
201
+ _merge_attributes(new_attributes)
232
202
 
233
203
  changed.clear
234
204
 
235
205
  self
236
206
  end
237
207
 
208
+ # Update model's attributes. New attributes take precedence over existing attributes.
209
+ #
210
+ # This is best called within a {Cistern::Model#update}, when {#new_attributes} represents attributes to be
211
+ # presented to a remote service. {#dirty_attributes} will contain the valid portion of {#new_attributes}
212
+ #
213
+ # @param new_attributes [Hash] attributes to merge with current attributes
214
+ def stage_attributes(new_attributes = {})
215
+ _merge_attributes(new_attributes)
216
+ self
217
+ end
218
+
238
219
  def new_record?
239
220
  identity.nil?
240
221
  end
241
222
 
242
- # check that the attributes specified in args exist and is not nil
223
+ # Require specification of certain attributes
224
+ #
225
+ # @raise [ArgumentError] if any requested attribute does not have a value
226
+ # @return [Hash] of matching attributes
243
227
  def requires(*args)
244
- missing = missing_attributes(args)
228
+ missing, required = missing_attributes(args)
229
+
245
230
  if missing.length == 1
246
- fail(ArgumentError, "#{missing.first} is required for this operation")
231
+ fail(ArgumentError, "#{missing.keys.first} is required for this operation")
247
232
  elsif missing.any?
248
- fail(ArgumentError, "#{missing[0...-1].join(', ')} and #{missing[-1]} are required for this operation")
233
+ fail(ArgumentError, "#{missing.keys[0...-1].join(', ')} and #{missing.keys[-1]} are required for this operation")
249
234
  end
235
+
236
+ required
250
237
  end
251
238
 
239
+ # Require specification of one or more attributes.
240
+ #
241
+ # @raise [ArgumentError] if no requested attributes have values
242
+ # @return [Hash] of matching attributes
252
243
  def requires_one(*args)
253
- missing = missing_attributes(args)
244
+ missing, required = missing_attributes(args)
245
+
254
246
  if missing.length == args.length
255
- fail(ArgumentError, "#{missing[0...-1].join(', ')} or #{missing[-1]} are required for this operation")
247
+ fail(ArgumentError, "#{missing.keys[0...-1].join(', ')} or #{missing.keys[-1]} are required for this operation")
256
248
  end
249
+
250
+ required
257
251
  end
258
252
 
259
253
  def dirty?
@@ -268,10 +262,12 @@ module Cistern::Attributes
268
262
  @changes ||= {}
269
263
  end
270
264
 
271
- protected
265
+ private
272
266
 
273
- def missing_attributes(args)
274
- args.select { |arg| send("#{arg}").nil? }
267
+ def missing_attributes(keys)
268
+ keys.reduce({}) { |a,e| a.merge(e => send("#{e}")) }
269
+ .partition { |_,v| v.nil? }
270
+ .map { |s| Hash[s] }
275
271
  end
276
272
 
277
273
  def changed!(attribute, from, to)
@@ -281,5 +277,45 @@ module Cistern::Attributes
281
277
  [from, to]
282
278
  end
283
279
  end
280
+
281
+ def _merge_attributes(new_attributes)
282
+ protected_methods = (Cistern::Model.instance_methods - PROTECTED_METHODS)
283
+ ignored_attributes = self.class.ignored_attributes
284
+ class_attributes = self.class.attributes
285
+ class_aliases = self.class.aliases
286
+
287
+ new_attributes.each do |_key, value|
288
+ string_key = _key.is_a?(String) ? _key : _key.to_s
289
+ symbol_key = case _key
290
+ when String
291
+ _key.to_sym
292
+ when Symbol
293
+ _key
294
+ else
295
+ string_key.to_sym
296
+ end
297
+
298
+ # find nested paths
299
+ value.is_a?(::Hash) && class_attributes.each do |name, options|
300
+ if options[:squash] && options[:squash].first == string_key
301
+ send("#{name}=", symbol_key => value)
302
+ end
303
+ end
304
+
305
+ next if ignored_attributes.include?(symbol_key)
306
+
307
+ if class_aliases.key?(symbol_key)
308
+ class_aliases[symbol_key].each do |aliased_key|
309
+ send("#{aliased_key}=", value)
310
+ end
311
+ end
312
+
313
+ assignment_method = "#{string_key}="
314
+
315
+ if !protected_methods.include?(symbol_key) && self.respond_to?(assignment_method, true)
316
+ send(assignment_method, value)
317
+ end
318
+ end
319
+ end
284
320
  end
285
321
  end
@@ -1,11 +1,11 @@
1
1
  module Cistern::Client
2
2
  module Collections
3
3
  def collections
4
- service.collections
4
+ cistern.collections
5
5
  end
6
6
 
7
7
  def requests
8
- service.requests
8
+ cistern.requests
9
9
  end
10
10
  end
11
11
 
@@ -42,20 +42,43 @@ module Cistern::Client
42
42
  interface = options[:interface] || :class
43
43
  interface_callback = (:class == interface) ? :inherited : :included
44
44
 
45
+ if interface == :class
46
+ Cistern.deprecation(
47
+ %q{class' interface is deprecated. Use `include Cistern::Client.with(interface: :module). See https://github.com/lanej/cistern#custom-architecture},
48
+ caller[2],
49
+ )
50
+ end
51
+
45
52
  unless klass.name
46
- fail ArgumentError, "can't turn anonymous class into a Cistern service"
53
+ fail ArgumentError, "can't turn anonymous class into a Cistern cistern"
47
54
  end
48
55
 
49
56
  klass.class_eval <<-EOS, __FILE__, __LINE__
50
57
  module Collections
51
58
  include ::Cistern::Client::Collections
52
59
 
53
- def service
60
+ def cistern
61
+ Cistern.deprecation(
62
+ '#cistern is deprecated. Please use #cistern',
63
+ caller[0]
64
+ )
65
+ #{klass.name}
66
+ end
67
+
68
+ def cistern
54
69
  #{klass.name}
55
70
  end
56
71
  end
57
72
 
58
- def self.service
73
+ def self.cistern
74
+ Cistern.deprecation(
75
+ '#cistern is deprecated. Please use #cistern',
76
+ caller[0]
77
+ )
78
+ #{klass.name}
79
+ end
80
+
81
+ def self.cistern
59
82
  #{klass.name}
60
83
  end
61
84
 
@@ -71,21 +94,29 @@ module Cistern::Client
71
94
 
72
95
  #{interface} #{model_class}
73
96
  def self.#{interface_callback}(klass)
74
- service.models << klass
97
+ cistern.models << klass
75
98
 
76
99
  klass.send(:include, ::Cistern::Model)
77
100
 
78
101
  super
79
102
  end
80
103
 
81
- def self.service
104
+ def self.cistern
105
+ Cistern.deprecation(
106
+ '#cistern is deprecated. Please use #cistern',
107
+ caller[0]
108
+ )
109
+ #{klass.name}
110
+ end
111
+
112
+ def self.cistern
82
113
  #{klass.name}
83
114
  end
84
115
  end
85
116
 
86
117
  #{interface} #{singular_class}
87
118
  def self.#{interface_callback}(klass)
88
- service.singularities << klass
119
+ cistern.singularities << klass
89
120
 
90
121
  klass.send(:include, ::Cistern::Singular)
91
122
 
@@ -93,6 +124,14 @@ module Cistern::Client
93
124
  end
94
125
 
95
126
  def self.service
127
+ Cistern.deprecation(
128
+ '#service is deprecated. Please use #cistern',
129
+ caller[0]
130
+ )
131
+ #{klass.name}
132
+ end
133
+
134
+ def self.cistern
96
135
  #{klass.name}
97
136
  end
98
137
  end
@@ -105,12 +144,20 @@ module Cistern::Client
105
144
  klass.send(:extend, Cistern::Collection::ClassMethods)
106
145
  klass.send(:include, Cistern::Attributes::InstanceMethods)
107
146
 
108
- service.collections << klass
147
+ cistern.collections << klass
109
148
 
110
149
  super
111
150
  end
112
151
 
113
152
  def self.service
153
+ Cistern.deprecation(
154
+ '#service is deprecated. Please use #cistern',
155
+ caller[0]
156
+ )
157
+ #{klass.name}
158
+ end
159
+
160
+ def self.cistern
114
161
  #{klass.name}
115
162
  end
116
163
  end
@@ -119,13 +166,21 @@ module Cistern::Client
119
166
  include ::Cistern::Request
120
167
 
121
168
  def self.service
169
+ Cistern.deprecation(
170
+ '#service is deprecated. Please use #cistern',
171
+ caller[0]
172
+ )
173
+ #{klass.name}
174
+ end
175
+
176
+ def self.cistern
122
177
  #{klass.name}
123
178
  end
124
179
 
125
180
  def self.#{interface_callback}(klass)
126
181
  klass.extend(::Cistern::Request::ClassMethods)
127
182
 
128
- service.requests << klass
183
+ cistern.requests << klass
129
184
 
130
185
  super
131
186
  end
@@ -219,31 +274,31 @@ module Cistern::Client
219
274
  return true if @_setup
220
275
 
221
276
  requests.each do |klass|
222
- name = klass.service_method ||
277
+ name = klass.cistern_method ||
223
278
  Cistern::String.camelize(Cistern::String.demodulize(klass.name))
224
279
 
225
- Cistern::Request.service_request(self, klass, name)
280
+ Cistern::Request.cistern_request(self, klass, name)
226
281
  end
227
282
 
228
283
  collections.each do |klass|
229
- name = klass.service_method ||
284
+ name = klass.cistern_method ||
230
285
  Cistern::String.underscore(klass.name.gsub("#{self.name}::", '').gsub('::', ''))
231
286
 
232
- Cistern::Collection.service_collection(self, klass, name)
287
+ Cistern::Collection.cistern_collection(self, klass, name)
233
288
  end
234
289
 
235
290
  models.each do |klass|
236
- name = klass.service_method ||
291
+ name = klass.cistern_method ||
237
292
  Cistern::String.underscore(klass.name.gsub("#{self.name}::", '').gsub('::', ''))
238
293
 
239
- Cistern::Model.service_model(self, klass, name)
294
+ Cistern::Model.cistern_model(self, klass, name)
240
295
  end
241
296
 
242
297
  singularities.each do |klass|
243
- name = klass.service_method ||
298
+ name = klass.cistern_method ||
244
299
  Cistern::String.underscore(klass.name.gsub("#{self.name}::", '').gsub('::', ''))
245
300
 
246
- Cistern::Singular.service_singular(self, klass, name)
301
+ Cistern::Singular.cistern_singular(self, klass, name)
247
302
  end
248
303
 
249
304
  @_setup = true