parse-stack 1.5.2 → 1.5.3

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +5 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +40 -80
  5. data/lib/parse/api/all.rb +7 -0
  6. data/lib/parse/api/analytics.rb +8 -3
  7. data/lib/parse/api/apps.rb +29 -1
  8. data/lib/parse/api/batch.rb +14 -129
  9. data/lib/parse/api/cloud_functions.rb +9 -0
  10. data/lib/parse/api/config.rb +10 -1
  11. data/lib/parse/api/files.rb +7 -2
  12. data/lib/parse/api/hooks.rb +45 -2
  13. data/lib/parse/api/objects.rb +43 -6
  14. data/lib/parse/api/push.rb +6 -1
  15. data/lib/parse/api/schemas.rb +15 -1
  16. data/lib/parse/api/sessions.rb +5 -0
  17. data/lib/parse/api/users.rb +64 -5
  18. data/lib/parse/client/authentication.rb +25 -8
  19. data/lib/parse/client/batch.rb +206 -0
  20. data/lib/parse/client/body_builder.rb +12 -6
  21. data/lib/parse/client/caching.rb +42 -10
  22. data/lib/parse/client/protocol.rb +51 -46
  23. data/lib/parse/client/response.rb +1 -47
  24. data/lib/parse/client.rb +171 -42
  25. data/lib/parse/model/acl.rb +184 -39
  26. data/lib/parse/model/associations/belongs_to.rb +1 -0
  27. data/lib/parse/model/classes/role.rb +7 -1
  28. data/lib/parse/model/classes/session.rb +7 -3
  29. data/lib/parse/model/classes/user.rb +107 -0
  30. data/lib/parse/model/core/actions.rb +166 -115
  31. data/lib/parse/model/core/fetching.rb +105 -0
  32. data/lib/parse/model/core/properties.rb +40 -13
  33. data/lib/parse/model/core/querying.rb +123 -39
  34. data/lib/parse/model/core/schema.rb +22 -32
  35. data/lib/parse/model/object.rb +26 -20
  36. data/lib/parse/model/pointer.rb +1 -0
  37. data/lib/parse/query/constraint.rb +65 -27
  38. data/lib/parse/query/constraints.rb +0 -3
  39. data/lib/parse/query/operation.rb +33 -22
  40. data/lib/parse/query/ordering.rb +10 -5
  41. data/lib/parse/stack/generators/rails.rb +5 -1
  42. data/lib/parse/stack/generators/templates/model_installation.rb +1 -1
  43. data/lib/parse/stack/generators/templates/model_role.rb +1 -1
  44. data/lib/parse/stack/generators/templates/model_session.rb +2 -2
  45. data/lib/parse/stack/generators/templates/model_user.rb +1 -1
  46. data/lib/parse/stack/generators/templates/parse.rb +0 -1
  47. data/lib/parse/stack/railtie.rb +1 -0
  48. data/lib/parse/stack/tasks.rb +3 -1
  49. data/lib/parse/stack/version.rb +3 -1
  50. data/lib/parse/webhooks/registration.rb +3 -3
  51. data/lib/parse/webhooks.rb +88 -7
  52. metadata +5 -3
@@ -8,26 +8,34 @@ require 'active_support/core_ext'
8
8
  require 'time'
9
9
  require 'parallel'
10
10
  require_relative '../../client/request'
11
+ require_relative 'fetching'
11
12
 
12
-
13
- # A Parse::RelationAction is special operation that adds one object to a relational
14
- # table as to another. Depending on the polarity of the action, the objects are
15
- # either added or removed from the relation. This class is used to generate the proper
16
- # hash request format Parse needs in order to modify relational information for classes.
17
13
  module Parse
14
+ # A Parse::RelationAction is special operation that adds one object to a relational
15
+ # table as to another. Depending on the polarity of the action, the objects are
16
+ # either added or removed from the relation. This class is used to generate the proper
17
+ # hash request formats Parse needs in order to modify relational information for classes.
18
18
  class RelationAction
19
19
  ADD = "AddRelation"
