parse-stack 1.5.1 → 1.5.2

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +15 -1
  3. data/Gemfile.lock +10 -10
  4. data/README.md +23 -9
  5. data/bin/console +3 -0
  6. data/lib/parse/api/analytics.rb +1 -1
  7. data/lib/parse/api/objects.rb +1 -1
  8. data/lib/parse/api/users.rb +1 -1
  9. data/lib/parse/client.rb +77 -40
  10. data/lib/parse/client/caching.rb +9 -5
  11. data/lib/parse/client/protocol.rb +47 -0
  12. data/lib/parse/client/request.rb +66 -37
  13. data/lib/parse/client/response.rb +39 -21
  14. data/lib/parse/model/acl.rb +4 -9
  15. data/lib/parse/model/associations/belongs_to.rb +97 -9
  16. data/lib/parse/model/associations/collection_proxy.rb +89 -29
  17. data/lib/parse/model/associations/has_many.rb +301 -28
  18. data/lib/parse/model/associations/has_one.rb +98 -4
  19. data/lib/parse/model/associations/pointer_collection_proxy.rb +48 -16
  20. data/lib/parse/model/associations/relation_collection_proxy.rb +61 -36
  21. data/lib/parse/model/bytes.rb +11 -5
  22. data/lib/parse/model/classes/installation.rb +50 -3
  23. data/lib/parse/model/classes/role.rb +7 -2
  24. data/lib/parse/model/classes/session.rb +21 -4
  25. data/lib/parse/model/classes/user.rb +122 -22
  26. data/lib/parse/model/core/actions.rb +7 -3
  27. data/lib/parse/model/core/properties.rb +14 -13
  28. data/lib/parse/model/core/querying.rb +16 -10
  29. data/lib/parse/model/core/schema.rb +2 -3
  30. data/lib/parse/model/date.rb +18 -12
  31. data/lib/parse/model/file.rb +77 -19
  32. data/lib/parse/model/geopoint.rb +70 -12
  33. data/lib/parse/model/model.rb +84 -8
  34. data/lib/parse/model/object.rb +225 -94
  35. data/lib/parse/model/pointer.rb +94 -13
  36. data/lib/parse/model/push.rb +76 -4
  37. data/lib/parse/query.rb +356 -41
  38. data/lib/parse/query/constraints.rb +399 -29
  39. data/lib/parse/query/ordering.rb +21 -8
  40. data/lib/parse/stack.rb +1 -0
  41. data/lib/parse/stack/version.rb +2 -1
  42. data/lib/parse/webhooks.rb +0 -24
  43. data/lib/parse/webhooks/payload.rb +54 -1
  44. data/lib/parse/webhooks/registration.rb +13 -2
  45. metadata +2 -2
@@ -30,65 +30,62 @@ require_relative "associations/has_many"
30
30
 
31
31
 
32
32
  module Parse
33
- =begin
34
- This is the core class for all app specific Parse table subclasses. This class
35
- in herits from Parse::Pointer since an Object is a Parse::Pointer with additional fields,
36
- at a minimum, created_at, updated_at and ACLs.
37
- This class also handles all the relational types of associations in a Parse application and
38
- handles the main CRUD operations.
39
-
40
- Most Pointers and Object subclasses are treated the same. Therefore, defining a class Artist < Parse::Object
41
- that only has `id` set, will be treated as a pointer. Therefore a Parse::Object can be in a "pointer" state
42
- based on the data that it contains. Becasue of this, it is possible to take a Artist instance
43
- (in this example), that is in a pointer state, and fetch the rest of the data for that particular
44
- record without having to create a new object. Doing so would now mark it as not being a pointer anymore.
45
- This is important to the understanding on how relations and properties are handled.
46
-
47
- The implementation of this class is large and has been broken up into several modules.
48
-
49
- Properties:
50
- All columns in a Parse object are considered a type of property (ex. string, numbers, arrays, etc)
51
- except in two cases - Pointers and Relations. For the list of basic supported data types, please see the
52
- Properties module variable Parse::Properties::TYPES . When defining a property,
53
- dynamic methods are created that take advantage of all the ActiveModel
54
- plugins (dirty tracking, callbacks, json, etc).
55
-
56
- Associations (BelongsTo):
57
- This module adds support for creating an association between one object to another using a
58
- Parse object pointer. By defining a belongs_to relationship in a specific class, it implies
59
- that the remote Parse table contains a local column, which has a pointer, referring to another
60
- Parse table.
61
-
62
- Associations (HasMany):
63
- In Parse there are two ways to deal with one-to-many and many-to-many relationships
64
- One is through an array of pointers (which is recommended to be less than 100) and
65
- through an intermediary table called a Relation (or a Join table in other languages.)
66
- The way Parse::Objects treat these associations different from defining a :property of array type, is
67
- by making sure items in the array as of a particular class cast type.
68
-
69
- Querying:
70
- The querying module provides all the general methods to be able to find and query a specific
71
- Parse table.
72
-
73
- Fetching:
74
- The fetching modules supports fetching data from Parse (depending on the state of the object),
75
- and providing some autofetching features when traversing relational objects and properties.
76
-
77
- Schema:
78
- The schema module provides methods to modify the Parse table remotely to either add or create
79
- any locally define properties in ruby and have those be reflected in the Parse application.
80
-
81
- =end
82
-
33
+ # @return [Array] an array of registered Parse::Object subclasses.
83
34
  def self.registered_classes
