parse-stack 1.5.3 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/parse-ruby-sdk.png +0 -0
  3. data/Changes.md +25 -1
  4. data/Gemfile.lock +4 -4
  5. data/README.md +37 -31
  6. data/bin/console +3 -0
  7. data/lib/parse/api/all.rb +2 -1
  8. data/lib/parse/api/apps.rb +12 -0
  9. data/lib/parse/api/config.rb +5 -1
  10. data/lib/parse/api/files.rb +1 -0
  11. data/lib/parse/api/hooks.rb +1 -0
  12. data/lib/parse/api/objects.rb +4 -1
  13. data/lib/parse/api/push.rb +1 -0
  14. data/lib/parse/api/{schemas.rb → schema.rb} +7 -0
  15. data/lib/parse/api/server.rb +44 -0
  16. data/lib/parse/api/sessions.rb +1 -0
  17. data/lib/parse/api/users.rb +4 -1
  18. data/lib/parse/client.rb +109 -73
  19. data/lib/parse/client/authentication.rb +2 -1
  20. data/lib/parse/client/batch.rb +9 -1
  21. data/lib/parse/client/body_builder.rb +16 -1
  22. data/lib/parse/client/caching.rb +15 -13
  23. data/lib/parse/client/protocol.rb +27 -15
  24. data/lib/parse/client/response.rb +26 -8
  25. data/lib/parse/model/acl.rb +1 -1
  26. data/lib/parse/model/associations/belongs_to.rb +18 -19
  27. data/lib/parse/model/associations/collection_proxy.rb +6 -0
  28. data/lib/parse/model/associations/has_many.rb +5 -6
  29. data/lib/parse/model/bytes.rb +4 -1
  30. data/lib/parse/model/classes/user.rb +46 -44
  31. data/lib/parse/model/core/actions.rb +508 -460
  32. data/lib/parse/model/core/builder.rb +75 -0
  33. data/lib/parse/model/core/errors.rb +9 -0
  34. data/lib/parse/model/core/fetching.rb +42 -38
  35. data/lib/parse/model/core/properties.rb +46 -27
  36. data/lib/parse/model/core/querying.rb +231 -228
  37. data/lib/parse/model/core/schema.rb +76 -74
  38. data/lib/parse/model/date.rb +10 -2
  39. data/lib/parse/model/file.rb +16 -2
  40. data/lib/parse/model/geopoint.rb +9 -2
  41. data/lib/parse/model/model.rb +38 -7
  42. data/lib/parse/model/object.rb +60 -19
  43. data/lib/parse/model/pointer.rb +22 -1
  44. data/lib/parse/model/push.rb +6 -2
  45. data/lib/parse/query.rb +57 -11
  46. data/lib/parse/query/constraint.rb +5 -2
  47. data/lib/parse/query/constraints.rb +588 -589
  48. data/lib/parse/query/ordering.rb +2 -2
  49. data/lib/parse/stack.rb +1 -0
  50. data/lib/parse/stack/version.rb +1 -1
  51. data/lib/parse/webhooks.rb +30 -29
  52. data/lib/parse/webhooks/payload.rb +181 -168
  53. data/lib/parse/webhooks/registration.rb +1 -1
  54. data/parse-stack.gemspec +9 -9
  55. metadata +9 -12
@@ -10,13 +10,56 @@ require 'parallel'
10
10
  require_relative '../../client/request'
11
11
  require_relative 'fetching'
12
12
 
13
+
13
14
  module Parse
15
+
16
+ class Query
17
+
18
+
19
+ # Supporting the `all` class method to be used in scope chaining with queries.
20
+ # @!visibility private
21
+ def all(expressions = {limit: :max})
22
+ conditions(expressions)
23
+ return results(&Proc.new) if block_given?
24
+ results
25
+ end
26
+
27
+ # Supporting the `first_or_create` class method to be used in scope chaining with queries.
28
+ # @!visibility private
29
+ def first_or_create(query_attrs = {}, resource_attrs = {})
30
+ conditions(query_attrs)
31
+ klass = Parse::Model.find_class self.table
32
+ if klass.blank?
33
+ raise ArgumentError, "Parse model with class name #{self.table} is not registered."
34
+ end
35
+ hash_constraints = constraints(true)
36
+ klass.first_or_create(hash_constraints, resource_attrs)
37
+ end
38
+
39
+ # Supporting the `save_all` method to be used in scope chaining with queries.
40
+ # @!visibility private
41
+ def save_all(expressions = {})
42
+ conditions(expressions)
43
+ klass = Parse::Model.find_class self.table
44
+ if klass.blank?
45
+ raise ArgumentError, "Parse model with class name #{self.table} is not registered."
46
+ end
47
+ hash_constraints = constraints(true)
48
+
49
+ klass.save_all(hash_constraints, &Proc.new) if block_given?
50
+ klass.save_all(hash_constraints)
51
+ end
52
+
53
+ end
54
+
14
55
  # A Parse::RelationAction is special operation that adds one object to a relational
15
56
  # table as to another. Depending on the polarity of the action, the objects are
16
57
  # either added or removed from the relation. This class is used to generate the proper
17
58
  # hash request formats Parse needs in order to modify relational information for classes.
18
59
  class RelationAction
