cistern 2.2.7 → 2.3.0

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: 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