84
35
  Parse::Object.descendants.map { |m| m.parse_class }.uniq
85
36
  end
86
37
 
87
- # Find a corresponding class for this string or symbol
88
- def self.classify(className)
89
- Parse::Model.find_class className.to_parse_class
90
- end
91
-
38
+ # This is the core class for all app specific Parse table subclasses. This class
39
+ # in herits from Parse::Pointer since an Object is a Parse::Pointer with additional fields,
40
+ # at a minimum, created_at, updated_at and ACLs.
41
+ # This class also handles all the relational types of associations in a Parse application and
42
+ # handles the main CRUD operations.
43
+ #
44
+ # Most Pointers and Object subclasses are treated the same. Therefore, defining a class Artist < Parse::Object
45
+ # that only has `id` set, will be treated as a pointer. Therefore a Parse::Object can be in a "pointer" state
46
+ # based on the data that it contains. Becasue of this, it is possible to take a Artist instance
47
+ # (in this example), that is in a pointer state, and fetch the rest of the data for that particular
48
+ # record without having to create a new object. Doing so would now mark it as not being a pointer anymore.
49
+ # This is important to the understanding on how relations and properties are handled.
50
+ #
51
+ # The implementation of this class is large and has been broken up into several modules.
52
+ #
53
+ # Properties:
54
+ #
55
+ # All columns in a Parse object are considered a type of property (ex. string, numbers, arrays, etc)
56
+ # except in two cases - Pointers and Relations. For the list of basic supported data types, please see the
57
+ # Properties module variable Parse::Properties::TYPES . When defining a property,
58
+ # dynamic methods are created that take advantage of all the ActiveModel
59
+ # plugins (dirty tracking, callbacks, json, etc).
60
+ #
61
+ # Associations:
62
+ #
63
+ # Parse supports a three main types of relational associations. One type of
64
+ # relation is the `One-to-One` association. This is implemented through a
65
+ # specific column in Parse with a Pointer data type. This pointer column,
66
+ # contains a local value that refers to a different record in a separate Parse
67
+ # table. This association is implemented using the `:belongs_to` feature. The
68
+ # second association is of `One-to-Many`. This is implemented is in Parse as a
69
+ # Array type column that contains a list of of Parse pointer objects. It is
70
+ # recommended by Parse that this array does not exceed 100 items for performance
71
+ # reasons. This feature is implemented using the `:has_many` operation with the
72
+ # plural name of the local Parse class. The last association type is a Parse
73
+ # Relation. These can be used to implement a large `Many-to-Many` association
74
+ # without requiring an explicit intermediary Parse table or class. This feature
75
+ # is also implemented using the `:has_many` method but passing the option of `:relation`.
76
+ #
77
+ #
78
+ # Querying:
79
+ # The querying module provides all the general methods to be able to find and query a specific
80
+ # Parse table.
81
+ #
82
+ # Fetching:
83
+ # The fetching modules supports fetching data from Parse (depending on the state of the object),
84
+ # and providing some autofetching features when traversing relational objects and properties.
85
+ #
86
+ # Schema:
87
+ # The schema module provides methods to modify the Parse table remotely to either add or create
88
+ # any locally define properties in ruby and have those be reflected in the Parse application.
92
89
  class Object < Pointer