60
+ # @!visibility private
19
61
  ADD = "AddRelation"
62
+ # @!visibility private
20
63
  REMOVE = "RemoveRelation"
21
64
  # @!attribute polarity
22
65
  # @return [Boolean] whether it is an addition (true) or removal (false) action.
@@ -55,7 +98,7 @@ end
55
98
  module Parse
56
99
 
57
100
  # An error raised when a save failure occurs.
58
- class SaveFailureError < StandardError
101
+ class RecordNotSaved < StandardError
59
102
  # @return [Parse::Object] the Parse::Object that failed to save.
60
103
  attr_reader :object
61
104
 
@@ -65,533 +108,538 @@ module Parse
65
108
  end
66
109
  end
67
110
 
68
- # Defines some of the save, update and destroy operations for Parse objects.
69
- module Actions
70
- # @!visibility private
71
- def self.included(base)
72
- base.extend(ClassMethods)
73
- end
74
-
75
- # Class methods applied to Parse::Object subclasses.
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.
98
- attr_accessor :raise_on_save_failure
99
-
100
- def raise_on_save_failure
101
- return @raise_on_save_failure unless @raise_on_save_failure.nil?
102
- Parse::Model.raise_on_save_failure
111
+ module Core
112
+ # Defines some of the save, update and destroy operations for Parse objects.
113
+ module Actions
114
+ # @!visibility private
115
+ def self.included(base)
116
+ base.extend(ClassMethods)
103
117
  end
104
118
 
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.
111
- def first_or_create(query_attrs = {}, resource_attrs = {})
112
-
113
- query_attrs.symbolize_keys!
114
- resource_attrs.symbolize_keys!
115
- obj = query(query_attrs).first
116
-
117
- if obj.blank?
118
- obj = self.new query_attrs
119
- obj.apply_attributes!(resource_attrs, dirty_track: false)
119
+ # Class methods applied to Parse::Object subclasses.
120
+ module ClassMethods
121
+ # @!attribute raise_on_save_failure
122
+ # By default, we return `true` or `false` for save and destroy operations.
123
+ # If you prefer to have `Parse::Object` raise an exception instead, you
124
+ # can tell to do so either globally or on a per-model basis. When a save
125
+ # fails, it will raise a {Parse::RecordNotSaved}.
126
+ #
127
+ # When enabled, if an error is returned by Parse due to saving or
128
+ # destroying a record, due to your `before_save` or `before_delete`
129
+ # validation cloud code triggers, `Parse::Object` will return the a
130
+ # {Parse::RecordNotSaved} exception type. This exception has an instance
131
+ # method of `#object` which contains the object that failed to save.
132
+ # @example
133
+ # # globally across all models
134
+ # Parse::Model.raise_on_save_failure = true
135
+ # Song.raise_on_save_failure = true # per-model
136
+ #
137
+ # # or per-instance raise on failure
138
+ # song.save!
139
+ #
140
+ # @return [Boolean] whether to raise a {Parse::RecordNotSaved}
141
+ # when an object fails to save.
142
+ attr_accessor :raise_on_save_failure
143
+
144
+ def raise_on_save_failure
145
+ return @raise_on_save_failure unless @raise_on_save_failure.nil?
146
+ Parse::Model.raise_on_save_failure
120
147
  end
121
- obj.save if obj.new? && Parse::Model.autosave_on_create
122
- obj
123
- end
124
148
 
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.
139
- def save_all(constraints = {})
140
- force = false
141
-
142
- iterator_block = nil
143
- if block_given?
144
- iterator_block = Proc.new
145
- force ||= false
146
- else
147
- # if no block given, assume you want to just save all objects
148
- # regardless of modification.
149
- force = true
149
+ # Finds the first object matching the query conditions, or creates a new
150
+ # unsaved object with the attributes. This method takes the possibility of two hashes,
151
+ # therefore make sure you properly wrap the contents of the input with `{}`.
152
+ # Parse::User.first_or_create({ ..query conditions..})
153
+ # Parse::User.first_or_create({ ..query conditions..}, {.. resrouce_attrs ..})
154
+ # @param query_attrs [Hash] a set of query constraints that also are applied.
155
+ # @param resource_attrs [Hash] a set of attribute values to be applied if an object was not found.
156
+ # @return [Parse::Object] a Parse::Object, whether found by the query or newly created.
157
+ # @see Parse::Model.autosave_on_create
158
+ def first_or_create(query_attrs = {}, resource_attrs = {})
159
+
160
+ query_attrs.symbolize_keys!
161
+ resource_attrs.symbolize_keys!
162
+ obj = query(query_attrs).first
163
+
164
+ if obj.blank?
165
+ obj = self.new query_attrs
166
+ obj.apply_attributes!(resource_attrs, dirty_track: false)
167
+ end
168
+ obj.save if obj.new? && Parse::Model.autosave_on_create
169
+ obj
150
170
  end
