bodhi-slam 0.8.6 → 0.8.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,33 @@
1
1
  module Bodhi
2
+ # Interface for interacting with resources on the HotSchedules IoT Platform.
2
3
  module Resource
4
+ # @!parse extend Bodhi::Resource::ClassMethods
3
5
 
6
+ # The API context that binds a +Bodhi::Resource+ instance to the HotSchedules IoT Platform
7
+ # @note This is required for the all instance methods to work correctly
8
+ # @return [Bodhi::Context] the API context linked to this object
4
9
  attr_accessor :bodhi_context
5
10
 
6
11
  module ClassMethods
7
12
 
8
- def embedded(bool); @embedded = bool; end
13
+ # Sets the resources embedded status to either true/false.
14
+ #
15
+ # @param status [Boolean]
16
+ # @return [Boolean]
17
+ def embedded(status); @embedded = status; end
18
+
19
+ # Checks if the resource is embedded
20
+ #
21
+ # @return [Boolean]
9
22
  def is_embedded?; @embedded; end
10
23
 
11
24
  # Defines the given +name+ and +options+ as a form attribute for the class.
12
- # The +name+ is set as a property, and validations & factory generators are added based on the supplied +options+
13
- #
25
+ # The +name+ is set as a property, and validations & factory generators
26
+ # are added based on the supplied +options+
27
+ #
28
+ # @param name [String]
29
+ # @param options [Hash]
30
+ # @example
14
31
  # class User
15
32
  # include Bodhi::Resource
16
33
  #
@@ -27,13 +44,30 @@ module Bodhi
27
44
  generates(name.to_sym, options)
28
45
  end
29
46
 
47
+ # Generates a new {Bodhi::Type} instance using the classes metadata
48
+ #
49
+ # @return [Bodhi::Type]
50
+ # @example
51
+ # class User
52
+ # include Bodhi::Resource
53
+ #
54
+ # field :first_name, type: "String", required: true, is_not_blank: true
55
+ # field :last_name, type: "String", required: true, is_not_blank: true
56
+ # field :email, type: "String", required: true, is_not_blank: true, is_email: true
57
+ #
58
+ # index ["last_name"]
59
+ # end
60
+ #
61
+ # User.build_type #=> #<Bodhi::Type:0x007fbff403e808 @name="User" @properties={...} @indexes=[...]>
30
62
  def build_type
31
63
  Bodhi::Type.new(name: self.name, properties: self.properties, indexes: self.indexes, embedded: self.is_embedded?)
32
64
  end
33
65
 
34
66
  # Saves a batch of resources to the Bodhi Cloud in the given +context+
35
67
  # Returns an array of JSON objects describing the results for each record in the batch
36
- #
68
+ #
69
+ # @deprecated This uses the old bulk upload process and will be removed in version 1.0.0 DO NOT USE!
70
+ # @example
37
71
  # context = Bodhi::Context.new
38
72
  # list = Resource.factory.build_list(10)
39
73
  # Resource.save_batch(context, list)
@@ -43,43 +77,80 @@ module Bodhi
43
77
  batch
44
78
  end
45
79
 
46
- # Counts all records that match the given query
80
+ # Counts all records that match the given +query+
47
81
  #
48
- # context = Bodhi::Context.new
49
- # count = Resource.count(context, name: "Foo")
82
+ # Equivalent CURL command:
83
+ # curl -u {username}:{password} https://{server}/{namespace}/resources/{resource}/count?where={query}
84
+ # @param context [Bodhi::Context]
85
+ # @param query [Hash] MongoDB query operations
86
+ # @example
87
+ # Resource.count(context) #=> # count all records
88
+ # Resource.count(context, name: "Foo") #=> # count all records with name == "Foo"
50
89
  def count(context, query={})
51
90
  query_obj = Bodhi::Query.new(name)
52
91
  query_obj.where(query).from(context)
53
92
  query_obj.count
54
93
  end
55
94
 