93
90
  include Properties
94
91
  include Associations::HasOne
@@ -98,14 +95,45 @@ module Parse
98
95
  include Fetching
99
96
  include Actions
100
97
  include Schema
101
- BASE_OBJECT_CLASS = "Parse::Object".freeze # used for comparison
98
+ BASE_OBJECT_CLASS = "Parse::Object".freeze
102
99
 
100
+ # @return [Model::TYPE_OBJECT]
103
101
  def __type; Parse::Model::TYPE_OBJECT; end;
104
- # These define callbacks
102
+
103
+ # Default ActiveModel::Callbacks
104
+ # @!group Callbacks
105
+ #
106
+ # @!method before_create
107
+ # A callback called before the object has been created.
108
+ # @yield A block to execute for the callback.
109
+ # @see ActiveModel::Callbacks
110
+ # @!method after_create
111
+ # A callback called after the object has been created.
112
+ # @yield A block to execute for the callback.
113
+ # @see ActiveModel::Callbacks
114
+ # @!method before_save
115
+ # A callback called before the object is saved.
116
+ # @note This is not related to a Parse beforeSave webhook trigger.
117
+ # @yield A block to execute for the callback.
118
+ # @see ActiveModel::Callbacks
119
+ # @!method after_save
120
+ # A callback called after the object has been successfully saved.
121
+ # @note This is not related to a Parse afterSave webhook trigger.
122
+ # @yield A block to execute for the callback.
123
+ # @see ActiveModel::Callbacks
124
+ # @!method before_destroy
125
+ # A callback called before the object is about to be deleted.
126
+ # @note This is not related to a Parse beforeDelete webhook trigger.
127
+ # @yield A block to execute for the callback.
128
+ # @see ActiveModel::Callbacks
129
+ # @!method after_destroy
130
+ # A callback called after the object has been successfully deleted.
131
+ # @note This is not related to a Parse afterDelete webhook trigger.
132
+ # @yield A block to execute for the callback.
133
+ # @see ActiveModel::Callbacks
134
+ # @!endgroup
105
135
  define_model_callbacks :create, :save, :destroy, only: [:after, :before]
106
- #core attributes. In general these should be treated as read_only, but the
107
- # setters are available since we will be decoding objects from Parse. The :acl
108
- # type is documented in its own class file.
136
+
109
137
  attr_accessor :created_at, :updated_at, :acl
110
138
 
111
139
  # All Parse Objects have a class-level and instance level `parse_class` method, in which the
@@ -114,17 +142,41 @@ module Parse
114
142
  # the remote Parse table is named 'Artist'. You may override this behavior by utilizing the `parse_class(<className>)` method
115
143
  # to set it to something different.
116
144
  class << self
117
- attr_accessor :disable_serialized_string_date
118
- attr_accessor :parse_class, :acl
119
- def parse_class(c = nil)
145
+
146
+ attr_accessor :disable_serialized_string_date, :parse_class, :acl
147
+
148
+ # @!attribute [rw] disable_serialized_string_date
149
+ # Disables returning a serialized string date properties when encoding to JSON.
150
+ # This affects created_at and updated_at fields in order to be backwards compatible with old SDKs.
151
+ # @return [Boolean]
152
+
153
+ # The class method to override the implicitly assumed Parse collection name
154
+ # in your Parse database. The default Parse collection name is the singular form
155
+ # of the ruby Parse::Object subclass name. The Parse class value should match to
156
+ # the corresponding remote table in your database in order to properly store records and
157
+ # perform queries.
158
+ # @example
159
+ # class Song < Parse::Object; end;
160
+ # class Artist < Parse::Object
161
+ # parse_class "Musician" # remote collection name
162
+ # end
163
+ #
164
+ # Parse::User.parse_class # => '_User'
165
+ # Song.parse_class # => 'Song'
166
+ # Artist.parse_class # => 'Musician'
167
+ #
168
+ # @param remoteName [String] the name of the remote collection
169
+ # @return [String] the name of the Parse collection for this model.
170
+ def parse_class(remoteName = nil)
120
171
  @parse_class ||= model_name.name