151
- # Only generate the comparison block once.
152
- # updated_comparison_block = Proc.new { |x| x.updated_at }
153
-
154
- anchor_date = Parse::Date.now
155
- constraints.merge! :updated_at.on_or_before => anchor_date
156
- constraints.merge! cache: false
157
- # oldest first, so we create a reduction-cycle
158
- constraints.merge! order: :updated_at.asc, limit: 100
159
- update_query = query(constraints)
160
- #puts "Setting Anchor Date: #{anchor_date}"
161
- cursor = nil
162
- has_errors = false
163
- loop do
164
- results = update_query.results
165
-
166
- break if results.empty?
167
-
168
- # verify we didn't get duplicates fetches
169
- if cursor.is_a?(Parse::Object) && results.any? { |x| x.id == cursor.id }
170
- warn "[Parse::SaveAll] Unbounded update detected with id #{cursor.id}."
171
- has_errors = true
172
- break cursor
171
+
172
+ # Auto save all objects matching the query constraints. This method is
173
+ # meant to be used with a block. Any objects that are modified in the block
174
+ # will be batched for a save operation. This uses the `updated_at` field to
175
+ # continue to query for all matching objects that have not been updated.
176
+ # @param constraints [Hash] a set of query constraints.
177
+ # @yield a block which will iterate through each matching object.
178
+ # @example
179
+ #
180
+ # post = Post.first
181
+ # Comments.save_all( post: post) do |comment|
182
+ # # .. modify comment ...
183
+ # # it will automatically be saved
184
+ # end
185
+ # @return [Boolean] whether there were any errors.
186
+ def save_all(constraints = {})
187
+ force = false
188
+
189
+ iterator_block = nil
190
+ if block_given?
191
+ iterator_block = Proc.new
192
+ force ||= false
193
+ else
194
+ # if no block given, assume you want to just save all objects
195
+ # regardless of modification.
196
+ force = true
173
197
  end
198
+ # Only generate the comparison block once.
199
+ # updated_comparison_block = Proc.new { |x| x.updated_at }
200
+
201
+ anchor_date = Parse::Date.now
202
+ constraints.merge! :updated_at.on_or_before => anchor_date
203
+ constraints.merge! cache: false
204
+ # oldest first, so we create a reduction-cycle
205
+ constraints.merge! order: :updated_at.asc, limit: 100
206
+ update_query = query(constraints)
207
+ #puts "Setting Anchor Date: #{anchor_date}"
208
+ cursor = nil
209
+ has_errors = false
210
+ loop do
211
+ results = update_query.results
212
+
213
+ break if results.empty?
214
+
215
+ # verify we didn't get duplicates fetches
216
+ if cursor.is_a?(Parse::Object) && results.any? { |x| x.id == cursor.id }
217
+ warn "[Parse::SaveAll] Unbounded update detected with id #{cursor.id}."
218
+ has_errors = true
219
+ break cursor
220
+ end
174
221
 
175
- results.each(&iterator_block) if iterator_block.present?
176
- # we don't need to refresh the objects in the array with the results
177
- # since we will be throwing them away. Force determines whether
178
- # to save these objects regardless of whether they are dirty.
179
- batch = results.save(merge: false, force: force)
222
+ results.each(&iterator_block) if iterator_block.present?
223
+ # we don't need to refresh the objects in the array with the results
224
+ # since we will be throwing them away. Force determines whether
225
+ # to save these objects regardless of whether they are dirty.
226
+ batch = results.save(merge: false, force: force)
180
227
 
181
- # faster version assuming sorting order wasn't messed up
182
- cursor = results.last
183
- # slower version, but more accurate
184
- # cursor_item = results.max_by(&updated_comparison_block).updated_at
185
- # puts "[Parse::SaveAll] Updated #{results.count} records updated <= #{cursor.updated_at}"
228
+ # faster version assuming sorting order wasn't messed up
229
+ cursor = results.last
230
+ # slower version, but more accurate
231
+ # cursor_item = results.max_by(&updated_comparison_block).updated_at
232
+ # puts "[Parse::SaveAll] Updated #{results.count} records updated <= #{cursor.updated_at}"
186
233
 
187
- if cursor.is_a?(Parse::Object)
188
- update_query.where :updated_at.gte => cursor.updated_at
234
+ if cursor.is_a?(Parse::Object)
235
+ update_query.where :updated_at.gte => cursor.updated_at
236
+
237
+ if cursor.updated_at.present? && cursor.updated_at > anchor_date
238
+ warn "[Parse::SaveAll] Reached anchor date #{anchor_date} < #{cursor.updated_at}"
239
+ break cursor
240
+ end
189
241
 
190
- if cursor.updated_at.present? && cursor.updated_at > anchor_date
191
- warn "[Parse::SaveAll] Reached anchor date #{anchor_date} < #{cursor.updated_at}"
192
- break cursor
193
242
  end
194
243
 
244
+ has_errors ||= batch.error?
195
245
  end
246
+ has_errors
247
+ end
248
+
249
+ end # ClassMethods
196
250
 
197
- has_errors ||= batch.error?
251
+ # Perform an atomic operation on this field. This operation is done on the
252
+ # Parse server which guarantees the atomicity of the operation. This is the low-level
253
+ # API on performing atomic operations on properties for classes. These methods do not
254
+ # update the current instance with any changes the server may have made to satisfy this
255
+ # operation.
256
+ #
257
+ # @param field [String] the name of the field in the Parse collection.
258
+ # @param op_hash [Hash] The operation hash. It may also be of type {Parse::RelationAction}.
259
+ # @return [Boolean] whether the operation was successful.
260
+ def operate_field!(field, op_hash)
261
+ field = field.to_sym
262
+ field = self.field_map[field] || field
263
+ if op_hash.is_a?(Parse::RelationAction)
264
+ op_hash = op_hash.as_json
265
+ else
266
+ op_hash = { field => op_hash }.as_json
198
267
  end
