parse-stack 1.5.3 → 1.6.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 +4 -4
- data/.github/parse-ruby-sdk.png +0 -0
- data/Changes.md +25 -1
- data/Gemfile.lock +4 -4
- data/README.md +37 -31
- data/bin/console +3 -0
- data/lib/parse/api/all.rb +2 -1
- data/lib/parse/api/apps.rb +12 -0
- data/lib/parse/api/config.rb +5 -1
- data/lib/parse/api/files.rb +1 -0
- data/lib/parse/api/hooks.rb +1 -0
- data/lib/parse/api/objects.rb +4 -1
- data/lib/parse/api/push.rb +1 -0
- data/lib/parse/api/{schemas.rb → schema.rb} +7 -0
- data/lib/parse/api/server.rb +44 -0
- data/lib/parse/api/sessions.rb +1 -0
- data/lib/parse/api/users.rb +4 -1
- data/lib/parse/client.rb +109 -73
- data/lib/parse/client/authentication.rb +2 -1
- data/lib/parse/client/batch.rb +9 -1
- data/lib/parse/client/body_builder.rb +16 -1
- data/lib/parse/client/caching.rb +15 -13
- data/lib/parse/client/protocol.rb +27 -15
- data/lib/parse/client/response.rb +26 -8
- data/lib/parse/model/acl.rb +1 -1
- data/lib/parse/model/associations/belongs_to.rb +18 -19
- data/lib/parse/model/associations/collection_proxy.rb +6 -0
- data/lib/parse/model/associations/has_many.rb +5 -6
- data/lib/parse/model/bytes.rb +4 -1
- data/lib/parse/model/classes/user.rb +46 -44
- data/lib/parse/model/core/actions.rb +508 -460
- data/lib/parse/model/core/builder.rb +75 -0
- data/lib/parse/model/core/errors.rb +9 -0
- data/lib/parse/model/core/fetching.rb +42 -38
- data/lib/parse/model/core/properties.rb +46 -27
- data/lib/parse/model/core/querying.rb +231 -228
- data/lib/parse/model/core/schema.rb +76 -74
- data/lib/parse/model/date.rb +10 -2
- data/lib/parse/model/file.rb +16 -2
- data/lib/parse/model/geopoint.rb +9 -2
- data/lib/parse/model/model.rb +38 -7
- data/lib/parse/model/object.rb +60 -19
- data/lib/parse/model/pointer.rb +22 -1
- data/lib/parse/model/push.rb +6 -2
- data/lib/parse/query.rb +57 -11
- data/lib/parse/query/constraint.rb +5 -2
- data/lib/parse/query/constraints.rb +588 -589
- data/lib/parse/query/ordering.rb +2 -2
- data/lib/parse/stack.rb +1 -0
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/webhooks.rb +30 -29
- data/lib/parse/webhooks/payload.rb +181 -168
- data/lib/parse/webhooks/registration.rb +1 -1
- data/parse-stack.gemspec +9 -9
- metadata +9 -12
@@ -0,0 +1,75 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/inflector'
|
6
|
+
require 'active_support/core_ext'
|
7
|
+
require_relative '../object'
|
8
|
+
|
9
|
+
module Parse
|
10
|
+
# Create all Parse::Object subclasses, including their properties and inferred
|
11
|
+
# associations by importing the schema for the remote collections in a Parse
|
12
|
+
# application. Uses the default configured client.
|
13
|
+
# @return [Array] an array of created Parse::Object subclasses.
|
14
|
+
# @see Parse::Model::Builder.build!
|
15
|
+
def self.auto_generate_models!
|
16
|
+
Parse.schemas.map do |schema|
|
17
|
+
Parse::Model::Builder.build!(schema)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Model
|
22
|
+
# This class provides a method to automatically generate Parse::Object subclasses, including
|
23
|
+
# their properties and inferred associations by importing the schema for the remote collections
|
24
|
+
# in a Parse application.
|
25
|
+
class Builder
|
26
|
+
|
27
|
+
# Builds a ruby Parse::Object subclass with the provided schema information.
|
28
|
+
# @param schema [Hash] the Parse-formatted hash schema for a collection. This hash
|
29
|
+
# should two keys:
|
30
|
+
# * className: Contains the name of the collection.
|
31
|
+
# * field: A hash containg the column fields and their type.
|
32
|
+
# @raise ArgumentError when the className could not be inferred from the schema.
|
33
|
+
# @return [Array] an array of Parse::Object subclass constants.
|
34
|
+
def self.build!(schema)
|
35
|
+
unless schema.is_a?(Hash)
|
36
|
+
raise ArgumentError, "Schema parameter should be a Parse schema hash object."
|
37
|
+
end
|
38
|
+
schema = schema.with_indifferent_access
|
39
|
+
fields = schema[:fields] || {}
|
40
|
+
className = schema[:className]
|
41
|
+
|
42
|
+
if className.blank?
|
43
|
+
raise ArgumentError, "No valid className provided for schema hash"
|
44
|
+
end
|
45
|
+
|
46
|
+
begin
|
47
|
+
klass = Parse::Model.find_class className
|
48
|
+
klass = ::Object.const_get(className.to_parse_class) if klass.nil?
|
49
|
+
rescue => e
|
50
|
+
klass = ::Class.new(Parse::Object)
|
51
|
+
::Object.const_set(className, klass)
|
52
|
+
end
|
53
|
+
|
54
|
+
base_fields = Parse::Properties::BASE.keys
|
55
|
+
class_fields = klass.field_map.values + [:className]
|
56
|
+
fields.each do |field, type|
|
57
|
+
field = field.to_sym
|
58
|
+
key = field.to_s.underscore.to_sym
|
59
|
+
next if base_fields.include?(field) || class_fields.include?(field)
|
60
|
+
|
61
|
+
data_type = type[:type].downcase.to_sym
|
62
|
+
if data_type == :pointer
|
63
|
+
klass.belongs_to key, as: type[:targetClass], field: field
|
64
|
+
elsif data_type == :relation
|
65
|
+
klass.has_many key, as: type[:targetClass], field: field
|
66
|
+
else
|
67
|
+
klass.property key, data_type, field: field
|
68
|
+
end
|
69
|
+
class_fields.push(field)
|
70
|
+
end
|
71
|
+
klass
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -1,54 +1,58 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
1
3
|
|
2
4
|
require 'time'
|
3
5
|
require 'parallel'
|
4
6
|
|
5
7
|
module Parse
|
6
|
-
#
|
7
|
-
module
|
8
|
+
# Combines a set of core functionality for {Parse::Object} and its subclasses.
|
9
|
+
module Core
|
10
|
+
# Defines the record fetching interface for instances of Parse::Object.
|
11
|
+
module Fetching
|
12
|
+
|
13
|
+
# Force fetches and updates the current object with the data contained in the Parse collection.
|
14
|
+
# The changes applied to the object are not dirty tracked.
|
15
|
+
# @param opts [Hash] a set of options to pass to the client request.
|
16
|
+
# @return [self] the current object, useful for chaining.
|
17
|
+
def fetch!(opts = {})
|
18
|
+
response = client.fetch_object(parse_class, id, opts)
|
19
|
+
if response.error?
|
20
|
+
puts "[Fetch Error] #{response.code}: #{response.error}"
|
21
|
+
end
|
22
|
+
# take the result hash and apply it to the attributes.
|
23
|
+
apply_attributes!(response.result, dirty_track: false)
|
24
|
+
clear_changes!
|
25
|
+
self
|
26
|
+
end
|
8
27
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
if response.error?
|
16
|
-
puts "[Fetch Error] #{response.code}: #{response.error}"
|
28
|
+
# Fetches the object from the Parse data store if the object is in a Pointer
|
29
|
+
# state. This is similar to the `fetchIfNeeded` action in the standard Parse client SDK.
|
30
|
+
# @return [self] the current object.
|
31
|
+
def fetch
|
32
|
+
# if it is a pointer, then let's go fetch the rest of the content
|
33
|
+
pointer? ? fetch! : self
|
17
34
|
end
|
18
|
-
# take the result hash and apply it to the attributes.
|
19
|
-
apply_attributes!(response.result, dirty_track: false)
|
20
|
-
clear_changes!
|
21
|
-
self
|
22
|
-
end
|
23
35
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
#
|
29
|
-
|
30
|
-
|
36
|
+
# Autofetches the object based on a key that is not part {Parse::Properties::BASE_KEYS}.
|
37
|
+
# If the key is not a Parse standard key, and the current object is in a
|
38
|
+
# Pointer state, then fetch the data related to this record from the Parse
|
39
|
+
# data store.
|
40
|
+
# @param key [String] the name of the attribute being accessed.
|
41
|
+
# @return [Boolean]
|
42
|
+
def autofetch!(key)
|
43
|
+
key = key.to_sym
|
44
|
+
@fetch_lock ||= false
|
45
|
+
if @fetch_lock != true && pointer? && key != :acl && Parse::Properties::BASE_KEYS.include?(key) == false && respond_to?(:fetch)
|
46
|
+
#puts "AutoFetching Triggerd by: #{self.class}.#{key} (#{id})"
|
47
|
+
@fetch_lock = true
|
48
|
+
send :fetch
|
49
|
+
@fetch_lock = false
|
50
|
+
end
|
31
51
|
|
32
|
-
# Autofetches the object based on a key that is not part {Parse::Properties::BASE_KEYS}.
|
33
|
-
# If the key is not a Parse standard key, and the current object is in a
|
34
|
-
# Pointer state, then fetch the data related to this record from the Parse
|
35
|
-
# data store.
|
36
|
-
# @param key [String] the name of the attribute being accessed.
|
37
|
-
# @return [Boolean]
|
38
|
-
def autofetch!(key)
|
39
|
-
key = key.to_sym
|
40
|
-
@fetch_lock ||= false
|
41
|
-
if @fetch_lock != true && pointer? && key != :acl && Parse::Properties::BASE_KEYS.include?(key) == false && respond_to?(:fetch)
|
42
|
-
#puts "AutoFetching Triggerd by: #{self.class}.#{key} (#{id})"
|
43
|
-
@fetch_lock = true
|
44
|
-
send :fetch
|
45
|
-
@fetch_lock = false
|
46
52
|
end
|
47
53
|
|
48
54
|
end
|
49
|
-
|
50
55
|
end
|
51
|
-
|
52
56
|
end
|
53
57
|
|
54
58
|
|
@@ -19,10 +19,6 @@ module Parse
|
|
19
19
|
# This module provides support for handling all the different types of column data types
|
20
20
|
# supported in Parse and mapping them between their remote names with their local ruby named attributes.
|
21
21
|
module Properties
|
22
|
-
# This is an exception that is thrown if there is an issue when creating a specific property for a class.
|
23
|
-
class DefinitionError < StandardError; end;
|
24
|
-
class ValueError < StandardError; end;
|
25
|
-
|
26
22
|
# These are the base types supported by Parse.
|
27
23
|
TYPES = [:id, :string, :relation, :integer, :float, :boolean, :date, :array, :file, :geopoint, :bytes, :object, :acl].freeze
|
28
24
|
# These are the base mappings of the remote field name types.
|
@@ -31,13 +27,15 @@ module Parse
|
|
31
27
|
BASE_KEYS = [:id, :created_at, :updated_at].freeze
|
32
28
|
# Default hash map of local attribute name to remote column name
|
33
29
|
BASE_FIELD_MAP = {id: :objectId, created_at: :createdAt, updated_at: :updatedAt, acl: :ACL}.freeze
|
34
|
-
|
30
|
+
# The delete operation hash.
|
35
31
|
DELETE_OP = {"__op"=>"Delete"}.freeze
|
36
32
|
|
33
|
+
# @!visibility private
|
37
34
|
def self.included(base)
|
38
35
|
base.extend(ClassMethods)
|
39
36
|
end
|
40
37
|
|
38
|
+
# The class methods added to Parse::Objects
|
41
39
|
module ClassMethods
|
42
40
|
|
43
41
|
# The fields method returns a mapping of all local attribute names and their data type.
|
@@ -64,7 +62,8 @@ module Parse
|
|
64
62
|
@enums ||= {}
|
65
63
|
end
|
66
64
|
|
67
|
-
#
|
65
|
+
# Set the property fields for this class.
|
66
|
+
# @return [Hash]
|
68
67
|
def attributes=(hash)
|
69
68
|
@attributes = BASE.merge(hash)
|
70
69
|
end
|
@@ -74,6 +73,7 @@ module Parse
|
|
74
73
|
@attributes ||= BASE.dup
|
75
74
|
end
|
76
75
|
|
76
|
+
# @return [Array] the list of fields that have defaults.
|
77
77
|
def defaults_list
|
78
78
|
@defaults_list ||= []
|
79
79
|
end
|
@@ -111,7 +111,8 @@ module Parse
|
|
111
111
|
|
112
112
|
# allow :bool for :boolean
|
113
113
|
data_type = :boolean if data_type == :bool
|
114
|
-
data_type = :
|
114
|
+
data_type = :geopoint if data_type == :geo_point
|
115
|
+
data_type = :integer if data_type == :int || data_type == :number
|
115
116
|
|
116
117
|
# set defaults
|
117
118
|
opts = { required: false,
|
@@ -127,11 +128,11 @@ module Parse
|
|
127
128
|
# it can be overriden by the :field parameter
|
128
129
|
parse_field = opts[:field].to_sym
|
129
130
|
if self.fields[key].present? && BASE_FIELD_MAP[key].nil?
|
130
|
-
raise
|
131
|
+
raise ArgumentError, "Property #{self}##{key} already defined with data type #{data_type}"
|
131
132
|
end
|
132
133
|
# We keep the list of fields that are on the remote Parse store
|
133
134
|
if self.fields[parse_field].present?
|
134
|
-
raise
|
135
|
+
raise ArgumentError, "Alias property #{self}##{parse_field} conflicts with previously defined property."
|
135
136
|
end
|
136
137
|
#dirty tracking. It is declared to use with ActiveModel DirtyTracking
|
137
138
|
define_attribute_methods key
|
@@ -161,12 +162,12 @@ module Parse
|
|
161
162
|
if is_enum_type
|
162
163
|
|
163
164
|
unless data_type == :string
|
164
|
-
raise
|
165
|
+
raise ArgumentError, "Property #{self}##{parse_field} :enum option is only supported on :string data types."
|
165
166
|
end
|
166
167
|
|
167
168
|
enum_values = opts[:enum]
|
168
169
|
unless enum_values.is_a?(Array) && enum_values.empty? == false
|
169
|
-
raise
|
170
|
+
raise ArgumentError, "Property #{self}##{parse_field} :enum option must be an Array type of symbols."
|
170
171
|
end
|
171
172
|
opts[:symbolize] = true
|
172
173
|
|
@@ -181,11 +182,11 @@ module Parse
|
|
181
182
|
# If the passed value is true, the methods are prefixed/suffixed with the name of the enum. It is also possible to supply a custom value:
|
182
183
|
prefix = opts[:_prefix]
|
183
184
|
unless opts[:_prefix].nil? || prefix.is_a?(Symbol) || prefix.is_a?(String)
|
184
|
-
raise
|
185
|
+
raise ArgumentError, "Enumeration option :_prefix must either be a symbol or string for #{self}##{key}."
|
185
186
|
end
|
186
187
|
|
187
188
|
unless opts[:_suffix].is_a?(TrueClass) || opts[:_suffix].is_a?(FalseClass)
|
188
|
-
raise
|
189
|
+
raise ArgumentError, "Enumeration option :_suffix must either be true or false for #{self}##{key}."
|
189
190
|
end
|
190
191
|
|
191
192
|
add_suffix = opts[:_suffix] == true
|
@@ -193,7 +194,7 @@ module Parse
|
|
193
194
|
|
194
195
|
class_method_name = prefix_or_key.to_s.pluralize.to_sym
|
195
196
|
if singleton_class.method_defined?(class_method_name)
|
196
|
-
raise
|
197
|
+
raise ArgumentError, "You tried to define an enum named `#{key}` for #{self} " + \
|
197
198
|
"but this will generate a method `#{self}.#{class_method_name}` " + \
|
198
199
|
" which is already defined. Try using :_suffix or :_prefix options."
|
199
200
|
end
|
@@ -229,7 +230,7 @@ module Parse
|
|
229
230
|
|
230
231
|
#only support symbolization of string data types
|
231
232
|
if symbolize_value && (data_type == :string || data_type == :array) == false
|
232
|
-
raise
|
233
|
+
raise ArgumentError, "Tried to symbolize #{self}##{key}, but it is only supported on :string or :array data types."
|
233
234
|
end
|
234
235
|
|
235
236
|
# Here is the where the 'magic' begins. For each property defined, we will
|
@@ -300,7 +301,7 @@ module Parse
|
|
300
301
|
# support question mark methods for boolean
|
301
302
|
if data_type == :boolean
|
302
303
|
if self.method_defined?("#{key}?")
|
303
|
-
puts "Creating boolean helper :#{key}?. Will overwrite existing method #{self}##{key}
|
304
|
+
puts "Creating boolean helper :#{key}?. Will overwrite existing method #{self}##{key}?."
|
304
305
|
end
|
305
306
|
|
306
307
|
# returns true if set to true, false otherwise
|
@@ -406,23 +407,28 @@ module Parse
|
|
406
407
|
|
407
408
|
end #ClassMethods
|
408
409
|
|
409
|
-
#
|
410
|
+
# @return [Hash] a hash mapping of all property fields and their types.
|
410
411
|
def field_map
|
411
412
|
self.class.field_map
|
412
413
|
end
|
413
414
|
|
414
|
-
# returns the list of fields
|
415
|
+
# @return returns the list of fields
|
415
416
|
def fields(type = nil)
|
416
417
|
self.class.fields(type)
|
417
418
|
end
|
418
419
|
|
419
420
|
# TODO: We can optimize
|
421
|
+
# @return [Hash] returns the list of property attributes for this class.
|
420
422
|
def attributes
|
421
423
|
{__type: :string, :className => :string}.merge!(self.class.attributes)
|
422
424
|
end
|
423
425
|
|
424
426
|
# support for setting a hash of attributes on the object with a given dirty tracking value
|
425
427
|
# if dirty_track: is set to false (default), attributes are set without dirty tracking.
|
428
|
+
# Allos mass assignment of properties with a provided hash.
|
429
|
+
# @param hash [Hash] the hash matching the property field names.
|
430
|
+
# @param dirty_track [Boolean] whether dirty tracking be enabled
|
431
|
+
# @return [Hash]
|
426
432
|
def apply_attributes!(hash, dirty_track: false)
|
427
433
|
return unless hash.is_a?(Hash)
|
428
434
|
|
@@ -433,8 +439,8 @@ module Parse
|
|
433
439
|
end
|
434
440
|
end
|
435
441
|
|
436
|
-
#
|
437
|
-
#
|
442
|
+
# Supports mass assignment of attributes
|
443
|
+
# @return (see #apply_attributes!)
|
438
444
|
def attributes=(hash)
|
439
445
|
return unless hash.is_a?(Hash)
|
440
446
|
# - [:id, :objectId]
|
@@ -442,12 +448,14 @@ module Parse
|
|
442
448
|
apply_attributes!(hash, dirty_track: true)
|
443
449
|
end
|
444
450
|
|
445
|
-
#
|
446
|
-
#
|
447
|
-
#
|
448
|
-
#
|
449
|
-
#
|
451
|
+
# Returns a hash of attributes for properties that have changed. This will
|
452
|
+
# not include any of the base attributes (ex. id, created_at, etc).
|
453
|
+
# This method helps generate the change payload that will be sent when saving
|
454
|
+
# objects to Parse.
|
455
|
+
# @param include_all [Boolean] whether to include all {Parse::Properties::BASE_KEYS} attributes.
|
456
|
+
# @return [Hash]
|
450
457
|
def attribute_updates(include_all = false)
|
458
|
+
# TODO: Replace this algorithm with reduce()
|
451
459
|
h = {}
|
452
460
|
changed.each do |key|
|
453
461
|
key = key.to_sym
|
@@ -464,13 +472,19 @@ module Parse
|
|
464
472
|
h
|
465
473
|
end
|
466
474
|
|
467
|
-
#
|
475
|
+
# @return [Boolean] true if any of the attributes have changed.
|
468
476
|
def attribute_changes?
|
469
477
|
changed.any? do |key|
|
470
478
|
fields[key.to_sym].present?
|
471
479
|
end
|
472
480
|
end
|
473
|
-
|
481
|
+
# Returns a formatted value based on the operation hash and data_type of the property.
|
482
|
+
# For some values in Parse, they are specified as operation hashes which could include
|
483
|
+
# Add, Remove, Delete, AddUnique and Increment.
|
484
|
+
# @param key [Symbol] the name of the property
|
485
|
+
# @param val [Hash] the Parse operation hash value.
|
486
|
+
# @param data_type [Symbol] The data type of the property.
|
487
|
+
# @return [Object]
|
474
488
|
def format_operation(key, val, data_type)
|
475
489
|
return val unless val.is_a?(Hash) && val["__op"].present?
|
476
490
|
op = val["__op"]
|
@@ -496,6 +510,11 @@ module Parse
|
|
496
510
|
|
497
511
|
# this method takes an input value and transforms it to the proper local format
|
498
512
|
# depending on the data type that was set for a particular property key.
|
513
|
+
# Return the internal representation of a property value for a given data type.
|
514
|
+
# @param key [Symbol] the name of the property
|
515
|
+
# @param val [Object] the value to format.
|
516
|
+
# @param data_type [Symbol] provide a hint to the data_type of this value.
|
517
|
+
# @return [Object]
|
499
518
|
def format_value(key, val, data_type = nil)
|
500
519
|
# if data_type wasn't passed, then get the data_type from the fields hash
|
501
520
|
data_type ||= self.fields[key]
|
@@ -4,249 +4,252 @@
|
|
4
4
|
require_relative '../../query'
|
5
5
|
|
6
6
|
module Parse
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
7
|
+
module Core
|
8
|
+
# Defines the querying methods applied to a Parse::Object.
|
9
|
+
module Querying
|
10
|
+
|
11
|
+
# This feature is a small subset of the
|
12
|
+
# {http://guides.rubyonrails.org/active_record_querying.html#scopes
|
13
|
+
# ActiveRecord named scopes} feature. Scoping allows you to specify
|
14
|
+
# commonly-used queries which can be referenced as class method calls and
|
15
|
+
# are chainable with other scopes. You can use every {Parse::Query}
|
16
|
+
# method previously covered such as `where`, `includes` and `limit`.
|
17
|
+
#
|
18
|
+
# class Article < Parse::Object
|
19
|
+
# property :published, :boolean
|
20
|
+
# scope :published, -> { query(published: true) }
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# This is the same as defining your own class method for the query.
|
24
|
+
#
|
25
|
+
# class Article < Parse::Object
|
26
|
+
# def self.published
|
27
|
+
# query(published: true)
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# You can also chain scopes and pass parameters. In addition, boolean and
|
32
|
+
# enumerated properties have automatically generated scopes for you to use.
|
33
|
+
#
|
34
|
+
# class Article < Parse::Object
|
35
|
+
# scope :published, -> { query(published: true) }
|
36
|
+
#
|
37
|
+
# property :comment_count, :integer
|
38
|
+
# property :category
|
39
|
+
# property :approved, :boolean
|
40
|
+
#
|
41
|
+
# scope :published_and_commented, -> { published.where :comment_count.gt => 0 }
|
42
|
+
# scope :popular_topics, ->(name) { published_and_commented.where category: name }
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# # simple scope
|
46
|
+
# Article.published # => where published is true
|
47
|
+
#
|
48
|
+
# # chained scope
|
49
|
+
# Article.published_and_commented # published is true and comment_count > 0
|
50
|
+
#
|
51
|
+
# # scope with parameters
|
52
|
+
# Article.popular_topic("music") # => popular music articles
|
53
|
+
# # equivalent: where(published: true, :comment_count.gt => 0, category: name)
|
54
|
+
#
|
55
|
+
# # automatically generated scope
|
56
|
+
# Article.approved(category: "tour") # => where approved: true, category: 'tour'
|
57
|
+
#
|
58
|
+
# If you would like to turn off automatic scope generation for property types,
|
59
|
+
# set the option `:scope` to false when declaring the property.
|
60
|
+
# @param name [Symbol] the name of the scope.
|
61
|
+
# @param body [Proc] the proc related to the scope.
|
62
|
+
# @raise ArgumentError if body parameter does not respond to `call`
|
63
|
+
# @return [Symbol] the name of the singleton method created.
|
64
|
+
def scope(name, body)
|
65
|
+
unless body.respond_to?(:call)
|
66
|
+
raise ArgumentError, 'The scope body needs to be callable.'
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
name = name.to_sym
|
70
|
+
if respond_to?(name, true)
|
71
|
+
puts "Creating scope :#{name}. Will overwrite existing method #{self}.#{name}."
|
72
|
+
end
|
72
73
|
|
73
|
-
|
74
|
+
define_singleton_method(name) do |*args, &block|
|
74
75
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
76
|
+
if body.arity.zero?
|
77
|
+
res = body.call
|
78
|
+
res.conditions(*args) if args.present?
|
79
|
+
else
|
80
|
+
res = body.call(*args)
|
81
|
+
end
|
81
82
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
83
|
+
_q = res || query
|
84
|
+
|
85
|
+
if _q.is_a?(Parse::Query)
|
86
|
+
klass = self
|
87
|
+
_q.define_singleton_method(:method_missing) do |m, *args, &chained_block|
|
88
|
+
if klass.respond_to?(m, true)
|
89
|
+
# must be a scope
|
90
|
+
klass_scope = klass.send(m, *args)
|
91
|
+
if klass_scope.is_a?(Parse::Query)
|
92
|
+
# merge constraints
|
93
|
+
add_constraints( klass_scope.constraints )
|
94
|
+
# if a block was passed, execute the query, otherwise return the query
|
95
|
+
return chained_block.present? ? results(&chained_block) : self
|
96
|
+
end # if
|
97
|
+
klass = nil # help clean up ruby gc
|
98
|
+
return klass_scope
|
99
|
+
end
|
96
100
|
klass = nil # help clean up ruby gc
|
97
|
-
return
|
101
|
+
return results.send(m, *args, &chained_block)
|
98
102
|
end
|
99
|
-
klass = nil # help clean up ruby gc
|
100
|
-
return results.send(m, *args, &chained_block)
|
101
103
|
end
|
104
|
+
|
105
|
+
Parse::Query.apply_auto_introspection!(_q)
|
106
|
+
|
107
|
+
return _q if block.nil?
|
108
|
+
_q.results(&block)
|
102
109
|
end
|
103
|
-
|
104
|
-
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# Creates a new {Parse::Query} with the given constraints for this class.
|
115
|
+
# @example
|
116
|
+
# # assume Post < Parse::Object
|
117
|
+
# query = Post.query(:updated_at.before => DateTime.now)
|
118
|
+
# @return [Parse::Query] a new query with the given constraints for this
|
119
|
+
# Parse::Object subclass.
|
120
|
+
def query(constraints = {})
|
121
|
+
Parse::Query.new self.parse_class, constraints
|
122
|
+
end; alias_method :where, :query
|
123
|
+
|
124
|
+
# @param conditions (see Parse::Query#where)
|
125
|
+
# @return (see Parse::Query#where)
|
126
|
+
# @see Parse::Query#where
|
127
|
+
def literal_where(conditions = {})
|
128
|
+
query.where(conditions)
|
105
129
|
end
|
106
130
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
# When no block is passed, all objects are returned. Using a block is more memory
|
130
|
-
# efficient as matching objects are fetched in batches and discarded after the iteration
|
131
|
-
# is completed.
|
132
|
-
# @param constraints [Hash] a set of {Parse::Query} constraints.
|
133
|
-
# @yield a block to iterate with each matching object.
|
134
|
-
# @example
|
135
|
-
#
|
136
|
-
# songs = Song.all( ... expressions ...) # => array of Parse::Objects
|
137
|
-
# # memory efficient for large amounts of records.
|
138
|
-
# Song.all( ... expressions ...) do |song|
|
139
|
-
# # ... do something with song..
|
140
|
-
# end
|
141
|
-
#
|
142
|
-
# @return [Array<Parse::Object>] an array of matching objects. If a block is passed,
|
143
|
-
# an empty array is returned.
|
144
|
-
def all(constraints = {limit: :max})
|
145
|
-
constraints = constraints.reverse_merge({limit: :max})
|
146
|
-
prepared_query = query(constraints)
|
147
|
-
return prepared_query.results(&Proc.new) if block_given?
|
148
|
-
prepared_query.results
|
149
|
-
end
|
150
|
-
|
151
|
-
# Returns the first item matching the constraint.
|
152
|
-
# @overload first(count = 1)
|
153
|
-
# @param count [Interger] The number of items to return.
|
154
|
-
# @example
|
155
|
-
# Object.first(2) # => an array of the first 2 objects in the collection.
|
156
|
-
# @return [Parse::Object] if count == 1
|
157
|
-
# @return [Array<Parse::Object>] if count > 1
|
158
|
-
# @overload first(constraints = {})
|
159
|
-
# @param constraints [Hash] a set of {Parse::Query} constraints.
|
160
|
-
# @example
|
161
|
-
# Object.first( :name => "Anthony" )
|
162
|
-
# @return [Parse::Object] the first matching object.
|
163
|
-
def first(constraints = {})
|
164
|
-
fetch_count = 1
|
165
|
-
if constraints.is_a?(Numeric)
|
166
|
-
fetch_count = constraints.to_i
|
167
|
-
constraints = {}
|
131
|
+
# Fetch all matching objects in this collection matching the constraints.
|
132
|
+
# This will be the most common way when querying Parse objects for a subclass.
|
133
|
+
# When no block is passed, all objects are returned. Using a block is more memory
|
134
|
+
# efficient as matching objects are fetched in batches and discarded after the iteration
|
135
|
+
# is completed.
|
136
|
+
# @param constraints [Hash] a set of {Parse::Query} constraints.
|
137
|
+
# @yield a block to iterate with each matching object.
|
138
|
+
# @example
|
139
|
+
#
|
140
|
+
# songs = Song.all( ... expressions ...) # => array of Parse::Objects
|
141
|
+
# # memory efficient for large amounts of records.
|
142
|
+
# Song.all( ... expressions ...) do |song|
|
143
|
+
# # ... do something with song..
|
144
|
+
# end
|
145
|
+
#
|
146
|
+
# @return [Array<Parse::Object>] an array of matching objects. If a block is passed,
|
147
|
+
# an empty array is returned.
|
148
|
+
def all(constraints = {limit: :max})
|
149
|
+
constraints = constraints.reverse_merge({limit: :max})
|
150
|
+
prepared_query = query(constraints)
|
151
|
+
return prepared_query.results(&Proc.new) if block_given?
|
152
|
+
prepared_query.results
|
168
153
|
end
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
return
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
# @param constraints (see #all)
|
188
|
-
# @return [Array<Parse::Object>]
|
189
|
-
def newest(constraints = {})
|
190
|
-
constraints.merge!(order: :created_at.desc)
|
191
|
-
_q = query(constraints)
|
192
|
-
_q.define_singleton_method(:method_missing) { |m, *args, &block| self.results.send(m, *args, &block) }
|
193
|
-
_q
|
194
|
-
end
|
195
|
-
|
196
|
-
# Find objects matching the constraint ordered by the ascending created_at date.
|
197
|
-
# @param constraints (see #all)
|
198
|
-
# @return [Array<Parse::Object>]
|
199
|
-
def oldest(constraints = {})
|
200
|
-
constraints.merge!(order: :created_at.asc)
|
201
|
-
_q = query(constraints)
|
202
|
-
_q.define_singleton_method(:method_missing) { |m, *args, &block| self.results.send(m, *args, &block) }
|
203
|
-
_q
|
204
|
-
end
|
205
|
-
|
206
|
-
# Find objects for a given objectId in this collection.The result is a list
|
207
|
-
# (or single item) of the objects that were successfully found.
|
208
|
-
# @example
|
209
|
-
# Object.find "<objectId>"
|
210
|
-
# Object.find "<objectId>", "<objectId>"....
|
211
|
-
# Object.find ["<objectId>", "<objectId>"]
|
212
|
-
# @param parse_ids [String] the objectId to find.
|
213
|
-
# @param type [Symbol] the fetching methodology to use if more than one id was passed.
|
214
|
-
# - *:parallel* : Utilizes parrallel HTTP requests to fetch all objects requested.
|
215
|
-
# - *:batch* : This uses a batch fetch request using a contained_in clause.
|
216
|
-
# @param compact [Boolean] whether to remove nil items from the returned array for objects
|
217
|
-
# that were not found.
|
218
|
-
# @return [Parse::Object] if only one id was provided as a parameter.
|
219
|
-
# @return [Array<Parse::Object>] if more than one id was provided as a parameter.
|
220
|
-
def find(*parse_ids, type: :parallel, compact: true)
|
221
|
-
# flatten the list of Object ids.
|
222
|
-
parse_ids.flatten!
|
223
|
-
parse_ids.compact!
|
224
|
-
# determines if the result back to the call site is an array or a single result
|
225
|
-
as_array = parse_ids.count > 1
|
226
|
-
results = []
|
227
|
-
|
228
|
-
if type == :batch
|
229
|
-
# use a .in query with the given id as a list
|
230
|
-
results = self.class.all(:id.in => parse_ids)
|
231
|
-
else
|
232
|
-
# use Parallel to make multiple threaded requests for finding these objects.
|
233
|
-
# The benefit of using this as default is that each request goes to a specific URL
|
234
|
-
# which is better than Query request (table scan). This in turn allows for caching of
|
235
|
-
# individual objects.
|
236
|
-
results = parse_ids.threaded_map do |parse_id|
|
237
|
-
next nil unless parse_id.present?
|
238
|
-
response = client.fetch_object(parse_class, parse_id)
|
239
|
-
next nil if response.error?
|
240
|
-
Parse::Object.build response.result, parse_class
|
154
|
+
|
155
|
+
# Returns the first item matching the constraint.
|
156
|
+
# @overload first(count = 1)
|
157
|
+
# @param count [Interger] The number of items to return.
|
158
|
+
# @example
|
159
|
+
# Object.first(2) # => an array of the first 2 objects in the collection.
|
160
|
+
# @return [Parse::Object] if count == 1
|
161
|
+
# @return [Array<Parse::Object>] if count > 1
|
162
|
+
# @overload first(constraints = {})
|
163
|
+
# @param constraints [Hash] a set of {Parse::Query} constraints.
|
164
|
+
# @example
|
165
|
+
# Object.first( :name => "Anthony" )
|
166
|
+
# @return [Parse::Object] the first matching object.
|
167
|
+
def first(constraints = {})
|
168
|
+
fetch_count = 1
|
169
|
+
if constraints.is_a?(Numeric)
|
170
|
+
fetch_count = constraints.to_i
|
171
|
+
constraints = {}
|
241
172
|
end
|
173
|
+
constraints.merge!( {limit: fetch_count} )
|
174
|
+
res = query(constraints).results
|
175
|
+
return res.first if fetch_count == 1
|
176
|
+
return res.first fetch_count
|
242
177
|
end
|
243
|
-
# removes any nil items in the array
|
244
|
-
results.compact! if compact
|
245
178
|
|
246
|
-
|
247
|
-
|
179
|
+
# Creates a count request which is more performant when counting objects.
|
180
|
+
# @example
|
181
|
+
# # number of songs with a like count greater than 20.
|
182
|
+
# count = Song.count( :like_count.gt => 20 )
|
183
|
+
# @param constraints (see #all)
|
184
|
+
# @return [Interger] the number of records matching the query.
|
185
|
+
# @see Parse::Query#count
|
186
|
+
def count(constraints = {})
|
187
|
+
query(constraints).count
|
188
|
+
end
|
189
|
+
|
190
|
+
# Find objects matching the constraint ordered by the descending created_at date.
|
191
|
+
# @param constraints (see #all)
|
192
|
+
# @return [Array<Parse::Object>]
|
193
|
+
def newest(constraints = {})
|
194
|
+
constraints.merge!(order: :created_at.desc)
|
195
|
+
_q = query(constraints)
|
196
|
+
_q.define_singleton_method(:method_missing) { |m, *args, &block| self.results.send(m, *args, &block) }
|
197
|
+
_q
|
198
|
+
end
|
248
199
|
|
249
|
-
|
200
|
+
# Find objects matching the constraint ordered by the ascending created_at date.
|
201
|
+
# @param constraints (see #all)
|
202
|
+
# @return [Array<Parse::Object>]
|
203
|
+
def oldest(constraints = {})
|
204
|
+
constraints.merge!(order: :created_at.asc)
|
205
|
+
_q = query(constraints)
|
206
|
+
_q.define_singleton_method(:method_missing) { |m, *args, &block| self.results.send(m, *args, &block) }
|
207
|
+
_q
|
208
|
+
end
|
209
|
+
|
210
|
+
# Find objects for a given objectId in this collection.The result is a list
|
211
|
+
# (or single item) of the objects that were successfully found.
|
212
|
+
# @example
|
213
|
+
# Object.find "<objectId>"
|
214
|
+
# Object.find "<objectId>", "<objectId>"....
|
215
|
+
# Object.find ["<objectId>", "<objectId>"]
|
216
|
+
# @param parse_ids [String] the objectId to find.
|
217
|
+
# @param type [Symbol] the fetching methodology to use if more than one id was passed.
|
218
|
+
# - *:parallel* : Utilizes parrallel HTTP requests to fetch all objects requested.
|
219
|
+
# - *:batch* : This uses a batch fetch request using a contained_in clause.
|
220
|
+
# @param compact [Boolean] whether to remove nil items from the returned array for objects
|
221
|
+
# that were not found.
|
222
|
+
# @return [Parse::Object] if only one id was provided as a parameter.
|
223
|
+
# @return [Array<Parse::Object>] if more than one id was provided as a parameter.
|
224
|
+
def find(*parse_ids, type: :parallel, compact: true)
|
225
|
+
# flatten the list of Object ids.
|
226
|
+
parse_ids.flatten!
|
227
|
+
parse_ids.compact!
|
228
|
+
# determines if the result back to the call site is an array or a single result
|
229
|
+
as_array = parse_ids.count > 1
|
230
|
+
results = []
|
231
|
+
|
232
|
+
if type == :batch
|
233
|
+
# use a .in query with the given id as a list
|
234
|
+
results = self.class.all(:id.in => parse_ids)
|
235
|
+
else
|
236
|
+
# use Parallel to make multiple threaded requests for finding these objects.
|
237
|
+
# The benefit of using this as default is that each request goes to a specific URL
|
238
|
+
# which is better than Query request (table scan). This in turn allows for caching of
|
239
|
+
# individual objects.
|
240
|
+
results = parse_ids.threaded_map do |parse_id|
|
241
|
+
next nil unless parse_id.present?
|
242
|
+
response = client.fetch_object(parse_class, parse_id)
|
243
|
+
next nil if response.error?
|
244
|
+
Parse::Object.build response.result, parse_class
|
245
|
+
end
|
246
|
+
end
|
247
|
+
# removes any nil items in the array
|
248
|
+
results.compact! if compact
|
250
249
|
|
250
|
+
as_array ? results : results.first
|
251
|
+
end; alias_method :get, :find
|
251
252
|
|
253
|
+
end # Querying
|
254
|
+
end
|
252
255
|
end
|