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.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +77 -0
- data/LICENSE +20 -0
- data/README.md +1281 -0
- data/Rakefile +12 -0
- data/bin/console +20 -0
- data/bin/server +10 -0
- data/bin/setup +7 -0
- data/lib/parse/api/all.rb +13 -0
- data/lib/parse/api/analytics.rb +16 -0
- data/lib/parse/api/apps.rb +37 -0
- data/lib/parse/api/batch.rb +148 -0
- data/lib/parse/api/cloud_functions.rb +18 -0
- data/lib/parse/api/config.rb +22 -0
- data/lib/parse/api/files.rb +21 -0
- data/lib/parse/api/hooks.rb +68 -0
- data/lib/parse/api/objects.rb +77 -0
- data/lib/parse/api/push.rb +16 -0
- data/lib/parse/api/schemas.rb +25 -0
- data/lib/parse/api/sessions.rb +11 -0
- data/lib/parse/api/users.rb +43 -0
- data/lib/parse/client.rb +225 -0
- data/lib/parse/client/authentication.rb +59 -0
- data/lib/parse/client/body_builder.rb +69 -0
- data/lib/parse/client/caching.rb +103 -0
- data/lib/parse/client/protocol.rb +15 -0
- data/lib/parse/client/request.rb +43 -0
- data/lib/parse/client/response.rb +116 -0
- data/lib/parse/model/acl.rb +182 -0
- data/lib/parse/model/associations/belongs_to.rb +121 -0
- data/lib/parse/model/associations/collection_proxy.rb +202 -0
- data/lib/parse/model/associations/has_many.rb +218 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +71 -0
- data/lib/parse/model/associations/relation_collection_proxy.rb +134 -0
- data/lib/parse/model/bytes.rb +50 -0
- data/lib/parse/model/core/actions.rb +499 -0
- data/lib/parse/model/core/properties.rb +377 -0
- data/lib/parse/model/core/querying.rb +100 -0
- data/lib/parse/model/core/schema.rb +92 -0
- data/lib/parse/model/date.rb +50 -0
- data/lib/parse/model/file.rb +127 -0
- data/lib/parse/model/geopoint.rb +98 -0
- data/lib/parse/model/model.rb +120 -0
- data/lib/parse/model/object.rb +347 -0
- data/lib/parse/model/pointer.rb +106 -0
- data/lib/parse/model/push.rb +99 -0
- data/lib/parse/query.rb +378 -0
- data/lib/parse/query/constraint.rb +130 -0
- data/lib/parse/query/constraints.rb +176 -0
- data/lib/parse/query/operation.rb +66 -0
- data/lib/parse/query/ordering.rb +49 -0
- data/lib/parse/stack.rb +11 -0
- data/lib/parse/stack/version.rb +5 -0
- data/lib/parse/webhooks.rb +228 -0
- data/lib/parse/webhooks/payload.rb +115 -0
- data/lib/parse/webhooks/registration.rb +139 -0
- data/parse-stack.gemspec +45 -0
- 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
|