199
- has_errors
200
- end
201
268
 
202
- end # ClassMethods
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.
213
- def operate_field!(field, op_hash)
214
- field = field.to_sym
215
- field = self.field_map[field] || field
216
- if op_hash.is_a?(Parse::RelationAction)
217
- op_hash = op_hash.as_json
218
- else
219
- op_hash = { field => op_hash }.as_json
269
+ response = client.update_object(parse_class, id, op_hash, session_token: _session_token )
270
+ if response.error?
271
+ puts "[#{parse_class}:#{field} Operation] #{response.error}"
272
+ end
273
+ response.success?
220
274
  end
221
275
 
222
- response = client.update_object(parse_class, id, op_hash, session_token: _session_token )
223
- if response.error?
224
- puts "[#{parse_class}:#{field} Operation] #{response.error}"
276
+ # Perform an atomic add operation to the array field.
277
+ # @param field [String] the name of the field in the Parse collection.
278
+ # @param objects [Array] the set of items to add to this field.
279
+ # @return [Boolean] whether it was successful
280
+ # @see #operate_field!
281
+ def op_add!(field,objects)
282
+ operate_field! field, { __op: :Add, objects: objects }
225
283
  end
226
- response.success?
227
- end
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!
234
- def op_add!(field,objects)
235
- operate_field! field, { __op: :Add, objects: objects }
236
- end
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!
244
- def op_add_unique!(field,objects)
245
- operate_field! field, { __op: :AddUnique, objects: objects }
246
- end
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!
253
- def op_remove!(field, objects)
254
- operate_field! field, { __op: :Remove, objects: objects }
255
- end
256
284
 
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!
261
- def op_destroy!(field)
262
- operate_field! field, { __op: :Delete }.freeze
263
- end
285
+ # Perform an atomic add unique operation to the array field. The objects will
286
+ # only be added if they don't already exists in the array for that particular field.
287
+ # @param field [String] the name of the field in the Parse collection.
288
+ # @param objects [Array] the set of items to add uniquely to this field.
289
+ # @return [Boolean] whether it was successful
290
+ # @see #operate_field!
291
+ def op_add_unique!(field,objects)
292
+ operate_field! field, { __op: :AddUnique, objects: objects }
293
+ end
264
294
 
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!
270
- def op_add_relation!(field, objects = [])
271
- objects = [objects] unless objects.is_a?(Array)
272
- return false if objects.empty?
273
- relation_action = Parse::RelationAction.new(field, polarity: true, objects: objects)
274
- operate_field! field, relation_action
275
- end
295
+ # Perform an atomic remove operation to the array field.
296
+ # @param field [String] the name of the field in the Parse collection.
297
+ # @param objects [Array] the set of items to remove to this field.
298
+ # @return [Boolean] whether it was successful
299
+ # @see #operate_field!
300
+ def op_remove!(field, objects)
301
+ operate_field! field, { __op: :Remove, objects: objects }
302
+ end
276
303
 
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!
282
- def op_remove_relation!(field, objects = [])
283
- objects = [objects] unless objects.is_a?(Array)
284
- return false if objects.empty?
285
- relation_action = Parse::RelationAction.new(field, polarity: false, objects: objects)
286
- operate_field! field, relation_action
287
- end
304
+ # Perform an atomic delete operation on this field.
305
+ # @param field [String] the name of the field in the Parse collection.
306
+ # @return [Boolean] whether it was successful
307
+ # @see #operate_field!
308
+ def op_destroy!(field)
309
+ operate_field! field, { __op: :Delete }.freeze
310
+ end
288
311
 
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"
312
+ # Perform an atomic add operation on this relational field.
313
+ # @param field [String] the name of the field in the Parse collection.
314
+ # @param objects [Array<Parse::Object>] the set of objects to add to this relational field.
315
+ # @return [Boolean] whether it was successful
316
+ # @see #operate_field!
317
+ def op_add_relation!(field, objects = [])
318
+ objects = [objects] unless objects.is_a?(Array)
319
+ return false if objects.empty?
320
+ relation_action = Parse::RelationAction.new(field, polarity: true, objects: objects)
321
+ operate_field! field, relation_action
296
322
  end
297
- operate_field! field, { __op: :Increment, amount: amount.to_i }.freeze
298
- end
299
323
 
300
- # @return [Parse::Request] a destroy_request for the current object.
301
- def destroy_request
302
- return nil unless @id.present?
303
- uri = self.uri_path
304
- r = Request.new( :delete, uri )
305
- r.tag = object_id
306
- r
307
- end
324
+ # Perform an atomic remove operation on this relational field.
325
+ # @param field [String] the name of the field in the Parse collection.
326
+ # @param objects [Array<Parse::Object>] the set of objects to remove to this relational field.
327
+ # @return [Boolean] whether it was successful
328
+ # @see #operate_field!
329
+ def op_remove_relation!(field, objects = [])
330
+ objects = [objects] unless objects.is_a?(Array)
331
+ return false if objects.empty?
332
+ relation_action = Parse::RelationAction.new(field, polarity: false, objects: objects)
333
+ operate_field! field, relation_action
334
+ end
308
335
 
