parse-stack 1.0.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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +6 -0
  3. data/Gemfile.lock +77 -0
  4. data/LICENSE +20 -0
  5. data/README.md +1281 -0
  6. data/Rakefile +12 -0
  7. data/bin/console +20 -0
  8. data/bin/server +10 -0
  9. data/bin/setup +7 -0
  10. data/lib/parse/api/all.rb +13 -0
  11. data/lib/parse/api/analytics.rb +16 -0
  12. data/lib/parse/api/apps.rb +37 -0
  13. data/lib/parse/api/batch.rb +148 -0
  14. data/lib/parse/api/cloud_functions.rb +18 -0
  15. data/lib/parse/api/config.rb +22 -0
  16. data/lib/parse/api/files.rb +21 -0
  17. data/lib/parse/api/hooks.rb +68 -0
  18. data/lib/parse/api/objects.rb +77 -0
  19. data/lib/parse/api/push.rb +16 -0
  20. data/lib/parse/api/schemas.rb +25 -0
  21. data/lib/parse/api/sessions.rb +11 -0
  22. data/lib/parse/api/users.rb +43 -0
  23. data/lib/parse/client.rb +225 -0
  24. data/lib/parse/client/authentication.rb +59 -0
  25. data/lib/parse/client/body_builder.rb +69 -0
  26. data/lib/parse/client/caching.rb +103 -0
  27. data/lib/parse/client/protocol.rb +15 -0
  28. data/lib/parse/client/request.rb +43 -0
  29. data/lib/parse/client/response.rb +116 -0
  30. data/lib/parse/model/acl.rb +182 -0
  31. data/lib/parse/model/associations/belongs_to.rb +121 -0
  32. data/lib/parse/model/associations/collection_proxy.rb +202 -0
  33. data/lib/parse/model/associations/has_many.rb +218 -0
  34. data/lib/parse/model/associations/pointer_collection_proxy.rb +71 -0
  35. data/lib/parse/model/associations/relation_collection_proxy.rb +134 -0
  36. data/lib/parse/model/bytes.rb +50 -0
  37. data/lib/parse/model/core/actions.rb +499 -0
  38. data/lib/parse/model/core/properties.rb +377 -0
  39. data/lib/parse/model/core/querying.rb +100 -0
  40. data/lib/parse/model/core/schema.rb +92 -0
  41. data/lib/parse/model/date.rb +50 -0
  42. data/lib/parse/model/file.rb +127 -0
  43. data/lib/parse/model/geopoint.rb +98 -0
  44. data/lib/parse/model/model.rb +120 -0
  45. data/lib/parse/model/object.rb +347 -0
  46. data/lib/parse/model/pointer.rb +106 -0
  47. data/lib/parse/model/push.rb +99 -0
  48. data/lib/parse/query.rb +378 -0
  49. data/lib/parse/query/constraint.rb +130 -0
  50. data/lib/parse/query/constraints.rb +176 -0
  51. data/lib/parse/query/operation.rb +66 -0
  52. data/lib/parse/query/ordering.rb +49 -0
  53. data/lib/parse/stack.rb +11 -0
  54. data/lib/parse/stack/version.rb +5 -0
  55. data/lib/parse/webhooks.rb +228 -0
  56. data/lib/parse/webhooks/payload.rb +115 -0
  57. data/lib/parse/webhooks/registration.rb +139 -0
  58. data/parse-stack.gemspec +45 -0
  59. metadata +340 -0