20
20
  REMOVE = "RemoveRelation"
21
+ # @!attribute polarity
22
+ # @return [Boolean] whether it is an addition (true) or removal (false) action.
23
+ # @!attribute key
24
+ # @return [String] the name of the Parse field (column).
25
+ # @!attribute objects
26
+ # @return [Array<Parse::Object>] the set of objects in this relation action.
21
27
  attr_accessor :polarity, :key, :objects
22
- # provide the column name of the field, polarity (true = add, false = remove) and the
23
- # list of objects.
28
+
29
+ # @param field [String] the name of the Parse field tied to this relation.
30
+ # @param polarity [Boolean] whether this is an addition (true) or removal (false) action.
31
+ # @param objects [Array<Parse::Object>] the set of objects tied to this relation action.
24
32
  def initialize(field, polarity: true, objects: [])
25
33
  @key = field.to_s
26
34
  self.polarity = polarity
27
- @objects = [objects].flatten.compact
35
+ @objects = Array.wrap(objects).compact
28
36
  end
29
37
 
30
- # generate the proper Parse hash-format operation
38
+ # @return [Hash] a hash representing a relation operation.
31
39
  def as_json(*args)
32
40
  { @key =>
33
41
  {
@@ -57,13 +65,36 @@ module Parse
57
65
  end
58
66
  end
59
67
 
68
+ # Defines some of the save, update and destroy operations for Parse objects.
60
69
  module Actions
61
-
70
+ # @!visibility private
62
71
  def self.included(base)
63
72
  base.extend(ClassMethods)
64
73
  end
65
74
 
75
+ # Class methods applied to Parse::Object subclasses.
66
76
  module ClassMethods
77
+ # @!attribute raise_on_save_failure
78
+ # By default, we return `true` or `false` for save and destroy operations.
79
+ # If you prefer to have `Parse::Object` raise an exception instead, you
80
+ # can tell to do so either globally or on a per-model basis. When a save
81
+ # fails, it will raise a {Parse::SaveFailureError}.
82
+ #
83
+ # When enabled, if an error is returned by Parse due to saving or
84
+ # destroying a record, due to your `before_save` or `before_delete`
85
+ # validation cloud code triggers, `Parse::Object` will return the a
86
+ # {Parse::SaveFailureError} exception type. This exception has an instance
87
+ # method of `#object` which contains the object that failed to save.
88
+ # @example
89
+ # # globally across all models
90
+ # Parse::Model.raise_on_save_failure = true
91
+ # Song.raise_on_save_failure = true # per-model
92
+ #
93
+ # # or per-instance raise on failure
94
+ # song.save!
95
+ #
96
+ # @return [Boolean] whether to raise a {Parse::SaveFailureError}
97
+ # when an object fails to save.
67
98
  attr_accessor :raise_on_save_failure
68
99
 
69
100
  def raise_on_save_failure
@@ -71,8 +102,14 @@ module Parse
71
102
  Parse::Model.raise_on_save_failure
72
103
  end
73
104
 
105
+ # Finds the first object matching the query conditions, or creates a new
106
+ # unsaved object with the attributes.
107
+ # the provided query attributes, otherwise create
108
+ # @param query_attrs [Hash] a set of query constraints that also are applied.
109
+ # @param resource_attrs [Hash] a set of attribute values to be applied if an object was not found.
110
+ # @return [Parse::Object] a Parse::Object, whether found by the query or newly created.
74
111
  def first_or_create(query_attrs = {}, resource_attrs = {})
75
- # force only one result
112
+
76
113
  query_attrs.symbolize_keys!
77
114
  resource_attrs.symbolize_keys!
78
115
  obj = query(query_attrs).first
@@ -85,7 +122,20 @@ module Parse
85
122
  obj
86
123
  end
87
124
 
88
- # not quite sure if I like the name of this API.
125
+ # Auto save all objects matching the query constraints. This method is
126
+ # meant to be used with a block. Any objects that are modified in the block
127
+ # will be batched for a save operation. This uses the `updated_at` field to
128
+ # continue to query for all matching objects that have not been updated.
129
+ # @param constraints [Hash] a set of query constraints.
130
+ # @yield a block which will iterate through each matching object.
131
+ # @example
132
+ #
133
+ # post = Post.first
134
+ # Comments.save_all( post: post) do |comment|
135
+ # # .. modify comment ...
136
+ # # it will automatically be saved
137
+ # end
138
+ # @return [Boolean] whether there were any errors.
89
139
  def save_all(constraints = {})
90
140
  force = false
91
141
 
@@ -151,6 +201,15 @@ module Parse
151
201
 
152
202
  end # ClassMethods
153
203
 
204
+ # Perform an atomic operation on this field. This operation is done on the
205
+ # Parse server which guarantees the atomicity of the operation. This is the low-level
206
+ # API on performing atomic operations on properties for classes. These methods do not
207
+ # update the current instance with any changes the server may have made to satisfy this
208
+ # operation.
209
+ #
210
+ # @param field [String] the name of the field in the Parse collection.
211
+ # @param op_hash [Hash] The operation hash. It may also be of type {Parse::RelationAction}.
212
+ # @return [Boolean] whether the operation was successful.
154
213
  def operate_field!(field, op_hash)
155
214
  field = field.to_sym
156
215
  field = self.field_map[field] || field
@@ -167,22 +226,47 @@ module Parse
167
226
  response.success?
168
227
  end
169
228
 
229
+ # Perform an atomic add operation to the array field.
230
+ # @param field [String] the name of the field in the Parse collection.
231
+ # @param objects [Array] the set of items to add to this field.
232
+ # @return [Boolean] whether it was successful
233
+ # @see #operate_field!
170
234
  def op_add!(field,objects)
171
235
  operate_field! field, { __op: :Add, objects: objects }
172
236
  end
173
237
 
238
+ # Perform an atomic add unique operation to the array field. The objects will
239
+ # only be added if they don't already exists in the array for that particular field.
240
+ # @param field [String] the name of the field in the Parse collection.
241
+ # @param objects [Array] the set of items to add uniquely to this field.
242
+ # @return [Boolean] whether it was successful
243
+ # @see #operate_field!
174
244
  def op_add_unique!(field,objects)
175
245
  operate_field! field, { __op: :AddUnique, objects: objects }
176
246
  end
177
247
 
248
+ # Perform an atomic remove operation to the array field.
249
+ # @param field [String] the name of the field in the Parse collection.
250
+ # @param objects [Array] the set of items to remove to this field.
251
+ # @return [Boolean] whether it was successful
252
+ # @see #operate_field!
178
253
  def op_remove!(field, objects)
179
254
  operate_field! field, { __op: :Remove, objects: objects }
180
255
  end
181
256
 
257
+ # Perform an atomic delete operation on this field.
258
+ # @param field [String] the name of the field in the Parse collection.
259
+ # @return [Boolean] whether it was successful
260
+ # @see #operate_field!
182
261
  def op_destroy!(field)
183
- operate_field! field, { __op: :Delete }
262
+ operate_field! field, { __op: :Delete }.freeze
184
263
  end
185
264
 
265
+ # Perform an atomic add operation on this relational field.
266
+ # @param field [String] the name of the field in the Parse collection.
267
+ # @param objects [Array<Parse::Object>] the set of objects to add to this relational field.
268
+ # @return [Boolean] whether it was successful
269
+ # @see #operate_field!
186
270
  def op_add_relation!(field, objects = [])
187
271
  objects = [objects] unless objects.is_a?(Array)
188
272
  return false if objects.empty?
@@ -190,6 +274,11 @@ module Parse
190
274
  operate_field! field, relation_action
191
275
  end
192
276
 
277
+ # Perform an atomic remove operation on this relational field.
278
+ # @param field [String] the name of the field in the Parse collection.
279
+ # @param objects [Array<Parse::Object>] the set of objects to remove to this relational field.
280
+ # @return [Boolean] whether it was successful
281
+ # @see #operate_field!
193
282
  def op_remove_relation!(field, objects = [])
194
283
  objects = [objects] unless objects.is_a?(Array)
195
284
  return false if objects.empty?
@@ -197,7 +286,18 @@ module Parse
197
286
  operate_field! field, relation_action
198
287
  end
199
288
 
200
- # This creates a destroy_request for the current object.
289
+ # Atomically increment or decrement a specific field.
290
+ # @param field [String] the name of the field in the Parse collection.
291
+ # @param amount [Integer] the amoun to increment. Use negative values to decrement.
292
+ # @see #operate_field!
293
+ def op_increment!(field, amount = 1)
294
+ unless amount.is_a?(Numeric)
295
+ raise ArgumentError, "Amount should be numeric"
296
+ end
297
+ operate_field! field, { __op: :Increment, amount: amount.to_i }.freeze
298
+ end
299
+
300
+ # @return [Parse::Request] a destroy_request for the current object.
201
301
  def destroy_request
202
302
  return nil unless @id.present?
203
303
  uri = self.uri_path
@@ -206,13 +306,16 @@ module Parse
206
306
  r
207
307
  end
208
308
 
309
+ # @return [String] the API uri path for this class.
209
310
  def uri_path
210
311
  self.client.url_prefix.path + Client.uri_path(self)
211
312
  end
212
- # Creates an array of all possible PUT operations that need to be performed
213
- # on this local object. The reason it is a list is because attribute operations,
214
- # relational add operations and relational remove operations are treated as separate
215
- # Parse requests.
313
+
314
+ # Creates an array of all possible operations that need to be performed
315
+ # on this object. This includes all property and relational operation changes.
316
+ # @param force [Boolean] whether this object should be saved even if does not have
317
+ # pending changes.
318
+ # @return [Array<Parse::Request>] the list of API requests.
216
319
  def change_requests(force = false)
217
320
  requests = []
218
321
  # get the URI path for this object.
@@ -244,6 +347,9 @@ module Parse
244
347
  # information based on its local attributes. The bang implies that it will send
245
348
  # the request even though it is possible no changes were performed. This is useful
246
349
  # in kicking-off an beforeSave / afterSave hooks
350
+ # Save the object regardless of whether there are changes. This would call
351
+ # any beforeSave and afterSave cloud code hooks you have registered for this class.
352
+ # @return [Boolean] true/false whether it was successful.
247
353
  def update!(raw: false)
248
354
  if valid? == false
249
355
  errors.full_messages.each do |msg|
@@ -262,13 +368,15 @@ module Parse
262
368
  response.success?
263
369
  end
264
370
 
265
- # save the updates on the objects, if any
371
+ # Save all the changes related to this object.
372
+ # @return [Boolean] true/false whether it was successful.
266
373
  def update
267
374
  return true unless attribute_changes?
268
375
  update!
269
376
  end
270
377
 
271
- # create this object in Parse
378
+ # Save the object as a new record, running all callbacks.
379
+ # @return [Boolean] true/false whether it was successful.
272
380
  def create
273
381
  run_callbacks :create do
274
382
  res = client.create_object(parse_class, attribute_updates, session_token: _session_token)
@@ -287,6 +395,7 @@ module Parse
287
395
  end
288
396
  end
289
397
 
398
+ # @!visibility private
290
399
  def _session_token
291
400
  if @_session_token.respond_to?(:session_token)
292
401
  @_session_token = @_session_token.session_token
@@ -298,6 +407,10 @@ module Parse
298
407
  # we will create the object. If the object has an id, we will update the record.
299
408
  # You can define before and after :save callbacks
300
409
  # autoraise: set to true will automatically raise an exception if the save fails
410
+ # @raise Parse::SaveFailureError if the save fails
411
+ # @param autoraise [Boolean] whether to raise an exception if the save fails.
412
+ # @param session [String] a session token in order to apply ACLs to this operation.
413
+ # @return [Boolean] whether the save was successful.
301
414
  def save(autoraise: false, session: nil)
302
415
  return true unless changed?
303
416
  success = false
@@ -332,13 +445,19 @@ module Parse
332
445
  success
333
446
  end
334
447
 
335
- # shortcut for raising an exception of saving this object failed.
448
+ # Save this object and raise an exception if it fails.
449
+ # @raise Parse::SaveFailureError if the save fails
450
+ # @param session (see #save)
451
+ # @return (see #save)
336
452
  def save!(session: nil)
337
453
  save(autoraise: true, session: session)
338
454
  end
339
455
 
340
- # only destroy the object if it has an id. You can setup before and after
341
- #callback hooks on :destroy
456
+
457
+ # Delete this record from the Parse collection. Only valid if this object has an `id`.
458
+ # This will run all the `destroy` callbacks.
459
+ # @param session [String] a session token if you want to apply ACLs for a user in this operation.
460
+ # @return [Boolean] whether the operation was successful.
342
461
  def destroy(session: nil)
343
462
  return false if new?
344
463
  @_session_token = session
@@ -358,10 +477,12 @@ module Parse
358
477
  success
359
478
  end
360
479
 
480
+ # Runs all the registered `before_save` related callbacks.
361
481
  def prepare_save!
362
482
  run_callbacks(:save) { false }
363
483
  end
364
484
 
485
+ # @return [Hash] a hash of the list of changes made to this instance.
365
486
  def changes_payload
366
487
  h = attribute_updates
367
488
  if relation_changes?
@@ -371,11 +492,14 @@ module Parse
371
492
  h.merge!(className: parse_class) unless h.empty?
372
493
  h.as_json
373
494
  end
374
-
375
495
  alias_method :update_payload, :changes_payload
376
496
 
377
- # this method is useful to generate an array of additions and removals to a relational
378
- # column.
497
+ # Generates an array with two entries for addition and removal operations. The first entry
498
+ # of the array will contain a hash of all the change operations regarding adding new relational
499
+ # objects. The second entry in the array is a hash of all the change operations regarding removing
500
+ # relation objects from this field.
501
+ # @return [Array] an array with two hashes; the first is a hash of all the addition operations and
502
+ # the second hash, all the remove operations.
379
503
  def relation_change_operations
380
504
  return [{},{}] unless relation_changes?
381
505
 
@@ -397,7 +521,8 @@ module Parse
397
521
  [additions, removals]
398
522
  end
399
523
 
400
- # update relations updates all the relational data that needs to be updated.
524
+ # Saves and updates all the relational changes for made to this object.
525
+ # @return [Boolean] whether all the save or update requests were successful.
401
526
  def update_relations
402
527
  # relational saves require an id
403
528
  return false unless @id.present?
@@ -425,6 +550,19 @@ module Parse
425
550
  has_error == false
426
551
  end
427
552
 
553
+ # Performs mass assignment using a hash with the ability to modify dirty tracking.
554
+ # This is an internal method used to set properties on the object while controlling
555
+ # whether they are dirty tracked. Each defined property has a method defined with the
556
+ # suffix `_set_attribute!` that can will be called if it is contained in the hash.
557
+ # @example
558
+ # object.set_attributes!( {"myField" => value}, false)
559
+ #
560
+ # # equivalent to calling the specific method.
561
+ # object.myField_set_attribute!(value, false)
562
+ # @param hash [Hash] the hash containing all the attribute names and values.
563
+ # @param dirty_track [Boolean] whether the assignment should be tracked in the change tracking
564
+ # system.
565
+ # @return [Hash]
428
566
  def set_attributes!(hash, dirty_track = false)
429
567
  return unless hash.is_a?(Hash)
430
568
  hash.each do |k,v|
@@ -434,7 +572,7 @@ module Parse
434
572
  end
435
573
  end
436
574
 
437
- # clears changes information on all collections (array and relations) and all
575
+ # Clears changes information on all collections (array and relations) and all
438
576
  # local attributes.
439
577
  def changes_applied!
440
578
  # find all fields that are of type :array
@@ -456,91 +594,4 @@ module Parse
456
594
 
457
595
  end
458
596
 
459
- module Fetching
460
-
461
- # force fetches the current object with the data contained in Parse.
462
- def fetch!(opts = {})
463
- response = client.fetch_object(parse_class, id, opts)
464
- if response.error?
465
- puts "[Fetch Error] #{response.code}: #{response.error}"
466
- end
467
- # take the result hash and apply it to the attributes.
468
- apply_attributes!(response.result, dirty_track: false)
469
- clear_changes!
470
- self
471
- end
472
-
473
- # fetches the object if needed
474
- def fetch
475
- # if it is a pointer, then let's go fetch the rest of the content
476
- pointer? ? fetch! : self
477
- end
478
-
479
- # autofetches the object based on a key. If the key is not a Parse standard
480
- # key, the current object is a pointer, then fetch the object - but only if
481
- # the current object is currently autofetching.
482
- def autofetch!(key)
483
- key = key.to_sym
484
- @fetch_lock ||= false
485
- if @fetch_lock != true && pointer? && key != :acl && Parse::Properties::BASE_KEYS.include?(key) == false && respond_to?(:fetch)
486
- #puts "AutoFetching Triggerd by: #{self.class}.#{key} (#{id})"
487
- @fetch_lock = true
488
- send :fetch
489
- @fetch_lock = false
490
- end
491
-
492
- end
493
-
494
- end
495
-
496
- end
497
-
498
- class Array
499
-
500
- # Support for threaded operations on array items
501
- def threaded_each(threads = 2)
502
- Parallel.each(self, {in_threads: threads}, &Proc.new)
503
- end
504
-
505
- def threaded_map(threads = 2)
506
- Parallel.map(self, {in_threads: threads}, &Proc.new)
507
- end
508
-
509
- def self.threaded_select(threads = 2)
510
- Parallel.select(self, {in_threads: threads}, &Proc.new)
511
- end
512
-
513
- # fetches all the objects in the array (force)
514
- # a parameter symbol can be passed indicating the lookup methodology. Default
515
- # is parallel which fetches all objects in parallel HTTP requests.
516
- # If nil is passed in, then all the fetching happens sequentially.
517
- def fetch_objects!(lookup = :parallel)
518
- # this gets all valid parse objects from the array
519
- items = valid_parse_objects
520
-
521
- # make parallel requests.
522
- unless lookup == :parallel
523
- # force fetch all objects
524
- items.threaded_each { |o| o.fetch! }
525
- else
526
- # serially fetch each object
527
- items.each { |o| o.fetch! }
528
- end
529
- self #return for chaining.
530
- end
531
-
532
- # fetches all pointer objects in the array. You can pass a symbol argument
533
- # that provides the lookup methodology, default is :parallel. Objects that have
534
- # already been fetched (not in a pointer state) are skipped.
535
- def fetch_objects(lookup = :parallel)
536
- items = valid_parse_objects
537
- if lookup == :parallel
538
- items.threaded_each { |o| o.fetch }
539
- else
540
- items.each { |e| e.fetch }
541
- end
542
- #self.replace items
543
- self
544
- end
545
-
546
597
  end
@@ -0,0 +1,105 @@
1
+
2
+ require 'time'
3
+ require 'parallel'
4
+
5
+ module Parse
6
+ # Defines the record fetching interface for instances of Parse::Object.
7
+ module Fetching
8
+
9
+ # Force fetches and updates the current object with the data contained in the Parse collection.
10
+ # The changes applied to the object are not dirty tracked.
11
+ # @param opts [Hash] a set of options to pass to the client request.
12
+ # @return [self] the current object, useful for chaining.
13
+ def fetch!(opts = {})
14
+ response = client.fetch_object(parse_class, id, opts)
15
+ if response.error?
16
+ puts "[Fetch Error] #{response.code}: #{response.error}"
17
+ end
18
+ # take the result hash and apply it to the attributes.
19
+ apply_attributes!(response.result, dirty_track: false)
20
+ clear_changes!
21
+ self
22
+ end
23
+
24
+ # Fetches the object from the Parse data store if the object is in a Pointer
25
+ # state. This is similar to the `fetchIfNeeded` action in the standard Parse client SDK.
26
+ # @return [self] the current object.
27
+ def fetch
28
+ # if it is a pointer, then let's go fetch the rest of the content
29
+ pointer? ? fetch! : self
30
+ end
31
+
32
+ # Autofetches the object based on a key that is not part {Parse::Properties::BASE_KEYS}.
33
+ # If the key is not a Parse standard key, and the current object is in a
34
+ # Pointer state, then fetch the data related to this record from the Parse
35
+ # data store.
36
+ # @param key [String] the name of the attribute being accessed.
37
+ # @return [Boolean]
38
+ def autofetch!(key)
39
+ key = key.to_sym
40
+ @fetch_lock ||= false
41
+ if @fetch_lock != true && pointer? && key != :acl && Parse::Properties::BASE_KEYS.include?(key) == false && respond_to?(:fetch)
42
+ #puts "AutoFetching Triggerd by: #{self.class}.#{key} (#{id})"
43
+ @fetch_lock = true
44
+ send :fetch
45
+ @fetch_lock = false
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+
54
+
55
+
56
+ class Array
57
+
58
+ # Perform a threaded each iteration on a set of array items.
59
+ # @param threads [Integer] the maximum number of threads to spawn/
60
+ # @yield the block for the each iteration.
61
+ # @return [self]
62
+ # @see Array#each
63
+ # @see https://github.com/grosser/parallel Parallel
64
+ def threaded_each(threads = 2, &block)
65
+ Parallel.each(self, {in_threads: threads}, &block)
66
+ end
67
+
68
+ # Perform a threaded map operation on a set of array items.
69
+ # @param threads [Integer] the maximum number of threads to spawn
70
+ # @yield the block for the map iteration.
71
+ # @return [Array] the resultant array from the map.
72
+ # @see Array#map
73
+ # @see https://github.com/grosser/parallel Parallel
74
+ def threaded_map(threads = 2, &block)
75
+ Parallel.map(self, {in_threads: threads}, &block)
76
+ end
77
+
78
+ # Fetches all the objects in the array even if they are not in a Pointer state.
79
+ # @param lookup [Symbol] The methodology to use for HTTP requests. Use :parallel
80
+ # to fetch all objects in parallel HTTP requests. Set to anything else to
81
+ # perform requests serially.
82
+ # @return [Array<Parse::Object>] an array of fetched Parse::Objects.
83
+ # @see Array#fetch_objects
84
+ def fetch_objects!(lookup = :parallel)
85
+ # this gets all valid parse objects from the array
86
+ items = valid_parse_objects
87
+ lookup == :parallel ? items.threaded_each(2,&:fetch!) : items.each(&:fetch!)
88
+ #self.replace items
89
+ self #return for chaining.
90
+ end
91
+
92
+ # Fetches all the objects in the array that are in Pointer state.
93
+ # @param lookup [Symbol] The methodology to use for HTTP requests. Use :parallel
94
+ # to fetch all objects in parallel HTTP requests. Set to anything else to
95
+ # perform requests serially.
96
+ # @return [Array<Parse::Object>] an array of fetched Parse::Objects.
97
+ # @see Array#fetch_objects!
98
+ def fetch_objects(lookup = :parallel)
99
+ items = valid_parse_objects
100
+ lookup == :parallel ? items.threaded_each(2,&:fetch) : items.each(&:fetch)
101
+ #self.replace items
102
+ self
103
+ end
104
+
105
+ end
@@ -14,18 +14,10 @@ require 'active_support/hash_with_indifferent_access'
14
14
  require 'time'
15
15
 
16
16
 
17
- # This module provides support for handling all the different types of column data types
18
- # supported in Parse and mapping them between their remote names with their local ruby named attributes.
19
- # By default, the convention used for naming parameters is that the remote column should be in lower-first-camelcase, (ex. myField, eventAddress), except for
20
- # a few special columns like "id" and "acl".
21
- # Properties are defined when creating subclasses of Parse::Object and using the `property` class method.
22
- #
23
- # By defining properties, dynamic methods are created in order to allow getters and setters to be used. We will go into detail below.
24
- #
25
- # Each class will have a different copy of attribute mapping and field mappings.
26
-
27
17
  module Parse
28
18
 
19
+ # This module provides support for handling all the different types of column data types
20
+ # supported in Parse and mapping them between their remote names with their local ruby named attributes.
29
21
  module Properties
30
22
  # This is an exception that is thrown if there is an issue when creating a specific property for a class.
31
23
  class DefinitionError < StandardError; end;
@@ -49,7 +41,10 @@ module Parse
49
41
  module ClassMethods
50
42
 
51
43
  # The fields method returns a mapping of all local attribute names and their data type.
52
- # if type is passed, we return only the fields that matched that data type
44
+ # if type is passed, we return only the fields that matched that data type. If `type`
45
+ # is provided, it will only return the fields that match the data type.
46
+ # @param type [Symbol] a property type.
47
+ # @return [Hash] the defined fields for this Parse collection with their data type.
53
48
  def fields(type = nil)
54
49
  @fields ||= {id: :string, created_at: :date, updated_at: :date, acl: :acl}
55
50
  if type.present?
@@ -59,11 +54,12 @@ module Parse
59
54
  @fields
60
55
  end
61
56
 
62
- # This returns the mapping of local to remote attribute names.
57
+ # @return [Hash] the field map for this subclass.
63
58
  def field_map
64
59
  @field_map ||= BASE_FIELD_MAP.dup
65
60
  end
66
61
 
62
+ # @return [Hash] the fields that are marked as enums.
67
63
  def enums
68
64
  @enums ||= {}
69
65
  end
@@ -73,6 +69,7 @@ module Parse
73
69
  @attributes = BASE.merge(hash)
74
70
  end
75
71
 
72
+ # @return [Hash] the fields that are marked as enums.
76
73
  def attributes
77
74
  @attributes ||= BASE.dup
78
75
  end
@@ -303,7 +300,7 @@ module Parse
303
300
  # support question mark methods for boolean
304
301
  if data_type == :boolean
305
302
  if self.method_defined?("#{key}?")
306
- puts "Creating boolean helper :#{key}?. Will overwrite existing method #{self}##{key}?."
303
+ puts "Creating boolean helper :#{key}?. Will overwrite existing method #{self}##{key}_increment!."
307
304
  end
308
305
 
309
306
  # returns true if set to true, false otherwise
@@ -311,6 +308,36 @@ module Parse
311
308
  unless opts[:scopes] == false
312
309
  scope key, ->(opts = {}){ query( opts.merge(key => true) ) }
313
310
  end
311
+ elsif data_type == :integer || data_type == :float
312
+ if self.method_defined?("#{key}_increment!")
313
+ puts "Creating increment helper :#{key}_increment!. Will overwrite existing method #{self}##{key}_increment!."
314
+ end
315
+
316
+ define_method("#{key}_increment!") do |amount = 1|
317
+ unless amount.is_a?(Numeric)
318
+ raise ArgumentError, "Amount needs to be an integer"
319
+ end
320
+ result = self.op_increment!(key, amount)
321
+ if result
322
+ new_value = send(key).to_i + amount
323
+ # set the updated value, with no dirty tracking
324
+ self.send set_attribute_method, new_value, false
325
+ end
326
+ result
327
+ end
328
+
329
+ if self.method_defined?("#{key}_decrement!")
330
+ puts "Creating decrement helper :#{key}_decrement!. Will overwrite existing method #{self}##{key}_decrement!."
331
+ end
332
+
333
+ define_method("#{key}_decrement!") do |amount = -1|
334
+ unless amount.is_a?(Numeric)
335
+ raise ArgumentError, "Amount needs to be an integer"
336
+ end
337
+ amount = -amount if amount > 0
338
+ send("#{key}_increment!", amount)
339
+ end
340
+
314
341
  end
315
342
 
316
343
  # The second method to be defined is a setter method. This is done by