309
- # @return [String] the API uri path for this class.
310
- def uri_path
311
- self.client.url_prefix.path + Client.uri_path(self)
312
- end
336
+ # Atomically increment or decrement a specific field.
337
+ # @param field [String] the name of the field in the Parse collection.
338
+ # @param amount [Integer] the amoun to increment. Use negative values to decrement.
339
+ # @see #operate_field!
340
+ def op_increment!(field, amount = 1)
341
+ unless amount.is_a?(Numeric)
342
+ raise ArgumentError, "Amount should be numeric"
343
+ end
344
+ operate_field! field, { __op: :Increment, amount: amount.to_i }.freeze
345
+ end
313
346
 
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.
319
- def change_requests(force = false)
320
- requests = []
321
- # get the URI path for this object.
322
- uri = self.uri_path
323
-
324
- # generate the request to update the object (PUT)
325
- if attribute_changes? || force
326
- # if it's new, then we should call :post for creating the object.
327
- method = new? ? :post : :put
328
- r = Request.new( method, uri, body: attribute_updates)
347
+ # @return [Parse::Request] a destroy_request for the current object.
348
+ def destroy_request
349
+ return nil unless @id.present?
350
+ uri = self.uri_path
351
+ r = Request.new( :delete, uri )
329
352
  r.tag = object_id
330
- requests << r
353
+ r
354
+ end
355
+
356
+ # @return [String] the API uri path for this class.
357
+ def uri_path
358
+ self.client.url_prefix.path + Client.uri_path(self)
331
359
  end
332
360
 
333
- # if the object is not new, then we can also add all the relational changes
334
- # we need to perform.
335
- if @id.present? && relation_changes?
336
- relation_change_operations.each do |ops|
337
- next if ops.empty?
338
- r = Request.new( :put, uri, body: ops)
361
+ # Creates an array of all possible operations that need to be performed
362
+ # on this object. This includes all property and relational operation changes.
363
+ # @param force [Boolean] whether this object should be saved even if does not have
364
+ # pending changes.
365
+ # @return [Array<Parse::Request>] the list of API requests.
366
+ def change_requests(force = false)
367
+ requests = []
368
+ # get the URI path for this object.
369
+ uri = self.uri_path
370
+
371
+ # generate the request to update the object (PUT)
372
+ if attribute_changes? || force
373
+ # if it's new, then we should call :post for creating the object.
374
+ method = new? ? :post : :put
375
+ r = Request.new( method, uri, body: attribute_updates)
339
376
  r.tag = object_id
340
377
  requests << r
341
378
  end
342
- end
343
- requests
344
- end
345
379
 
346
- # This methods sends an update request for this object with the any change
347
- # information based on its local attributes. The bang implies that it will send
348
- # the request even though it is possible no changes were performed. This is useful
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.
353
- def update!(raw: false)
354
- if valid? == false
355
- errors.full_messages.each do |msg|
356
- warn "[#{parse_class}] warning: #{msg}"
380
+ # if the object is not new, then we can also add all the relational changes
381
+ # we need to perform.
382
+ if @id.present? && relation_changes?
383
+ relation_change_operations.each do |ops|
384
+ next if ops.empty?
385
+ r = Request.new( :put, uri, body: ops)
386
+ r.tag = object_id
387
+ requests << r
388
+ end
357
389
  end
390
+ requests
358
391
  end
359
- response = client.update_object(parse_class, id, attribute_updates, session_token: _session_token)
360
- if response.success?
361
- result = response.result
362
- # Because beforeSave hooks can change the fields we are saving, any items that were
363
- # changed, are returned to us and we should apply those locally to be in sync.
364
- set_attributes!(result)
365
- end
366
- puts "Error updating #{self.parse_class}: #{response.error}" if response.error?
367
- return response if raw
368
- response.success?
369
- end
370
-
371
- # Save all the changes related to this object.
372
- # @return [Boolean] true/false whether it was successful.
373
- def update
374
- return true unless attribute_changes?
375
- update!
376
- end
377
392
 
378
- # Save the object as a new record, running all callbacks.
379
- # @return [Boolean] true/false whether it was successful.
380
- def create
381
- run_callbacks :create do
382
- res = client.create_object(parse_class, attribute_updates, session_token: _session_token)
383
- unless res.error?
384
- result = res.result
385
- @id = result[Parse::Model::OBJECT_ID] || @id
386
- @created_at = result["createdAt"] || @created_at
387
- #if the object is created, updatedAt == createdAt
388
- @updated_at = result["updatedAt"] || result["createdAt"] || @updated_at
393
+ # This methods sends an update request for this object with the any change
394
+ # information based on its local attributes. The bang implies that it will send
395
+ # the request even though it is possible no changes were performed. This is useful
396
+ # in kicking-off an beforeSave / afterSave hooks
397
+ # Save the object regardless of whether there are changes. This would call
398
+ # any beforeSave and afterSave cloud code hooks you have registered for this class.
399
+ # @return [Boolean] true/false whether it was successful.
400
+ def update!(raw: false)
401
+ if valid? == false
402
+ errors.full_messages.each do |msg|
403
+ warn "[#{parse_class}] warning: #{msg}"
404
+ end
405
+ end
406
+ response = client.update_object(parse_class, id, attribute_updates, session_token: _session_token)
407
+ if response.success?
408
+ result = response.result
389
409
  # Because beforeSave hooks can change the fields we are saving, any items that were
