parse-stack 1.5.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/parse-ruby-sdk.png +0 -0
- data/Changes.md +25 -1
- data/Gemfile.lock +4 -4
- data/README.md +37 -31
- data/bin/console +3 -0
- data/lib/parse/api/all.rb +2 -1
- data/lib/parse/api/apps.rb +12 -0
- data/lib/parse/api/config.rb +5 -1
- data/lib/parse/api/files.rb +1 -0
- data/lib/parse/api/hooks.rb +1 -0
- data/lib/parse/api/objects.rb +4 -1
- data/lib/parse/api/push.rb +1 -0
- data/lib/parse/api/{schemas.rb → schema.rb} +7 -0
- data/lib/parse/api/server.rb +44 -0
- data/lib/parse/api/sessions.rb +1 -0
- data/lib/parse/api/users.rb +4 -1
- data/lib/parse/client.rb +109 -73
- data/lib/parse/client/authentication.rb +2 -1
- data/lib/parse/client/batch.rb +9 -1
- data/lib/parse/client/body_builder.rb +16 -1
- data/lib/parse/client/caching.rb +15 -13
- data/lib/parse/client/protocol.rb +27 -15
- data/lib/parse/client/response.rb +26 -8
- data/lib/parse/model/acl.rb +1 -1
- data/lib/parse/model/associations/belongs_to.rb +18 -19
- data/lib/parse/model/associations/collection_proxy.rb +6 -0
- data/lib/parse/model/associations/has_many.rb +5 -6
- data/lib/parse/model/bytes.rb +4 -1
- data/lib/parse/model/classes/user.rb +46 -44
- data/lib/parse/model/core/actions.rb +508 -460
- data/lib/parse/model/core/builder.rb +75 -0
- data/lib/parse/model/core/errors.rb +9 -0
- data/lib/parse/model/core/fetching.rb +42 -38
- data/lib/parse/model/core/properties.rb +46 -27
- data/lib/parse/model/core/querying.rb +231 -228
- data/lib/parse/model/core/schema.rb +76 -74
- data/lib/parse/model/date.rb +10 -2
- data/lib/parse/model/file.rb +16 -2
- data/lib/parse/model/geopoint.rb +9 -2
- data/lib/parse/model/model.rb +38 -7
- data/lib/parse/model/object.rb +60 -19
- data/lib/parse/model/pointer.rb +22 -1
- data/lib/parse/model/push.rb +6 -2
- data/lib/parse/query.rb +57 -11
- data/lib/parse/query/constraint.rb +5 -2
- data/lib/parse/query/constraints.rb +588 -589
- data/lib/parse/query/ordering.rb +2 -2
- data/lib/parse/stack.rb +1 -0
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/webhooks.rb +30 -29
- data/lib/parse/webhooks/payload.rb +181 -168
- data/lib/parse/webhooks/registration.rb +1 -1
- data/parse-stack.gemspec +9 -9
- 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
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
152
|
-
#
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
constraints
|
157
|
-
#
|
158
|
-
|
159
|
-
|
160
|
-
#
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
-
|
188
|
-
|
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
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
operate_field!
|
263
|
-
|
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
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
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
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
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
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
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
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
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
|
-
|
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
|
-
#
|
334
|
-
#
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
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
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
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
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|
394
|
-
|
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
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
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
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
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
|
-
|
437
|
-
|
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
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
490
|
+
end #callbacks
|
491
|
+
@_session_token = nil
|
492
|
+
success
|
493
|
+
end
|
447
494
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
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
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
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
|
-
|
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
|
-
|
486
|
-
|
487
|
-
|
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
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
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
|
-
#
|
514
|
-
|
515
|
-
|
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
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
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
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
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
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
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
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
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
|