121
- unless c.nil?
122
- @parse_class = c.to_s
123
- end
172
+ @parse_class = remoteName.to_s unless remoteName.nil?
124
173
  @parse_class
125
174
  end
126
- #this provides basic ACLs for the specific class. Each class can change the default
127
- # ACLs set on their instance objects.
175
+
176
+ # A method to override the default ACLs for new objects for this particular
177
+ # subclass.
178
+ # @param acls [Hash] a hash with key value pairs of ACLs permissions.
179
+ # @return [ACL] the default ACLs for this class.
128
180
  def acl(acls = {}, owner: nil)
129
181
  acls = {"*" => {read: true, write: false} }.merge( acls ).symbolize_keys
130
182
  @acl ||= Parse::ACL.new(acls, owner: owner)
@@ -132,15 +184,39 @@ module Parse
132
184
 
133
185
  end
134
186
 
187
+ # @return [String] the Parse class for this object.
188
+ # @see Parse::Object.parse_class
135
189
  def parse_class
136
190
  self.class.parse_class
137
191
  end
138
192
  alias_method :className, :parse_class
139
193
 
194
+ # @return [Hash] a json-hash representing this object.
140
195
  def as_json(*args)
141
196
  pointer? ? pointer : super(*args)
142
197
  end
143
198
 
199
+ # The main constructor for subclasses. It can take different parameter types
200
+ # including a String and a JSON hash. Assume a `Post` class that inherits
201
+ # from Parse::Object:
202
+ # @note Should only be called with Parse::Object subclasses.
203
+ # @overload new(id)
204
+ # Create a new object with an objectId. This method is useful for creating
205
+ # an unfetched object (pointer-state).
206
+ # @example
207
+ # Post.new "1234"
208
+ # @param id [String] The object id.
209
+ # @overload new(hash = {})
210
+ # Create a new object with Parse JSON hash.
211
+ # @example
212
+ # # JSON hash from Parse
213
+ # Post.new({"className" => "Post", "objectId" => "1234", "title" => "My Title"})
214
+ #
215
+ # post = Post.new title: "My Title"
216
+ # post.title # => "My Title"
217
+ #
218
+ # @param hash [Hash] the hash representing the object
219
+ # @return [Parse::Object] a the corresponding Parse::Object or subclass.
144
220
  def initialize(opts = {})
145
221
  if opts.is_a?(String) #then it's the objectId
146
222
  @id = opts.to_s
@@ -162,28 +238,36 @@ module Parse
162
238
  # do not call super since it is Pointer subclass
163
239
  end
164
240
 
241
+ # force apply default values for any properties defined with default values.
242
+ # @return [Array] list of default fields
165
243
  def apply_defaults!
166
244
  self.class.defaults_list.each do |key|
167
245
  send(key) # should call set default proc/values if nil
168
246
  end
169
247
  end
170
248
 
249
+ # Helper method to create a Parse::Pointer object for a given id.
250
+ # @param id [String] The objectId
251
+ # @return [Parse::Pointer] a pointer object corresponding to this class and id.
171
252
  def self.pointer(id)
172
253
  return nil if id.nil?
173
254
  Parse::Pointer.new self.parse_class, id
174
255
  end
175
256
 
176
- # determines if this object has been saved to parse. If an object has
177
- # pending changes, then it is considered to not yet be persisted. This is true of
178
- # new objects as well.
257
+ # Determines if this object has been saved to the Parse database. If an object has
258
+ # pending changes, then it is considered to not yet be persisted.
259
+ # @return [Boolean] true if this object has not been saved.
179
260
  def persisted?
180
261
  changed? == false && !(@id.nil? || @created_at.nil? || @updated_at.nil? || @acl.nil?)
181
262
  end
182
263
 
183
- # force reload and replace any local fields with data from the persistent store
184
- def reload!
264
+ # force reload from the database and replace any local fields with data from
265
+ # the persistent store
266
+ # @param opts [Hash] a set of options to send to fetch!
267
+ # @see Fetching#fetch!
268
+ def reload!(opts = {})
185
269
  # get the values from the persistence layer