390
410
  # changed, are returned to us and we should apply those locally to be in sync.
391
411
  set_attributes!(result)
392
412
  end
393
- puts "Error creating #{self.parse_class}: #{res.error}" if res.error?
394
- res.success?
413
+ puts "Error updating #{self.parse_class}: #{response.error}" if response.error?
414
+ return response if raw
415
+ response.success?
395
416
  end
396
- end
397
417
 
398
- # @!visibility private
399
- def _session_token
400
- if @_session_token.respond_to?(:session_token)
401
- @_session_token = @_session_token.session_token
418
+ # Save all the changes related to this object.
419
+ # @return [Boolean] true/false whether it was successful.
420
+ def update
421
+ return true unless attribute_changes?
422
+ update!
423
+ end
424
+
425
+ # Save the object as a new record, running all callbacks.
426
+ # @return [Boolean] true/false whether it was successful.
427
+ def create
428
+ run_callbacks :create do
429
+ res = client.create_object(parse_class, attribute_updates, session_token: _session_token)
430
+ unless res.error?
431
+ result = res.result
432
+ @id = result[Parse::Model::OBJECT_ID] || @id
433
+ @created_at = result["createdAt"] || @created_at
434
+ #if the object is created, updatedAt == createdAt
435
+ @updated_at = result["updatedAt"] || result["createdAt"] || @updated_at
436
+ # Because beforeSave hooks can change the fields we are saving, any items that were
437
+ # changed, are returned to us and we should apply those locally to be in sync.
438
+ set_attributes!(result)
439
+ end
440
+ puts "Error creating #{self.parse_class}: #{res.error}" if res.error?
441
+ res.success?
442
+ end
402
443
  end
403
- @_session_token
404
- end
405
444
 
406
- # saves the object. If the object has not changed, it is a noop. If it is new,
407
- # we will create the object. If the object has an id, we will update the record.
408
- # You can define before and after :save callbacks
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.
414
- def save(autoraise: false, session: nil)
415
- return true unless changed?
416
- success = false
417
- @_session_token = session
418
- run_callbacks :save do
419
- #first process the create/update action if any
420
- #then perform any relation changes that need to be performed
421
- success = new? ? create : update
422
-
423
- # if the save was successful and we have relational changes
424
- # let's update send those next.
425
- if success
426
- if relation_changes?
427
- # get the list of changed keys
428
- changed_attribute_keys = changed - relations.keys.map(&:to_s)
429
- clear_attribute_changes( changed_attribute_keys )
430
- success = update_relations
431
- if success
445
+ # @!visibility private
446
+ def _session_token
447
+ if @_session_token.respond_to?(:session_token)
448
+ @_session_token = @_session_token.session_token
449
+ end
450
+ @_session_token
451
+ end
452
+
453
+ # saves the object. If the object has not changed, it is a noop. If it is new,
454
+ # we will create the object. If the object has an id, we will update the record.
455
+ # You can define before and after :save callbacks
456
+ # autoraise: set to true will automatically raise an exception if the save fails
457
+ # @raise Parse::RecordNotSaved if the save fails
458
+ # @param autoraise [Boolean] whether to raise an exception if the save fails.
459
+ # @param session [String] a session token in order to apply ACLs to this operation.
460
+ # @return [Boolean] whether the save was successful.
461
+ def save(autoraise: false, session: nil)
462
+ return true unless changed?
463
+ success = false
464
+ @_session_token = session
465
+ run_callbacks :save do
466
+ #first process the create/update action if any
467
+ #then perform any relation changes that need to be performed
468
+ success = new? ? create : update
469
+
470
+ # if the save was successful and we have relational changes
471
+ # let's update send those next.
472
+ if success
473
+ if relation_changes?
474
+ # get the list of changed keys
475
+ changed_attribute_keys = changed - relations.keys.map(&:to_s)
476
+ clear_attribute_changes( changed_attribute_keys )
477
+ success = update_relations
478
+ if success
479
+ changes_applied!
480
+ elsif self.class.raise_on_save_failure || autoraise.present?
481
+ raise Parse::RecordNotSaved.new(self), "Failed updating relations. #{self.parse_class} partially saved."
482
+ end
483
+ else
432
484
  changes_applied!
433
- elsif self.class.raise_on_save_failure || autoraise.present?
434
- raise Parse::SaveFailureError.new(self), "Failed updating relations. #{self.parse_class} partially saved."
435
485
  end
436
- else
437
- changes_applied!
486
+ elsif self.class.raise_on_save_failure || autoraise.present?
487
+ raise Parse::RecordNotSaved.new(self), "Failed to create or save attributes. #{self.parse_class} was not saved."
438
488
  end
439
- elsif self.class.raise_on_save_failure || autoraise.present?
440
- raise Parse::SaveFailureError.new(self), "Failed to create or save attributes. #{self.parse_class} was not saved."
441
- end
442
489
 