56
- # Deletes all records that match the given query
95
+ # Deletes all records that match the given +query+
57
96
  #
58
- # context = Bodhi::Context.new
59
- # count = Resource.delete(context, name: "Foo")
97
+ # Equivalent CURL command:
98
+ # curl -u {username}:{password} -X DELETE https://{server}/{namespace}/resources/{resource}?where={query}
99
+ # @note Beware: It's easy to delete an entire collection with this method! Use it wisely :)
100
+ # @param context [Bodhi::Context]
101
+ # @param query [Hash] MongoDB query operations
102
+ # @return [Hash] with key: +count+
103
+ # @todo add more query complex examples
104
+ # @example
105
+ # # delete with a query
106
+ # Resource.delete!(context, sys_created_at: { '$lte': 6.months.ago.iso8601 })
107
+ #
108
+ # # delete all records
109
+ # Resource.delete!(context)
60
110
  def delete!(context, query={})
61
111
  query_obj = Bodhi::Query.new(name)
62
112
  query_obj.where(query).from(context)
63
113
  query_obj.delete
64
114
  end
65
115
 
66
- def create!(params, context=nil)
67
- if context.nil?
68
- context = Bodhi::Context.global_context
69
- end
70
-
116
+ # Creates a new resource with the given +properties+
117
+ # and POSTs it to the IoT Platform
118
+ #
119
+ # Equivalent CURL command:
120
+ # curl -u {username}:{password} -X POST -H "Content-Type: application/json" \
121
+ # https://{server}/{namespace}/resources/{resource} \
122
+ # -d '{properties}'
123
+ # @param context [Bodhi::Context]
124
+ # @param properties [Hash]
125
+ # @return [Bodhi::Resource]
126
+ # @raise [Bodhi::ContextErrors] if the given +Bodhi::Context+ is invalid
127
+ # @raise [Bodhi::ApiErrors] if the record cannot be saved
128
+ # Resource.create!(context, name: "Foo", age: 125, tags: ["green", "blue"])
129
+ def create!(context, properties)
71
130
  if context.invalid?
72
131
  raise Bodhi::ContextErrors.new(context.errors.messages), context.errors.to_a.to_s
73
132
  end
74
133
 
75
- record = self.new(params)
134
+ # Build a new record and set the context
135
+ record = self.new(properties)
76
136
  record.bodhi_context = context
137
+
138
+ # POST to the IoT Platform
77
139
  record.save!
78
140
  return record
79
141
  end
80
142
 
81
- # Returns a single resource from the Bodhi Cloud that matches the given +id+
82
- #
143
+ # Search for a record with the given +id+
144
+ #
145
+ # Equivalent CURL command:
146
+ # curl -u {username}:{password} https://{server}/{namespace}/resources/{resource}/{id}
147
+ # @param id [String] the {Bodhi::Properties#sys_id} of the record
148
+ # @param context [Bodhi::Context]
149
+ # @return [Bodhi::Resource]
150
+ # @raise [Bodhi::ContextErrors] if the given +Bodhi::Context+ is invalid
151
+ # @raise [Bodhi::ApiErrors] if response status is NOT +200+
152
+ # @raise [ArgumentError] if the given +id+ is NOT a +String+
153
+ # @example
83
154
  # context = Bodhi::Context.new
84
155
  # id = Resource.factory.create(context).sys_id
85
156
  # obj = Resource.find(context, id)
@@ -110,9 +181,16 @@ module Bodhi
110
181
  record
111
182
  end
112
183
 
113
- # Returns all records of the given resource from the Bodhi Cloud.
114
- #
115
- # context = Bodhi::Context.new
184
+ # Returns all records in the given +context+
185
+ #
186
+ # All records returned by this method will have their
187
+ # {#bodhi_context} attribute set to +context+
188
+ # @note This method will return ALL records!! Don't use on large collections! YE BE WARNED!!
189
+ # @param context [Bodhi::Context]
190
+ # @return [Array<Bodhi::Resource>]
191
+ # @raise [Bodhi::ContextErrors] if the given +context+ is invalid
192
+ # @raise [Bodhi::ApiErrors] if any response status is NOT 200
193
+ # @example
116
194
  # Resource.find_all(context) # => [#<Resource:0x007fbff403e808>, #<Resource:0x007fbff403e808>, ...]