@@ -0,0 +1,347 @@
1
+ require 'active_model'
2
+ require 'active_support'
3
+ require 'active_support/inflector'
4
+ require 'active_support/core_ext/object'
5
+ require 'active_model_serializers'
6
+ require 'time'
7
+ require 'open-uri'
8
+
9
+ require_relative "../client"
10
+ require_relative "model"
11
+ require_relative "pointer"
12
+ require_relative "geopoint"
13
+ require_relative "file"
14
+ require_relative "bytes"
15
+ require_relative "date"
16
+ require_relative "acl"
17
+ require_relative "push"
18
+ require_relative 'core/actions'
19
+ require_relative 'core/querying'
20
+ require_relative "core/schema"
21
+ require_relative "core/properties"
22
+ require_relative "associations/belongs_to"
23
+ require_relative "associations/has_many"
24
+
25
+
26
+ module Parse
27
+ =begin
28
+ This is the core class for all app specific Parse table subclasses. This class
29
+ in herits from Parse::Pointer since an Object is a Parse::Pointer with additional fields,
30
+ at a minimum, created_at, updated_at and ACLs.
31
+ This class also handles all the relational types of associations in a Parse application and
32
+ handles the main CRUD operations.
33
+
34
+ Most Pointers and Object subclasses are treated the same. Therefore, defining a class Artist < Parse::Object
35
+ that only has `id` set, will be treated as a pointer. Therefore a Parse::Object can be in a "pointer" state
36
+ based on the data that it contains. Becasue of this, it is possible to take a Artist instance
37
+ (in this example), that is in a pointer state, and fetch the rest of the data for that particular
38
+ record without having to create a new object. Doing so would now mark it as not being a pointer anymore.
39
+ This is important to the understanding on how relations and properties are handled.
40
+
41
+ The implementation of this class is large and has been broken up into several modules.
42
+
43
+ Properties:
44
+ All columns in a Parse object are considered a type of property (ex. string, numbers, arrays, etc)
45
+ except in two cases - Pointers and Relations. For the list of basic supported data types, please see the
46
+ Properties module variable Parse::Properties::TYPES . When defining a property,
47
+ dynamic methods are created that take advantage of all the ActiveModel
48
+ plugins (dirty tracking, callbacks, json, etc).
49
+
50
+ Associations (BelongsTo):
51
+ This module adds support for creating an association between one object to another using a
52
+ Parse object pointer. By defining a belongs_to relationship in a specific class, it implies
53
+ that the remote Parse table contains a local column, which has a pointer, referring to another
54
+ Parse table.
55
+
56
+ Associations (HasMany):
57
+ In Parse there are two ways to deal with one-to-many and many-to-many relationships
58
+ One is through an array of pointers (which is recommended to be less than 100) and
59
+ through an intermediary table called a Relation (or a Join table in other languages.)
60
+ The way Parse::Objects treat these associations different from defining a :property of array type, is
61
+ by making sure items in the array as of a particular class cast type.
62
+
63
+ Querying:
64
+ The querying module provides all the general methods to be able to find and query a specific
65
+ Parse table.
66
+
67
+ Fetching:
68
+ The fetching modules supports fetching data from Parse (depending on the state of the object),
69
+ and providing some autofetching features when traversing relational objects and properties.
70
+
71
+ Schema:
72
+ The schema module provides methods to modify the Parse table remotely to either add or create
73
+ any locally define properties in ruby and have those be reflected in the Parse application.
74
+
75
+ =end
76
+
77
+ def self.registered_classes
78
+ Parse::Object.descendants.map { |m| m.parse_class }.uniq
79
+ end
80
+
81
+ class Object < Pointer
82
+ include Properties
83
+ include Associations::BelongsTo
84
+ include Associations::HasMany
85
+ include Querying
86
+ include Fetching
87
+ include Actions
88
+ include Schema
89
+
90
+ def __type; Parse::Model::TYPE_OBJECT; end;
91
+ # These define callbacks
92
+ define_model_callbacks :save, :destroy
93
+ #core attributes. In general these should be treated as read_only, but the
94
+ # setters are available since we will be decoding objects from Parse. The :acl
95
+ # type is documented in its own class file.
96
+ attr_accessor :created_at, :updated_at, :acl
97
+
98
+ # All Parse Objects have a class-level and instance level `parse_class` method, in which the
99
+ # instance method is a convenience one for the class one. The default value for the parse_class is
100
+ # the name of the ruby class name. Therefore if you have an 'Artist' ruby class, then by default we will assume
101
+ # the remote Parse table is named 'Artist'. You may override this behavior by utilizing the `parse_class(<className>)` method
102
+ # to set it to something different.
103
+ class << self
104
+ attr_accessor :disable_serialized_string_date
105
+ attr_accessor :parse_class, :acl
106
+ def parse_class(c = nil)
107
+ @parse_class ||= model_name.name
108
+ unless c.nil?
109
+ @parse_class = c.to_s
110
+ end
111
+ @parse_class
112
+ end
113
+ #this provides basic ACLs for the specific class. Each class can change the default
114
+ # ACLs set on their instance objects.
115
+ def acl(acls = {}, owner: nil)
116
+ acls = {"*" => {read: true, write: false} }.merge( acls ).symbolize_keys
117
+ @acl ||= Parse::ACL.new(acls, owner: owner)
118
+ end
119
+
120
+ end
121
+
122
+ def parse_class
123
+ self.class.parse_class
124
+ end
125
+ alias_method :className, :parse_class
126
+
127
+ def as_json(*args)
128
+ pointer? ? pointer : super(*args)
129
+ end
130
+
131
+ def initialize(opts = {})
132
+ if opts.is_a?(String) #then it's the objectId
133
+ @id = opts.to_s
134
+ elsif opts.is_a?(Hash)
135
+ #if the objectId is provided we will consider the object pristine
136
+ #and not track dirty items
137
+ dirty_track = opts["objectId".freeze] || opts[:objectId] || opts[:id]
138
+ apply_attributes!(opts, dirty_track: !dirty_track)
139
+ end
140
+
141
+ if self.acl.blank?
142
+ self.acl = self.class.acl({}, owner: self) || Parse::ACL.new(owner: self)
143
+ end
144
+ clear_changes! if @id.present? #then it was an import
145
+ # do not call super since it is Pointer subclass
146
+ end
147
+
148
+ def self.pointer(id)
149
+ return nil if id.nil?
150
+ Parse::Pointer.new self.parse_class, id
151
+ end
152
+
153
+ # determines if this object has been saved to parse. If an object has
154
+ # pending changes, then it is considered to not yet be persisted. This is true of
155
+ # new objects as well.
156
+ def persisted?
157
+ changed? == false && !(@id.nil? || @created_at.nil? || @updated_at.nil? || @acl.nil?)
158
+ end
159
+
160
+ # force reload and replace any local fields with data from the persistent store
161
+ def reload!
162
+ # get the values from the persistence layer
163
+ fetch!
164
+ clear_changes!
165
+ end
166
+
167
+ # clears all dirty tracking information
168
+ def clear_changes!
169
+ clear_changes_information
170
+ end
171
+
172
+ # an object is considered new if it has no id
173
+ def new?
174
+ @id.blank?
175
+ end
176
+
177
+ # Existed returns true/false depending whether the object
178
+ # had existed before its last save operation.
179
+ def existed?
180
+ if @id.blank? || @created_at.blank? || @updated_at.blank?
181
+ return false
182
+ end
183
+ created_at != updated_at
184
+ end
185
+
186
+ # returns a hash of all the changes that have been made to the object. By default
187
+ # changes to the Parse::Properties::BASE_KEYS are ignored unless you pass true as
188
+ # an argument.
189
+ def updates(include_all = false)
190
+ h = {}
191
+ changed.each do |key|
192
+ next if include_all == false && Parse::Properties::BASE_KEYS.include?(key.to_sym)
193
+ # lookup the remote Parse field name incase it is different from the local attribute name
194
+ remote_field = self.field_map[key.to_sym] || key
195
+ h[remote_field] = send key
196
+ # make an exception to Parse::Objects, we should return a pointer to them instead
197
+ h[remote_field] = h[remote_field].pointer if h[remote_field].respond_to?(:pointer)
198
+ end
199
+ h
200
+ end
201
+
202
+ # restores the previous state of the object (discards changes, but not from the persistent store)
203
+ def rollback!
204
+ restore_attributes
205
+ end
206
+
207
+ # Returns a twin copy of the object without the objectId
208
+ def twin
209
+ h = self.as_json
210
+ h.delete("objectId")
211
+ h.delete(:objectId)
212
+ h.delete(:id)
213
+ self.class.new h
214
+ end
215
+
216
+ def clear_attribute_change!(atts)
217
+ clear_attribute_changes(atts)
218
+ end
219
+
220
+ # Method used for decoding JSON objects into their corresponding Object subclasses.
221
+ # The first parameter is a hash containing the object data and the second parameter is the
222
+ # name of the table / class if it is known. If it is not known, we we try and determine it
223
+ # by checking the "className" or :className entries in the hash. If an Parse class object hash
224
+ # is encoutered for which we don't have a corresponding Parse::Object subclass for, a Parse::Pointer
225
+ # will be returned instead.
226
+ def self.build(json, table = nil)
227
+ className = table
228
+ className ||= (json["className"] || json[:className]) if json.is_a?(Hash)
229
+ if json.is_a?(Hash) && json["error"].present? && json["code"].present?
230
+ warn "[Parse::Object] Detected object hash with 'error' and 'code' set. : #{json}"
231
+ end
232
+ return if className.nil?
233
+ # we should do a reverse lookup on who is registered for a different class type
234
+ # than their name with parse_class
235
+ klass = Parse::Model.find_class className
236
+ o = nil
237
+ if klass.present?
238
+ o = klass.new
239
+ # when creating objects from Parse JSON data, don't use dirty tracking since
240
+ # we are considering these objects as "pristine"
241
+ o.apply_attributes!(json, dirty_track: false)
242
+ o.clear_changes!
243
+ else
244
+ o = Parse::Pointer.new className, (json["objectId"] || json[:objectId])
245
+ end
246
+ return o
247
+ # rescue NameError => e
248
+ # puts "Parse::Object.build constant class error: #{e}"
249
+ # rescue Exception => e
250
+ # puts "Parse::Object.build error: #{e}"
251
+ end
252
+
253
+ # Set default base properties of any Parse::Object
254
+ property :id, field: :objectId
255
+ property :created_at, :date
256
+ property :updated_at, :date
257
+ property :acl, :acl, field: :ACL
258
+
259
+ # TODO: Hack since Parse createdAt and updatedAt dates have to be returned as strings
260
+ # in UTC Zulu iso 8601 with 3 millisecond format.
261
+ def createdAt
262
+ return @created_at if Parse::Object.disable_serialized_string_date.present?
263
+ @created_at.to_time.utc.iso8601(3) if @created_at.present?
264
+ end
265
+
266
+ def updatedAt
267
+ return @updated_at if Parse::Object.disable_serialized_string_date.present?
268
+ @updated_at.to_time.utc.iso8601(3) if @updated_at.present?
269
+ end
270
+
271
+
272
+
273
+ end
274
+
275
+ # The User class provided by Parse with the required fields. You may
276
+ # add mixings to this class to add the app specific properties
277
+ class User < Parse::Object
278
+ parse_class "_User".freeze
279
+ property :auth_data, :object
280
+ property :email
281
+ property :password
282
+ property :username
283
+ end
284
+
285
+ class Installation < Parse::Object
286
+ parse_class "_Installation".freeze
287
+ property :channels, :array
288
+ property :gcm_sender_id, :string, field: :GCMSenderId
289
+ property :badge, :integer
290
+ property :installation_id
291
+ property :device_token
292
+ property :device_type
293
+ property :locale_identifier
294
+ property :push_type
295
+ property :time_zone
296
+ end
297
+
298
+ class Role < Parse::Object
299
+ parse_class "_Role".freeze
300
+ property :name
301
+
302
+ has_many :roles, through: :relation
303
+ has_many :users, through: :relation
304
+
305
+ def update_acl
306
+ acl.everyone true, false
307
+ end
308
+
309
+ before_save do
310
+ update_acl
311
+ end
312
+ end
313
+
314
+ class Session < Parse::Object
315
+ parse_class "_Session".freeze
316
+ property :created_with, :object
317
+ property :expires_at, :date
318
+ property :installation_id
319
+ property :restricted, :boolean
320
+ property :session_token
321
+
322
+ belongs_to :user
323
+ end
324
+
325
+ end
326
+
327
+
328
+ class Array
329
+ # This helper method selects all objects in an array that are either inherit from
330
+ # Parse::Pointer or are a hash. If it is a hash, a Pare::Object will be built from it
331
+ # if it constains the proper fields. Non convertible objects will be removed
332
+ # If the className is not contained or known, you can pass a table name as an argument
333
+ def parse_objects(table = nil)
334
+ map do |m|
335
+ next m if m.is_a?(Parse::Pointer)
336
+ if m.is_a?(Hash) && (m["className"] || m[:className] || table)
337
+ next Parse::Object.build m, (m["className"] || m[:className] || table)
338
+ end
339
+ nil
340
+ end.compact
341
+ end
342
+
343
+ def parse_ids
344
+ parse_objects.map { |d| d.id }
345
+ end
346
+
347
+ end
@@ -0,0 +1,106 @@
1
+ require 'active_model'
2
+ require 'active_support/inflector'
3
+ require 'active_model_serializers'
4
+ require_relative 'model'
5
+ module Parse
6
+
7
+ # A Parse Pointer is the superclass of Parse::Object types. A pointer can be considered
8
+ # a type of Parse::Object in which only the class name and id is known. In most cases,
9
+ # you may not see Parse::Pointer object be used if you have defined all your Parse::Object subclasses
10
+ # based on your Parse application tables - however they are used for when a class is found that cannot be
11
+ # associated with a defined ruby class or used when specifically saving Parse relation types.
12
+ class Pointer < Model
13
+
14
+ attr_accessor :parse_class, :id
15
+
16
+ def __type; "Pointer".freeze; end;
17
+ alias_method :className, :parse_class
18
+ # A Parse object as a className field and objectId. In ruby, we will use the
19
+ # id attribute method, but for usability, we will also alias it to objectId
20
+ alias_method :objectId, :id
21
+
22
+ def initialize(table, oid)
23
+ @parse_class = table.to_s
24
+ @id = oid.to_s
25
+ end
26
+
27
+ def parse_class
28
+ @parse_class
29
+ end
30
+
31
+ def attributes
32
+ { __type: :string, className: :string, objectId: :string}.freeze
33
+ end
34
+
35
+
36
+ def json_hash
37
+ JSON.parse to_json
38
+ end
39
+
40
+ # Create a new pointer with the current class name and id. While this may not make sense
41
+ # for a pointer instance, Parse::Object subclasses use this inherited method to turn themselves into
42
+ # pointer objects.
43
+ def pointer
44
+ Pointer.new parse_class, @id
45
+ end
46
+
47
+ # determines if an object (or subclass) is a pointer type. A pointer is determined
48
+ # if we have a parse class and an id, but no timestamps, then we probably are a pointer.
49
+ def pointer?
50
+ present? && @created_at.blank? && @updated_at.blank?
51
+ end
52
+
53
+ # boolean whether this object has data and is not a pointer. Because of some autofetching
54
+ # mechanisms, this is useful to know whether the object already has data without actually causing
55
+ # a fetch of the data.
56
+ def fetched?
57
+ present? && pointer? == false
58
+ end
59
+
60
+ # This method is a general implementation that gets overriden by Parse::Object subclass.
61
+ # Given the class name and the id, we will go to Parse and fetch the actual record, returning the
62
+ # JSON object. Note that the subclass implementation does something a bit different.
63
+ def fetch
64
+ response = client.fetch_object(parse_class, id)
65
+ return nil if response.error?
66
+ response.result
67
+ end
68
+
69
+ def ==(o)
70
+ return false unless o.is_a?(Pointer)
71
+ #only equal if the Parse class and object ID are the same.
72
+ self.parse_class == o.parse_class && id == o.id
73
+ end
74
+ alias_method :eql?, :==
75
+
76
+ def present?
77
+ parse_class.present? && @id.present?
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ # extensions
84
+ class Array
85
+ def objectIds
86
+ map { |m| m.is_?(Parse::Pointer) ? m.id : nil }.reject { |r| r.nil? }
87
+ end
88
+
89
+ def valid_parse_objects
90
+ select { |s| s.is_a?(Parse::Pointer) }
91
+ end
92
+
93
+ def parse_pointers(table = nil)
94
+ self.map do |m|
95
+ #if its an exact Parse::Pointer
96
+ if m.is_a?(Parse::Pointer) || m.respond_to?(:pointer)
97
+ next m.pointer
98
+ elsif m.is_a?(Hash) && m["className"] && m["objectId"]
99
+ next Parse::Pointer.new m["className"], m["objectId"]
100
+ elsif m.is_a?(Hash) && m[:className] && m[:objectId]
101
+ next Parse::Pointer.new m[:className], m[:objectId]
102
+ end
103
+ nil
104
+ end.compact
105
+ end
106
+ end