186
- fetch!
270
+ fetch!(opts)
187
271
  clear_changes!
188
272
  end
189
273
 
@@ -192,13 +276,20 @@ module Parse
192
276
  clear_changes_information
193
277
  end
194
278
 
195
- # an object is considered new if it has no id
279
+ # An object is considered new if it has no id. This is the method to use
280
+ # in a webhook beforeSave when checking if this object is new.
281
+ # @return [Boolean] true if the object has no id.
196
282
  def new?
197
283
  @id.blank?
198
284
  end
199
285
 
200
286
  # Existed returns true/false depending whether the object
201
- # had existed before its last save operation.
287
+ # had existed before *its last save operation*. This implies
288
+ # that the created_at and updated_at dates are exactly the same. This
289
+ # is a helper method in a webhook afterSave to know if this object was recently
290
+ # saved in the beforeSave webhook.
291
+ # @note You should not use this method inside a beforeSave webhook.
292
+ # @return [Boolean] true if the last beforeSave webhook successfully saved this object for the first time.
202
293
  def existed?
203
294
  if @id.blank? || @created_at.blank? || @updated_at.blank?
204
295
  return false
@@ -206,9 +297,12 @@ module Parse
206
297
  created_at != updated_at
207
298
  end
208
299
 
209
- # returns a hash of all the changes that have been made to the object. By default
300
+ # Returns a hash of all the changes that have been made to the object. By default
210
301
  # changes to the Parse::Properties::BASE_KEYS are ignored unless you pass true as
211
302
  # an argument.
303
+ # @param include_all [Boolean] whether to include all keys in result.
304
+ # @return [Hash] a hash containing only the change information.
305
+ # @see Properties::BASE_KEYS
212
306
  def updates(include_all = false)
213
307
  h = {}
214
308
  changed.each do |key|
@@ -223,20 +317,29 @@ module Parse
223
317
  h
224
318
  end
225
319
 
226
- # restores the previous state of the object (discards changes, but not from the persistent store)
320
+ # Locally restores the previous state of the object and clears all dirty
321
+ # tracking information.
322
+ # @note This does not reload the object from the persistent store, for this use "reload!" instead.
323
+ # @see #reload!
227
324
  def rollback!
228
325
  restore_attributes
229
326
  end
230
327
 
231
- # overrides ActiveModel::Validations validate! instance method
232
- # if it fails, it raises ActiveModel::ValidationError
233
- # otherwise return self instead of true
328
+ # Overrides ActiveModel::Validations#validate! instance method.
329
+ # It runs all valudations for this object. If it validation fails,
330
+ # it raises ActiveModel::ValidationError otherwise it returns the object.
331
+ # @raise ActiveModel::ValidationError
332
+ # @see ActiveModel::Validations#validate!
333
+ # @return [self] self the object if validation passes.
234
334
  def validate!
235
335
  super
236
336
  self
237
337
  end
238
338
 
239
- # Returns a twin copy of the object without the objectId
339
+ # This method creates a new object of the same instance type with a copy of
340
+ # all the properties of the current instance. This is useful when you want
341
+ # to create a duplicate record.
342
+ # @return [Parse::Object] a twin copy of the object without the objectId
240
343
  def twin
241
344
  h = self.as_json
242
345
  h.delete(Parse::Model::OBJECT_ID)
@@ -245,10 +348,13 @@ module Parse
245
348
  self.class.new h
246
349
  end
247
350
 
351
+ # @return [String] a pretty-formatted JSON string
352
+ # @see JSON.pretty_generate
248
353
  def pretty
249
354
  JSON.pretty_generate( as_json )
250
355
  end
251
356
 
357
+ # clear all change and dirty tracking information.
252
358
  def clear_attribute_change!(atts)
253
359
  clear_attribute_changes(atts)
254
360
  end
@@ -256,9 +362,22 @@ module Parse
256
362
  # Method used for decoding JSON objects into their corresponding Object subclasses.
257
363
  # The first parameter is a hash containing the object data and the second parameter is the
258
364
  # name of the table / class if it is known. If it is not known, we we try and determine it