117
195
  def find_all(context=nil)
118
196
  if context.nil?
@@ -144,10 +222,19 @@ module Bodhi
144
222
  end
145
223
  alias :all :find_all
146
224
 
147
- # Aggregates the given resource based on the supplied +pipeline+
148
- #
149
- # context = Bodhi::Context.new
150
- # Resource.aggregate(context, "[{ $match: { property: { $gte: 20 }}}]")
225
+ # Performs MongoDB aggregations using the given +pipeline+
226
+ #
227
+ # Equivalent CURL command:
228
+ # curl -u {username}:{password} https://{server}/{namespace}/resources/{resource}/aggregate?pipeline={pipeline}
229
+ # @note Large aggregations can be very time and resource intensive!
230
+ # @param context [Bodhi::Context]
231
+ # @param pipeline [String]
232
+ # @return [Hash] the JSON response converted to a Ruby Hash
233
+ # @raise [ArgumentError] if the given +pipeline+ is NOT a +String+
234
+ # @raise [Bodhi::ContextErrors] if the given +context+ is invalid
235
+ # @raise [Bodhi::ApiErrors] if any response status is NOT +200+
236
+ # @example
237
+ # Resource.aggregate(context, "[{ $match: { age: { $gte: 21 }}}]")
151
238
  def aggregate(context, pipeline)
152
239
  if context.invalid?
153
240
  raise Bodhi::ContextErrors.new(context.errors.messages), context.errors.to_a.to_s
@@ -169,11 +256,14 @@ module Bodhi
169
256
  result.body
170
257
  end
171
258
 
172
- # Returns a Bodhi::Query object for quering the given Resource
173
- #
259
+ # Returns a {Bodhi::Query} object with the given +query+
260
+ #
261
+ # @param query [Hash] the MongoDB query operations
262
+ # @return [Bodhi::Query<Bodhi::Resource>] a {Bodhi::Query} object, bound to the {Bodhi::Resource} with the given +query+
263
+ # @example
174
264
  # context = Bodhi::Context.new
175
- # Resource.where("{property: 'value'}").from(context).all
176
- # Resource.where("{conditions}").and("{more conditions}").limit(10).from(context).all
265
+ # Resource.where({conditions}).from(context).all
266
+ # Resource.where({conditions}).and({more conditions}).limit(10).from(context).all
177
267
  def where(query)
178
268
  query_obj = Bodhi::Query.new(name)
179
269
  query_obj.where(query)
@@ -181,145 +271,211 @@ module Bodhi
181
271
  end
182
272
  end
183
273
 
184
- module InstanceMethods
185
- # Saves the resource to the Bodhi Cloud. Returns true if record was saved
186
- #
187
- # obj = Resource.new
188
- # obj.save # => true
189
- # obj.persisted? # => true
190
- def save
191
- if invalid?
192
- return false
193
- end
194
-
195
- if bodhi_context.nil?
196
- @bodhi_context = Bodhi::Context.global_context
197
- end
274
+ # POST the record to the IoT Platform. Returns +true+ if record was saved correctly.
275
+ #
276
+ # Equivalent CURL command:
277
+ # curl -u {username}:{password} -X POST -H "Content-Type: application/json" \
278
+ # https://{server}/{namespace}/resources/{resource} \
279
+ # -d '{properties}'
280
+ # @return [Boolean]
281
+ # @raise [Bodhi::ContextErrors] if the given +Bodhi::Context+ is invalid
282
+ # @example
283
+ # obj = Resource.new
284
+ # obj.save # => true
285
+ # obj.persisted? # => true
286
+ def save
287
+ if invalid?
288
+ return false
289
+ end
198
290
 
