parse-stack 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
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