443
- end #callbacks
444
- @_session_token = nil
445
- success
446
- end
490
+ end #callbacks
491
+ @_session_token = nil
492
+ success
493
+ end
447
494
 
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)
452
- def save!(session: nil)
453
- save(autoraise: true, session: session)
454
- end
495
+ # Save this object and raise an exception if it fails.
496
+ # @raise Parse::RecordNotSaved if the save fails
497
+ # @param session (see #save)
498
+ # @return (see #save)
499
+ def save!(session: nil)
500
+ save(autoraise: true, session: session)
501
+ end
455
502
 
456
503
 
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.
461
- def destroy(session: nil)
462
- return false if new?
463
- @_session_token = session
464
- success = false
465
- run_callbacks :destroy do
466
- res = client.delete_object parse_class, id, session_token: _session_token
467
- success = res.success?
468
- if success
469
- @id = nil
470
- changes_applied!
471
- elsif self.class.raise_on_save_failure
472
- raise Parse::SaveFailureError.new(self), "Failed to create or save attributes. #{self.parse_class} was not saved."
504
+ # Delete this record from the Parse collection. Only valid if this object has an `id`.
505
+ # This will run all the `destroy` callbacks.
506
+ # @param session [String] a session token if you want to apply ACLs for a user in this operation.
507
+ # @return [Boolean] whether the operation was successful.
508
+ def destroy(session: nil)
509
+ return false if new?
510
+ @_session_token = session
511
+ success = false
512
+ run_callbacks :destroy do
513
+ res = client.delete_object parse_class, id, session_token: _session_token
514
+ success = res.success?
515
+ if success
516
+ @id = nil
517
+ changes_applied!
518
+ elsif self.class.raise_on_save_failure
519
+ raise Parse::RecordNotSaved.new(self), "Failed to create or save attributes. #{self.parse_class} was not saved."
520
+ end
521
+ # Your create action methods here
473
522
  end
474
- # Your create action methods here
523
+ @_session_token = nil
524
+ success
475
525
  end
476
- @_session_token = nil
477
- success
478
- end
479
-
480
- # Runs all the registered `before_save` related callbacks.
481
- def prepare_save!
482
- run_callbacks(:save) { false }
483
- end
484
526
 
485
- # @return [Hash] a hash of the list of changes made to this instance.
486
- def changes_payload
487
- h = attribute_updates
488
- if relation_changes?
489
- r = relation_change_operations.select { |s| s.present? }.first
490
- h.merge!(r) if r.present?
527
+ # Runs all the registered `before_save` related callbacks.
528
+ def prepare_save!
529
+ run_callbacks(:save) { false }
491
530
  end
492
- h.merge!(className: parse_class) unless h.empty?
493
- h.as_json
494
- end
495
- alias_method :update_payload, :changes_payload
496
-
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.
503
- def relation_change_operations
504
- return [{},{}] unless relation_changes?
505
-
506
- additions = []
507
- removals = []
508
- # go through all the additions of a collection and generate an action to add.
509
- relation_updates.each do |field,collection|
510
- if collection.additions.count > 0
511
- additions.push Parse::RelationAction.new(field, objects: collection.additions, polarity: true)
531
+
532
+ # @return [Hash] a hash of the list of changes made to this instance.
533
+ def changes_payload
534
+ h = attribute_updates
535
+ if relation_changes?
536
+ r = relation_change_operations.select { |s| s.present? }.first
537
+ h.merge!(r) if r.present?
512
538
  end
513
- # go through all the additions of a collection and generate an action to remove.
514
- if collection.removals.count > 0
515
- removals.push Parse::RelationAction.new(field, objects: collection.removals, polarity: false)
539
+ #h.merge!(className: parse_class) unless h.empty?
540
+ h.as_json
541
+ end
542
+ alias_method :update_payload, :changes_payload
543
+
544
+ # Generates an array with two entries for addition and removal operations. The first entry
545
+ # of the array will contain a hash of all the change operations regarding adding new relational
546
+ # objects. The second entry in the array is a hash of all the change operations regarding removing
547
+ # relation objects from this field.
548
+ # @return [Array] an array with two hashes; the first is a hash of all the addition operations and
549
+ # the second hash, all the remove operations.
550
+ def relation_change_operations
551
+ return [{},{}] unless relation_changes?
552
+
553
+ additions = []
554
+ removals = []
555
+ # go through all the additions of a collection and generate an action to add.
556
+ relation_updates.each do |field,collection|
557
+ if collection.additions.count > 0
558
+ additions.push Parse::RelationAction.new(field, objects: collection.additions, polarity: true)
559
+ end
560
+ # go through all the additions of a collection and generate an action to remove.
561
+ if collection.removals.count > 0
562
+ removals.push Parse::RelationAction.new(field, objects: collection.removals, polarity: false)
563
+ end
516
564
  end
565
+ # merge all additions and removals into one large hash
566
+ additions = additions.reduce({}) { |m,v| m.merge! v.as_json }
567
+ removals = removals.reduce({}) { |m,v| m.merge! v.as_json }
568
+ [additions, removals]
517
569
  end