199
- if bodhi_context.invalid?
200
- raise Bodhi::ContextErrors.new(bodhi_context.errors.messages), bodhi_context.errors.to_a.to_s
201
- end
291
+ if bodhi_context.nil?
292
+ @bodhi_context = Bodhi::Context.global_context
293
+ end
202
294
 
203
- result = bodhi_context.connection.post do |request|
204
- request.url "/#{bodhi_context.namespace}/resources/#{self.class}"
205
- request.headers['Content-Type'] = 'application/json'
206
- request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
207
- request.body = attributes.to_json
208
- end
209
-
210
- if result.status != 201
211
- raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
212
- end
295
+ if bodhi_context.invalid?
296
+ raise Bodhi::ContextErrors.new(bodhi_context.errors.messages), bodhi_context.errors.to_a.to_s
297
+ end
213
298
 
214
- if result.headers['location']
215
- @sys_id = result.headers['location'].match(/(?<id>[a-zA-Z0-9]{24})/)[:id]
216
- end
299
+ result = bodhi_context.connection.post do |request|
300
+ request.url "/#{bodhi_context.namespace}/resources/#{self.class}"
301
+ request.headers['Content-Type'] = 'application/json'
302
+ request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
303
+ request.body = attributes.to_json
304
+ end
217
305
 
218
- true
306
+ if result.headers['location']
307
+ @sys_id = result.headers['location'].match(/(?<id>[a-zA-Z0-9]{24})/)[:id]
219
308
  end
220
309
 
221
- # Saves the resource to the Bodhi Cloud. Raises ArgumentError if record could not be saved.
222
- #
223
- # obj = Resouce.new
224
- # obj.save!
225
- # obj.persisted? # => true
226
- def save!
227
- if bodhi_context.invalid?
228
- raise Bodhi::ContextErrors.new(bodhi_context.errors.messages), bodhi_context.errors.to_a.to_s
229
- end
310
+ true
311
+ end
230
312
 
231
- result = bodhi_context.connection.post do |request|
232
- request.url "/#{bodhi_context.namespace}/resources/#{self.class}"
233
- request.headers['Content-Type'] = 'application/json'
234
- request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
235
- request.body = attributes.to_json
236
- end
237
-
238
- if result.status != 201
239
- raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
240
- end
313
+ # POST the record to the IoT Platform. Raises error if record could not be saved
314
+ #
315
+ # Equivalent CURL command:
316
+ # curl -u {username}:{password} -X POST -H "Content-Type: application/json" \
317
+ # https://{server}/{namespace}/resources/{resource} \
318
+ # -d '{properties}'
319
+ # @return [nil]
320
+ # @raise [Bodhi::ContextErrors] if the given +Bodhi::Context+ is invalid
321
+ # @raise [Bodhi::ApiErrors] if the response status is NOT +201+
322
+ # @example
323
+ # obj = Resource.new
324
+ # obj.save # => true
325
+ # obj.persisted? # => true
326
+ def save!
327
+ if bodhi_context.invalid?
328
+ raise Bodhi::ContextErrors.new(bodhi_context.errors.messages), bodhi_context.errors.to_a.to_s
329
+ end
241
330
 
242
- if result.headers['location']
243
- @sys_id = result.headers['location'].match(/(?<id>[a-zA-Z0-9]{24})/)[:id]
244
- end
331
+ result = bodhi_context.connection.post do |request|
332
+ request.url "/#{bodhi_context.namespace}/resources/#{self.class}"
333
+ request.headers['Content-Type'] = 'application/json'
334
+ request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
335
+ request.body = attributes.to_json
245
336
  end
246
337
 
247
- def delete!
248
- result = bodhi_context.connection.delete do |request|
249
- request.url "/#{bodhi_context.namespace}/resources/#{self.class}/#{sys_id}"
250
- request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
251
- end
252
-
253
- if result.status != 204
254
- raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
255
- end
338
+ if result.status != 201
339
+ raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
256
340
  end