259
- # by checking the "className" or :className entries in the hash. If an Parse class object hash
260
- # is encoutered for which we don't have a corresponding Parse::Object subclass for, a Parse::Pointer
261
- # will be returned instead.
365
+ # by checking the "className" or :className entries in the hash.
366
+ # @note If a Parse class object hash is encoutered for which we don't have a
367
+ # corresponding Parse::Object subclass for, a Parse::Pointer will be returned instead.
368
+ #
369
+ # @example
370
+ # # assume you have defined Post subclass
371
+ # post = Parse::Object.build({"className" => "Post", "objectId" => '1234'})
372
+ # post # => #<Post:....>
373
+ #
374
+ # # if you know the table name
375
+ # post = Parse::Object.build({"title" => "My Title"}, "Post")
376
+ # # or
377
+ # post = Post.build({"title" => "My Title"})
378
+ # @param json [Hash] a JSON hash that contains a Parse object.
379
+ # @param table [String] the Parse class for this hash. If not passed it will be detected.
380
+ # @return [Parse::Object] an instance of the Parse subclass
262
381
  def self.build(json, table = nil)
263
382
  className = table
264
383
  className ||= (json[Parse::Model::KEY_CLASS_NAME] || json[:className]) if json.is_a?(Hash)
@@ -285,14 +404,22 @@ module Parse
285
404
  # puts "Parse::Object.build error: #{e}"
286
405
  end
287
406
 
288
- # Set default base properties of any Parse::Object
407
+ # @!attribute [rw] id
408
+ # @return [String] the value of Parse "objectId" field.
409
+
410
+ # @!attribute [r] created_at
411
+ # @return [Date] the created_at date of the record in UTC Zulu iso 8601 with 3 millisecond format.
412
+
413
+ # @!attribute [r] updated_at
414
+ # @return [Date] the updated_at date of the record in UTC Zulu iso 8601 with 3 millisecond format.
415
+
416
+ # @!attribute [rw] acl
417
+ # @return [ACL] the access control list (permissions) object for this record.
289
418
  property :id, field: :objectId
290
419
  property :created_at, :date
291
420
  property :updated_at, :date
292
421
  property :acl, :acl, field: :ACL
293
422
 
294
- # TODO: Hack since Parse createdAt and updatedAt dates have to be returned as strings
295
- # in UTC Zulu iso 8601 with 3 millisecond format.
296
423
  def createdAt
297
424
  return @created_at if Parse::Object.disable_serialized_string_date.present?
298
425
  @created_at.to_time.utc.iso8601(3) if @created_at.present?
@@ -305,24 +432,28 @@ module Parse
305
432
 
306
433
  end
307
434
 
435
+
308
436
  end
309
437
 
310
438
  class Array
311
- # This helper method selects all objects in an array that are either inherit from
312
- # Parse::Pointer or are a hash. If it is a hash, a Pare::Object will be built from it
313
- # if it constains the proper fields. Non convertible objects will be removed
439
+ # This helper method selects or converts all objects in an array that are either inherit from
440
+ # Parse::Pointer or are a JSON Parse hash. If it is a hash, a Pare::Object will be built from it
441
+ # if it constains the proper fields. Non-convertible objects will be removed.
314
442
  # If the className is not contained or known, you can pass a table name as an argument
315
- def parse_objects(table = nil)
443
+ # @param className [String] the name of the Parse class if it could not be detected.
444
+ # @return [Array<Parse::Object>] an array of Parse::Object subclasses.
445
+ def parse_objects(className = nil)
316
446
  f = Parse::Model::KEY_CLASS_NAME
317
447
  map do |m|
318
448
  next m if m.is_a?(Parse::Pointer)
319
- if m.is_a?(Hash) && (m[f] || m[:className] || table)
320
- next Parse::Object.build m, (m[f] || m[:className] || table)
449
+ if m.is_a?(Hash) && (m[f] || m[:className] || className)
450
+ next Parse::Object.build m, (m[f] || m[:className] || className)
321
451
  end
322
452
  nil
323
453
  end.compact
324
454
  end
325
455
 
456
+ # @return [Array<String>] an array of objectIds for all objects that are Parse::Objects.
326
457
  def parse_ids
327
458
  parse_objects.map(&:id)
328
459
  end