518
- # merge all additions and removals into one large hash
519
- additions = additions.reduce({}) { |m,v| m.merge! v.as_json }
520
- removals = removals.reduce({}) { |m,v| m.merge! v.as_json }
521
- [additions, removals]
522
- end
523
570
 
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.
526
- def update_relations
527
- # relational saves require an id
528
- return false unless @id.present?
529
- # verify we have relational changes before we do work.
530
- return true unless relation_changes?
531
- raise "Unable to update relations for a new object." if new?
532
- # get all the relational changes (both additions and removals)
533
- additions, removals = relation_change_operations
534
-
535
- responses = []
536
- # Send parallel Parse requests for each of the items to update.
537
- # since we will have multiple responses, we will track it in array
538
- [removals, additions].threaded_each do |ops|
539
- next if ops.empty? #if no operations to be performed, then we are done
540
- responses << client.update_object(parse_class, @id, ops, session_token: _session_token)
571
+ # Saves and updates all the relational changes for made to this object.
572
+ # @return [Boolean] whether all the save or update requests were successful.
573
+ def update_relations
574
+ # relational saves require an id
575
+ return false unless @id.present?
576
+ # verify we have relational changes before we do work.
577
+ return true unless relation_changes?
578
+ raise "Unable to update relations for a new object." if new?
579
+ # get all the relational changes (both additions and removals)
580
+ additions, removals = relation_change_operations
581
+
582
+ responses = []
583
+ # Send parallel Parse requests for each of the items to update.
584
+ # since we will have multiple responses, we will track it in array
585
+ [removals, additions].threaded_each do |ops|
586
+ next if ops.empty? #if no operations to be performed, then we are done
587
+ responses << client.update_object(parse_class, @id, ops, session_token: _session_token)
588
+ end
589
+ # check if any of them ended up in error
590
+ has_error = responses.any? { |response| response.error? }
591
+ # if everything was ok, find the last response to be returned and update
592
+ #their fields in case beforeSave made any changes.
593
+ unless has_error || responses.empty?
594
+ result = responses.last.result #last result to come back
595
+ set_attributes!(result)
596
+ end #unless
597
+ has_error == false
541
598
  end
542
- # check if any of them ended up in error
543
- has_error = responses.any? { |response| response.error? }
544
- # if everything was ok, find the last response to be returned and update
545
- #their fields in case beforeSave made any changes.
546
- unless has_error || responses.empty?
547
- result = responses.last.result #last result to come back
548
- set_attributes!(result)
549
- end #unless
550
- has_error == false
551
- end
552
599
 
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]
566
- def set_attributes!(hash, dirty_track = false)
567
- return unless hash.is_a?(Hash)
568
- hash.each do |k,v|
569
- next if k == Parse::Model::OBJECT_ID || k == Parse::Model::ID
570
- method = "#{k}_set_attribute!"
571
- send(method, v, dirty_track) if respond_to?(method)
600
+ # Performs mass assignment using a hash with the ability to modify dirty tracking.
601
+ # This is an internal method used to set properties on the object while controlling
602
+ # whether they are dirty tracked. Each defined property has a method defined with the
603
+ # suffix `_set_attribute!` that can will be called if it is contained in the hash.
604
+ # @example
605
+ # object.set_attributes!( {"myField" => value}, false)
606
+ #
607
+ # # equivalent to calling the specific method.
608
+ # object.myField_set_attribute!(value, false)
609
+ # @param hash [Hash] the hash containing all the attribute names and values.
610
+ # @param dirty_track [Boolean] whether the assignment should be tracked in the change tracking
611
+ # system.
612
+ # @return [Hash]
613
+ def set_attributes!(hash, dirty_track = false)
614
+ return unless hash.is_a?(Hash)
615
+ hash.each do |k,v|
616
+ next if k == Parse::Model::OBJECT_ID || k == Parse::Model::ID
617
+ method = "#{k}_set_attribute!"
618
+ send(method, v, dirty_track) if respond_to?(method)
619
+ end
572
620
  end
573
- end
574
621
 
575
- # Clears changes information on all collections (array and relations) and all
576
- # local attributes.
577
- def changes_applied!
578
- # find all fields that are of type :array
579
- fields(:array) do |key,v|
580
- proxy = send(key)
581
- # clear changes
582
- proxy.changes_applied! if proxy.respond_to?(:changes_applied!)
583
- end
622
+ # Clears changes information on all collections (array and relations) and all
623
+ # local attributes.
624
+ def changes_applied!
625
+ # find all fields that are of type :array
626
+ fields(:array) do |key,v|
627
+ proxy = send(key)
628
+ # clear changes
629
+ proxy.changes_applied! if proxy.respond_to?(:changes_applied!)
630
+ end
584
631
 
585
- # for all relational fields,
586
- relations.each do |key,v|
587
- proxy = send(key)
588
- # clear changes if they support the method.
589
- proxy.changes_applied! if proxy.respond_to?(:changes_applied!)
632
+ # for all relational fields,
633
+ relations.each do |key,v|
634
+ proxy = send(key)
635
+ # clear changes if they support the method.
636
+ proxy.changes_applied! if proxy.respond_to?(:changes_applied!)
637
+ end
638
+ changes_applied
590
639
  end
591
- changes_applied
592
- end
593
640
 
594
641
 
642
+ end
595
643
  end
596
644
 
597
645
  end