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