257
- alias :destroy :delete!
258
341
 
259
- def update!(params)
260
- update_attributes(params)
342
+ if result.headers['location']
343
+ @sys_id = result.headers['location'].match(/(?<id>[a-zA-Z0-9]{24})/)[:id]
344
+ end
261
345
 
262
- if invalid?
263
- return false
264
- end
346
+ return nil
347
+ end
265
348
 
266
- result = bodhi_context.connection.put do |request|
267
- request.url "/#{bodhi_context.namespace}/resources/#{self.class}/#{sys_id}"
268
- request.headers['Content-Type'] = 'application/json'
269
- request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
270
- request.body = attributes.to_json
271
- end
349
+ # DELETE the record from the IoT Platform. Raises error if record could not be deleted
350
+ #
351
+ # Equivalent CURL command:
352
+ # curl -u {username}:{password} -X DELETE https://{server}/{namespace}/resources/{resource}/{sys_id}
353
+ # @return [nil]
354
+ # @raise [Bodhi::ContextErrors] if the given +Bodhi::Context+ is invalid
355
+ # @raise [Bodhi::ApiErrors] if the response status is NOT +204+
356
+ # @example
357
+ # #given: obj.persisted? # => true
358
+ # obj.delete! # => nil
359
+ def delete!
360
+ result = bodhi_context.connection.delete do |request|
361
+ request.url "/#{bodhi_context.namespace}/resources/#{self.class}/#{sys_id}"
362
+ request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
363
+ end
272
364
 
273
- if result.status != 204
274
- raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
275
- end
365
+ if result.status != 204
366
+ raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
367
+ end
276
368
 
277
- true
369
+ return nil
370
+ end
371
+ alias :destroy :delete!
372
+
373
+ # PUT an updated version of the record to the IoT Platform. Raises error if record could not be updated
374
+ #
375
+ # Equivalent CURL command:
376
+ # curl -u {username}:{password} -X PUT -H "Content-Type: application/json" \
377
+ # https://{server}/{namespace}/resources/{resource}/{sys_id} \
378
+ # -d '{updated properties}'
379
+ # @param properties [Hash] Key/Value pairs of the properties to update
380
+ # @return [nil]
381
+ # @raise [Bodhi::ContextErrors] if the given +Bodhi::Context+ is invalid
382
+ # @raise [Bodhi::ApiErrors] if the response status is NOT +204+
383
+ # @example
384
+ # #given: obj.persisted? # => true
385
+ # obj.update!(foo: "test", bar: 12345) # => nil
386
+ def update!(properties)
387
+ update_attributes(properties)
388
+
389
+ if invalid?
390
+ return false
278
391
  end
279
- alias :update :update!
280
392
 
281
- def upsert!(params={})
282
- update_attributes(params)
393
+ result = bodhi_context.connection.put do |request|
394
+ request.url "/#{bodhi_context.namespace}/resources/#{self.class}/#{sys_id}"
395
+ request.headers['Content-Type'] = 'application/json'
396
+ request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
397
+ request.body = attributes.to_json
398
+ end
283
399
 
284
- if invalid?
285
- return false
286
- end
400
+ if result.status != 204
401
+ raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
402
+ end
287
403
 
