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,377 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'active_model_serializers'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
=begin
|
7
|
+
This module provides support for handling all the different types of column data types
|
8
|
+
supported in Parse and mapping them between their remote names with their local ruby named attributes.
|
9
|
+
By default, the convention used for naming parameters is that the remote column should be in lower-first-camelcase, (ex. myField, eventAddress), except for
|
10
|
+
a few special columns like "id" and "acl".
|
11
|
+
Properties are defined when creating subclasses of Parse::Object and using the `property` class method.
|
12
|
+
|
13
|
+
By defining properties, dynamic methods are created in order to allow getters and setters to be used. We will go into detail below.
|
14
|
+
|
15
|
+
Each class will have a different copy of attribute mapping and field mappings.
|
16
|
+
=end
|
17
|
+
|
18
|
+
module Parse
|
19
|
+
|
20
|
+
module Properties
|
21
|
+
# This is an exception that is thrown if there is an issue when creating a specific property for a class.
|
22
|
+
class DefinitionError < Exception; end;
|
23
|
+
|
24
|
+
# These are the base types supported by Parse.
|
25
|
+
TYPES = [:id, :string, :relation, :integer, :float, :boolean, :date, :array, :file, :geopoint, :bytes, :object, :acl].freeze
|
26
|
+
# These are the base mappings of the remote field name types.
|
27
|
+
BASE = {objectId: :string, createdAt: :date, updatedAt: :date, ACL: :acl}.freeze
|
28
|
+
# The list of properties that are part of all objects
|
29
|
+
BASE_KEYS = [:id, :created_at, :updated_at].freeze
|
30
|
+
# Default hash map of local attribute name to remote column name
|
31
|
+
BASE_FIELD_MAP = {id: :objectId, created_at: :createdAt, updated_at: :updatedAt, acl: :ACL}.freeze
|
32
|
+
|
33
|
+
def self.included(base)
|
34
|
+
base.extend(ClassMethods)
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
|
39
|
+
# The fields method returns a mapping of all local attribute names and their data type.
|
40
|
+
# if type is passed, we return only the fields that matched that data type
|
41
|
+
def fields(type = nil)
|
42
|
+
@fields ||= {id: :string, created_at: :date, updated_at: :date, acl: :acl}
|
43
|
+
if type.present?
|
44
|
+
type = type.to_sym
|
45
|
+
return @fields.select { |k,v| v == type }
|
46
|
+
end
|
47
|
+
@fields
|
48
|
+
end
|
49
|
+
|
50
|
+
# This returns the mapping of local to remote attribute names.
|
51
|
+
def field_map
|
52
|
+
@field_map ||= BASE_FIELD_MAP.dup
|
53
|
+
end
|
54
|
+
|
55
|
+
# Keeps track of all the attributes supported by this class.
|
56
|
+
def attributes=(hash)
|
57
|
+
@attributes = BASE.merge(hash)
|
58
|
+
end
|
59
|
+
|
60
|
+
def attributes
|
61
|
+
@attributes ||= BASE.dup
|
62
|
+
end
|
63
|
+
|
64
|
+
# property :songs, :array
|
65
|
+
# property :my_date, :date, field: "myRemoteCOLUMNName"
|
66
|
+
# property :my_int, :integer, required: true, default: ->{ rand(10) }
|
67
|
+
|
68
|
+
# field: (literal column name in Parse)
|
69
|
+
# required: (data_type)
|
70
|
+
# default: (value or Proc)
|
71
|
+
# alias: Whether to create the remote field alias getter/setters for this attribute
|
72
|
+
# This is the class level property method to be used when declaring properties. This helps builds specific methods, formatters
|
73
|
+
# and conversion handlers for property storing and saving data for a particular parse class.
|
74
|
+
# The first parameter is the name of the local attribute you want to declare with its corresponding data type.
|
75
|
+
# Declaring a `property :my_date, :date`, would declare the attribute my_date with a corresponding remote column called
|
76
|
+
# "myDate" (lower-first-camelcase) with a Parse data type of Date.
|
77
|
+
# You can override the implicit naming behavior by passing the option :field to override.
|
78
|
+
|
79
|
+
# symbolize: Makes sure the saved and return value locally is in symbol format. useful
|
80
|
+
# for enum type fields that are string columns in Parse. Ex. a booking_status for a field
|
81
|
+
# could be either "submitted" or "completed" in Parse, however with symbolize, these would
|
82
|
+
# be available as :submitted or :completed.
|
83
|
+
def property(key, data_type = :string, opts = {})
|
84
|
+
|
85
|
+
key = key.to_sym
|
86
|
+
|
87
|
+
if data_type.is_a?(Hash)
|
88
|
+
opts.merge!(data_type)
|
89
|
+
data_type = :string
|
90
|
+
end
|
91
|
+
# set defaults
|
92
|
+
opts = { required: false,
|
93
|
+
alias: true,
|
94
|
+
symbolize: false,
|
95
|
+
field: key.to_s.camelize(:lower)
|
96
|
+
}.merge( opts )
|
97
|
+
#By default, the remote field name is a lower-first-camelcase version of the key
|
98
|
+
# it can be overriden by the :field parameter
|
99
|
+
parse_field = opts[:field].to_sym
|
100
|
+
if self.fields[key].present? && BASE_FIELD_MAP[key].nil?
|
101
|
+
raise DefinitionError, "Property #{self}##{key} already defined with data type #{data_type}"
|
102
|
+
end
|
103
|
+
# We keep the list of fields that are on the remote Parse store
|
104
|
+
if self.fields[parse_field].present?
|
105
|
+
raise DefinitionError, "Alias property #{self}##{parse_field} conflicts with previously defined property."
|
106
|
+
end
|
107
|
+
#dirty tracking. It is declared to use with ActiveModel DirtyTracking
|
108
|
+
define_attribute_methods key
|
109
|
+
|
110
|
+
# this hash keeps list of attributes (based on remote fields) and their data types
|
111
|
+
self.attributes.merge!( parse_field => data_type )
|
112
|
+
# this maps all the possible attribute fields and their data types. We use both local
|
113
|
+
# keys and remote keys because when we receive a remote object that has the remote field name
|
114
|
+
# we need to know what the data type conversion should be.
|
115
|
+
self.fields.merge!( key => data_type, parse_field => data_type )
|
116
|
+
# This creates a mapping between the local field and the remote field name.
|
117
|
+
self.field_map.merge!( key => parse_field )
|
118
|
+
#puts "Current Self: #{self} - #{key} = #{self.attributes}"
|
119
|
+
# if the field is marked as required, then add validations
|
120
|
+
if opts[:required]
|
121
|
+
# if integer or float, validate that it's a number
|
122
|
+
if data_type == :integer || data_type == :float
|
123
|
+
validates_numericality_of key
|
124
|
+
end
|
125
|
+
# validate that it is not empty
|
126
|
+
validates_presence_of key
|
127
|
+
end
|
128
|
+
|
129
|
+
# get the default value if provided (or Proc)
|
130
|
+
default_value = opts[:default]
|
131
|
+
symbolize_value = opts[:symbolize]
|
132
|
+
#only support symbolization of string data types
|
133
|
+
if symbolize_value && data_type != :string
|
134
|
+
raise 'Symbolization is only supported on :string data types.'
|
135
|
+
end
|
136
|
+
|
137
|
+
# Here is the where the 'magic' begins. For each property defined, we will
|
138
|
+
# generate special setters and getters that will take advantage of ActiveModel
|
139
|
+
# helpers.
|
140
|
+
|
141
|
+
# We define a getter with the key
|
142
|
+
define_method(key) do
|
143
|
+
|
144
|
+
# we will get the value using the internal value of the instance variable
|
145
|
+
# using the instance_variable_get
|
146
|
+
ivar = :"@#{key}"
|
147
|
+
value = instance_variable_get ivar
|
148
|
+
|
149
|
+
# If the value is nil and this current Parse::Object instance is a pointer?
|
150
|
+
# then someone is calling the getter for this, which means they probably want
|
151
|
+
# its value - so let's go turn this pointer into a full object record
|
152
|
+
if value.nil? && pointer?
|
153
|
+
# call autofetch to fetch the entire record
|
154
|
+
# and then get the ivar again cause it might have been updated.
|
155
|
+
autofetch!(key)
|
156
|
+
value = instance_variable_get ivar
|
157
|
+
end
|
158
|
+
|
159
|
+
# if value is nil (even after fetching), then lets see if the developer
|
160
|
+
# set a default value for this attribute.
|
161
|
+
if value.nil? && default_value.present?
|
162
|
+
# If the default object provided is a Proc, then run the proc, otherwise
|
163
|
+
# we'll assume it's just a plain literal value
|
164
|
+
value = default_value.is_a?(Proc) ? default_value.call : default_value
|
165
|
+
# lets set the variable with the updated value
|
166
|
+
instance_variable_set ivar, value
|
167
|
+
end
|
168
|
+
|
169
|
+
# if the value is a String (like an iso8601 date) and the data type of
|
170
|
+
# this object is :date, then let's be nice and create a parse date for it.
|
171
|
+
if value.is_a?(String) && data_type == :date
|
172
|
+
value = Parse::Date.parse value
|
173
|
+
instance_variable_set ivar, value
|
174
|
+
end
|
175
|
+
# finally return the value
|
176
|
+
symbolize_value && value.respond_to?(:to_sym) ? value.to_sym : value
|
177
|
+
end
|
178
|
+
|
179
|
+
# The second method to be defined is a setter method. This is done by
|
180
|
+
# defining :key with a '=' sign. However, to support setting the attribute
|
181
|
+
# with and without dirty tracking, we really will just proxy it to another method
|
182
|
+
|
183
|
+
define_method("#{key}=") do |val|
|
184
|
+
#we proxy the method passing the value and true. Passing true to the
|
185
|
+
# method tells it to make sure dirty tracking is enabled.
|
186
|
+
self.send "#{key}_set_attribute!", val, true
|
187
|
+
end
|
188
|
+
|
189
|
+
# This is the real setter method. Takes two arguments, the value to set
|
190
|
+
# and whether to mark it as dirty tracked.
|
191
|
+
define_method("#{key}_set_attribute!") do |val, track = true|
|
192
|
+
# Each value has a data type, based on that we can treat the incoming
|
193
|
+
# value as input, and format it to the correct storage format. This method is
|
194
|
+
# defined in this file (instance method)
|
195
|
+
val = format_value(key, val, data_type)
|
196
|
+
# if dirty trackin is enabled, call the ActiveModel required method of _will_change!
|
197
|
+
# this will grab the current value and keep a copy of it - but we only do this if
|
198
|
+
# the new value being set is different from the current value stored.
|
199
|
+
if track == true
|
200
|
+
send :"#{key}_will_change!" unless val == instance_variable_get( :"@#{key}" )
|
201
|
+
end
|
202
|
+
if symbolize_value && data_type == :string
|
203
|
+
val = nil if val.blank?
|
204
|
+
val = val.to_sym if val.respond_to?(:to_sym)
|
205
|
+
end
|
206
|
+
# now set the instance value
|
207
|
+
instance_variable_set :"@#{key}", val
|
208
|
+
end
|
209
|
+
|
210
|
+
# The core methods above support all attributes with the base local :key parameter
|
211
|
+
# however, for ease of use and to handle that the incoming fields from parse have different
|
212
|
+
# names, we will alias all those methods defined above with the defined parse_field.
|
213
|
+
# if both the local name matches the calculated/provided remote column name, don't create
|
214
|
+
# an alias method since it is the same thing. Ex. attribute 'username' would probably have the
|
215
|
+
# remote column name also called 'username'.
|
216
|
+
return if parse_field == key
|
217
|
+
|
218
|
+
# we will now create the aliases, however if the method is already defined
|
219
|
+
# we warn the user unless the field is :objectId since we are in charge of that one.
|
220
|
+
# this is because it is possible they want to override. You can turn off this
|
221
|
+
# behavior by passing false to :alias
|
222
|
+
|
223
|
+
if self.method_defined?(parse_field) == false && opts[:alias]
|
224
|
+
alias_method parse_field, key
|
225
|
+
alias_method "#{parse_field}=", "#{key}="
|
226
|
+
alias_method "#{parse_field}_set_attribute!", "#{key}_set_attribute!"
|
227
|
+
elsif parse_field.to_sym != :objectId
|
228
|
+
warn "Alias property method #{self}##{parse_field} already defined."
|
229
|
+
end
|
230
|
+
|
231
|
+
end # property
|
232
|
+
|
233
|
+
end #ClassMethods
|
234
|
+
|
235
|
+
# returns the class level stored field map
|
236
|
+
def field_map
|
237
|
+
self.class.field_map
|
238
|
+
end
|
239
|
+
|
240
|
+
# returns the list of fields
|
241
|
+
def fields(type = nil)
|
242
|
+
self.class.fields(type)
|
243
|
+
end
|
244
|
+
|
245
|
+
def attributes
|
246
|
+
{__type: :string, :className => :string}.merge!(self.class.attributes)
|
247
|
+
end
|
248
|
+
|
249
|
+
# support for setting a hash of attributes on the object with a given dirty tracking value
|
250
|
+
# if dirty_track: is set to false (default), attributes are set without dirty tracking.
|
251
|
+
def apply_attributes!(hash, dirty_track: false)
|
252
|
+
return unless hash.is_a?(Hash)
|
253
|
+
|
254
|
+
@id ||= hash["id"] || hash["objectId"]
|
255
|
+
hash.each do |key, value|
|
256
|
+
method = "#{key}_set_attribute!"
|
257
|
+
send(method, value, dirty_track) if respond_to?( method )
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# applies a hash of attributes overriding any current value the object has for those
|
262
|
+
# attributes
|
263
|
+
def attributes=(hash)
|
264
|
+
return unless hash.is_a?(Hash)
|
265
|
+
# - [:id, :objectId]
|
266
|
+
# only overwrite @id if it hasn't been set.
|
267
|
+
apply_attributes!(hash, dirty_track: true)
|
268
|
+
end
|
269
|
+
|
270
|
+
# returns a hash of attributes (and their new values) that had been changed.
|
271
|
+
# This will not include any of the base attributes (ex. id, created_at, etc)
|
272
|
+
# If true is passed as an argument, then all attributes will be included.
|
273
|
+
# This method is useful for generating an update hash for the Parse PUT API
|
274
|
+
# TODO: Replace this algorithm with reduce()
|
275
|
+
def attribute_updates(include_all = false)
|
276
|
+
h = {}
|
277
|
+
changed.each do |key|
|
278
|
+
key = key.to_sym
|
279
|
+
next if include_all == false && Parse::Properties::BASE_KEYS.include?(key)
|
280
|
+
next unless fields[key].present?
|
281
|
+
remote_field = self.field_map[key] || key
|
282
|
+
h[remote_field] = send key
|
283
|
+
h[remote_field] = {__op: :Delete} if h[remote_field].nil?
|
284
|
+
# in the case that the field is a Parse object, generate a pointer
|
285
|
+
h[remote_field] = h[remote_field].pointer if h[remote_field].respond_to?(:pointer)
|
286
|
+
end
|
287
|
+
h
|
288
|
+
end
|
289
|
+
|
290
|
+
# determines if any of the attributes have changed.
|
291
|
+
def attribute_changes?
|
292
|
+
changed.any? do |key|
|
293
|
+
fields[key.to_sym].present?
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def format_operation(key, val, data_type)
|
298
|
+
return val unless val.is_a?(Hash) && val["__op"].present?
|
299
|
+
op = val["__op"]
|
300
|
+
#handles delete case otherwise 'null' shows up in column
|
301
|
+
if "Delete" == op
|
302
|
+
val = nil
|
303
|
+
elsif "Add" == op && data_type == :array
|
304
|
+
val = (instance_variable_get(:"@#{key}") || []).to_a + (val["objects"] || [])
|
305
|
+
elsif "Remove" == op && data_type == :array
|
306
|
+
val = (instance_variable_get(:"@#{key}") || []).to_a - (val["objects"] || [])
|
307
|
+
elsif "AddUnique" == op && data_type == :array
|
308
|
+
objects = (val["objects"] || []).uniq
|
309
|
+
original_items = (instance_variable_get(:"@#{key}") || []).to_a
|
310
|
+
objects.reject! { |r| original_items.include?(r) }
|
311
|
+
val = original_items + objects
|
312
|
+
elsif "Increment" == op && data_type == :integer || data_type == :integer
|
313
|
+
# for operations that increment by a certain amount, they come as a hash
|
314
|
+
val = (instance_variable_get(:"@#{key}") || 0) + (val["amount"] || 0).to_i
|
315
|
+
end
|
316
|
+
val
|
317
|
+
end
|
318
|
+
|
319
|
+
# this method takes an input value and transforms it to the proper local format
|
320
|
+
# depending on the data type that was set for a particular property key.
|
321
|
+
def format_value(key, val, data_type = nil)
|
322
|
+
# if data_type wasn't passed, then get the data_type from the fields hash
|
323
|
+
data_type ||= self.fields[key]
|
324
|
+
|
325
|
+
val = format_operation(key, val, data_type)
|
326
|
+
|
327
|
+
case data_type
|
328
|
+
when :object
|
329
|
+
val = val #should be regular hash, maybe in the future we return hashie?
|
330
|
+
when :array
|
331
|
+
# All "array" types use a collection proxy
|
332
|
+
val = [val] unless val.is_a?(Array) #all objects must be in array form
|
333
|
+
val.compact! #remove any nil
|
334
|
+
val = Parse::CollectionProxy.new val, delegate: self, key: key
|
335
|
+
when :geopoint
|
336
|
+
val = Parse::GeoPoint.new(val) unless val.blank?
|
337
|
+
when :file
|
338
|
+
val = Parse::File.new(val) unless val.blank?
|
339
|
+
when :bytes
|
340
|
+
val = Parse::Bytes.new(val) unless val.blank?
|
341
|
+
when :integer
|
342
|
+
val = val.to_i unless val.blank?
|
343
|
+
when :boolean
|
344
|
+
val = val ? true : false
|
345
|
+
when :string
|
346
|
+
val = val.to_s unless val.blank?
|
347
|
+
when :float
|
348
|
+
val = val.to_f unless val.blank?
|
349
|
+
when :acl
|
350
|
+
# ACL types go through a special conversion
|
351
|
+
val = ACL.typecast(val, self)
|
352
|
+
when :date
|
353
|
+
# if it respond to parse_date, then use that as the conversion.
|
354
|
+
if val.respond_to?(:parse_date)
|
355
|
+
val = val.parse_date
|
356
|
+
# if the value is a hash, then it may be the Parse hash format for an iso date.
|
357
|
+
elsif val.is_a?(Hash) # val.respond_to?(:iso8601)
|
358
|
+
val = Parse::Date.parse(val["iso".freeze] || val[:iso])
|
359
|
+
elsif val.is_a?(String)
|
360
|
+
# if it's a string, try parsing the date
|
361
|
+
val = Parse::Date.parse val
|
362
|
+
end
|
363
|
+
else
|
364
|
+
# You can provide a specific class instead of a symbol format
|
365
|
+
if data_type.respond_to?(:typecast)
|
366
|
+
val = data_type.typecast(val)
|
367
|
+
else
|
368
|
+
warn "Property :#{key}: :#{data_type} has not valid data type"
|
369
|
+
val = val #default
|
370
|
+
end
|
371
|
+
end
|
372
|
+
val
|
373
|
+
end
|
374
|
+
|
375
|
+
end # Properties
|
376
|
+
|
377
|
+
end # Parse
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require_relative '../../query'
|
2
|
+
|
3
|
+
# This module provides most of the querying methods for Parse Objects.
|
4
|
+
# It proxies much of the query methods to the Parse::Query object.
|
5
|
+
module Parse
|
6
|
+
|
7
|
+
module Querying
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
# This query method helper returns a Query object tied to a parse class.
|
16
|
+
# The parse class should be the name of the one that will be sent in the query
|
17
|
+
# request pointing to the remote table.
|
18
|
+
def query(constraints = {})
|
19
|
+
Parse::Query.new self.parse_class, constraints
|
20
|
+
end
|
21
|
+
|
22
|
+
def where(clauses = {})
|
23
|
+
query.where(clauses)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Most common method to use when querying a class. This takes a hash of constraints
|
27
|
+
# and conditions and returns the results.
|
28
|
+
def all(constraints = {})
|
29
|
+
constraints = {limit: :max}.merge(constraints)
|
30
|
+
prepared_query = query(constraints)
|
31
|
+
return prepared_query.results(&Proc.new) if block_given?
|
32
|
+
prepared_query.results
|
33
|
+
end
|
34
|
+
|
35
|
+
# returns the first item matching the constraint. If constraint parameter is numeric,
|
36
|
+
# then we treat it as a count.
|
37
|
+
# Ex. Object.first( :name => "Anthony" ) (returns single object)
|
38
|
+
# Ex. Object.first(3) # first 3 objects (array of 3 objects)
|
39
|
+
def first(constraints = {})
|
40
|
+
fetch_count = 1
|
41
|
+
if constraints.is_a?(Numeric)
|
42
|
+
fetch_count = constraints.to_i
|
43
|
+
constraints = {}
|
44
|
+
end
|
45
|
+
constraints.merge!( {limit: fetch_count} )
|
46
|
+
res = query(constraints).results
|
47
|
+
return res.first if fetch_count == 1
|
48
|
+
return res.first fetch_count
|
49
|
+
end
|
50
|
+
|
51
|
+
# creates a count request (which is more performant when counting objects)
|
52
|
+
def count(constraints = {})
|
53
|
+
query(constraints).count
|
54
|
+
end
|
55
|
+
|
56
|
+
# Find objects based on objectIds. The result is a list (or single item) of the
|
57
|
+
# objects that were successfully found.
|
58
|
+
# Example:
|
59
|
+
# Object.find "<objectId>"
|
60
|
+
# Object.find "<objectId>", "<objectId>"....
|
61
|
+
# Object.find ["<objectId>", "<objectId>"]
|
62
|
+
# Additional named parameters:
|
63
|
+
# type: - :parrallel by default - makes all find requests in parallel vs serial.
|
64
|
+
# :batch - makes a single query request for all objects with a "contained in" query.
|
65
|
+
# compact: - true by default, removes any nil values from the array as it is potential
|
66
|
+
# that an object with a specified ID does not exist.
|
67
|
+
|
68
|
+
def find(*parse_ids, type: :parallel, compact: true)
|
69
|
+
# flatten the list of Object ids.
|
70
|
+
parse_ids.flatten!
|
71
|
+
# determines if the result back to the call site is an array or a single result
|
72
|
+
as_array = parse_ids.count > 1
|
73
|
+
results = []
|
74
|
+
|
75
|
+
if type == :batch
|
76
|
+
# use a .in query with the given id as a list
|
77
|
+
results = self.class.all(:id.in => parse_ids)
|
78
|
+
else
|
79
|
+
# use Parallel to make multiple threaded requests for finding these objects.
|
80
|
+
# The benefit of using this as default is that each request goes to a specific URL
|
81
|
+
# which is better than Query request (table scan). This in turn allows for caching of
|
82
|
+
# individual objects.
|
83
|
+
results = parse_ids.threaded_map do |parse_id|
|
84
|
+
response = client.fetch_object(parse_class, parse_id)
|
85
|
+
next nil if response.error?
|
86
|
+
Parse::Object.build response.result, parse_class
|
87
|
+
end
|
88
|
+
end
|
89
|
+
# removes any nil items in the array
|
90
|
+
results.compact! if compact
|
91
|
+
|
92
|
+
as_array ? results : results.first
|
93
|
+
end; alias_method :get, :find
|
94
|
+
|
95
|
+
end # ClassMethods
|
96
|
+
|
97
|
+
end # Querying
|
98
|
+
|
99
|
+
|
100
|
+
end
|