288
- result = bodhi_context.connection.put do |request|
289
- request.url "/#{bodhi_context.namespace}/resources/#{self.class}?upsert=true"
290
- request.headers['Content-Type'] = 'application/json'
291
- request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
292
- request.body = attributes.to_json
293
- end
404
+ true
405
+ end
406
+ alias :update :update!
407
+
408
+
409
+ # Create or Update an existing record on the IoT Platform. Raises error if record could not be updated
410
+ #
411
+ # Equivalent CURL command:
412
+ # curl -u {username}:{password} -X PUT -H "Content-Type: application/json" \
413
+ # https://{server}/{namespace}/resources/{resource}/{sys_id} \
414
+ # -d '{updated properties}'
415
+ # @param properties [Hash] Key/Value pairs of the properties to update
416
+ # @return [nil]
417
+ # @raise [Bodhi::ContextErrors] if the given +Bodhi::Context+ is invalid
418
+ # @raise [Bodhi::ApiErrors] if the response status is NOT +204+ OR +201+
419
+ # @example
420
+ # #given: obj.persisted? # => true
421
+ # obj.upsert!(foo: "test", bar: 12345) # => nil
422
+ def upsert!(properties={})
423
+ update_attributes(properties)
424
+
425
+ if invalid?
426
+ return false
427
+ end
294
428
 
295
- unless [204, 201].include?(result.status)
296
- raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
297
- end
429
+ result = bodhi_context.connection.put do |request|
430
+ request.url "/#{bodhi_context.namespace}/resources/#{self.class}?upsert=true"
431
+ request.headers['Content-Type'] = 'application/json'
432
+ request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
433
+ request.body = attributes.to_json
434
+ end
298
435
 
299
- if result.headers['location']
300
- @sys_id = result.headers['location'].match(/(?<id>[a-zA-Z0-9]{24})/)[:id]
301
- end
436
+ unless [204, 201].include?(result.status)
437
+ raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
302
438
  end
303
- alias :upsert :upsert!
304
-
305
- def patch!(params)
306
- result = bodhi_context.connection.patch do |request|
307
- request.url "/#{bodhi_context.namespace}/resources/#{self.class}/#{sys_id}"
308
- request.headers['Content-Type'] = 'application/json'
309
- request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
310
- request.body = params.to_json
311
- end
312
-
313
- if result.status != 204
314
- raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
315
- end
439
+
440
+ if result.headers['location']
441
+ @sys_id = result.headers['location'].match(/(?<id>[a-zA-Z0-9]{24})/)[:id]
442
+ end
443
+ end
444
+ alias :upsert :upsert!
445
+
446
+
447
+ # PATCH an existing record on the IoT Platform. Raises error if record could not be updated
448
+ #
449
+ # Equivalent CURL command:
450
+ # curl -u {username}:{password} -X PATCH -H "Content-Type: application/json" \
451
+ # https://{server}/{namespace}/resources/{resource}/{sys_id} \
452
+ # -d '[{operation1}, {operation2}, ...]'
453
+ #
454
+ # @note This method will not update the object after patching. Only the record on the IoT Platform will be updated.
455
+ # @param operations [Array<Hash>] Array of PATCH operations
456
+ # @return [nil]
457
+ # @raise [Bodhi::ContextErrors] if the given +Bodhi::Context+ is invalid
458
+ # @raise [Bodhi::ApiErrors] if the response status is NOT +204+ OR +201+
459
+ # @example
460
+ # #given: obj.persisted? # => true
461
+ # obj.upsert!(foo: "test", bar: 12345) # => nil
462
+ def patch!(operations)
463
+ result = bodhi_context.connection.patch do |request|
464
+ request.url "/#{bodhi_context.namespace}/resources/#{self.class}/#{sys_id}"
465
+ request.headers['Content-Type'] = 'application/json'
466
+ request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
467
+ request.body = operations.to_json
468
+ end
469
+
470
+ if result.status != 204
471
+ raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
316
472
  end
317
473
  end
318
474
 
319
475
  def self.included(base)
320
476
  base.extend(ClassMethods)
321
- base.include(InstanceMethods, Bodhi::Properties, Bodhi::Associations, Bodhi::Validations, Bodhi::Indexes, Bodhi::Factories)
477
+ base.include(Bodhi::Properties, Bodhi::Associations, Bodhi::Validations, Bodhi::Indexes, Bodhi::Factories)
322
478
  base.instance_variable_set(:@embedded, false)
323
479
  end
324
480
  end
325
- end
481
+ end