parse-stack 1.4.3 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes.md +52 -39
- data/Gemfile.lock +2 -2
- data/README.md +609 -124
- data/bin/console +0 -9
- data/lib/parse/api/all.rb +3 -0
- data/lib/parse/api/analytics.rb +2 -2
- data/lib/parse/api/apps.rb +15 -17
- data/lib/parse/api/batch.rb +4 -1
- data/lib/parse/api/cloud_functions.rb +2 -0
- data/lib/parse/api/config.rb +14 -2
- data/lib/parse/api/files.rb +6 -3
- data/lib/parse/api/hooks.rb +4 -4
- data/lib/parse/api/objects.rb +14 -11
- data/lib/parse/api/push.rb +4 -2
- data/lib/parse/api/schemas.rb +6 -5
- data/lib/parse/api/sessions.rb +11 -1
- data/lib/parse/api/users.rb +65 -15
- data/lib/parse/client/authentication.rb +4 -2
- data/lib/parse/client/body_builder.rb +11 -3
- data/lib/parse/client/caching.rb +17 -6
- data/lib/parse/client/protocol.rb +14 -8
- data/lib/parse/client/request.rb +4 -1
- data/lib/parse/client/response.rb +59 -6
- data/lib/parse/client.rb +72 -42
- data/lib/parse/model/acl.rb +22 -4
- data/lib/parse/model/associations/belongs_to.rb +22 -10
- data/lib/parse/model/associations/collection_proxy.rb +14 -1
- data/lib/parse/model/associations/has_many.rb +76 -15
- data/lib/parse/model/associations/has_one.rb +69 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +13 -6
- data/lib/parse/model/associations/relation_collection_proxy.rb +5 -2
- data/lib/parse/model/bytes.rb +6 -2
- data/lib/parse/model/classes/installation.rb +27 -0
- data/lib/parse/model/classes/role.rb +20 -0
- data/lib/parse/model/classes/session.rb +26 -0
- data/lib/parse/model/classes/user.rb +185 -0
- data/lib/parse/model/core/actions.rb +40 -26
- data/lib/parse/model/core/properties.rb +126 -20
- data/lib/parse/model/core/querying.rb +63 -3
- data/lib/parse/model/core/schema.rb +9 -6
- data/lib/parse/model/date.rb +5 -1
- data/lib/parse/model/file.rb +12 -9
- data/lib/parse/model/geopoint.rb +6 -4
- data/lib/parse/model/model.rb +29 -21
- data/lib/parse/model/object.rb +29 -76
- data/lib/parse/model/pointer.rb +8 -6
- data/lib/parse/model/push.rb +4 -1
- data/lib/parse/query/constraint.rb +3 -0
- data/lib/parse/query/constraints.rb +6 -3
- data/lib/parse/query/operation.rb +3 -0
- data/lib/parse/query/ordering.rb +3 -0
- data/lib/parse/query.rb +85 -38
- data/lib/parse/stack/generators/rails.rb +3 -0
- data/lib/parse/stack/railtie.rb +2 -0
- data/lib/parse/stack/tasks.rb +4 -1
- data/lib/parse/stack/version.rb +4 -1
- data/lib/parse/stack.rb +3 -0
- data/lib/parse/webhooks/payload.rb +14 -8
- data/lib/parse/webhooks/registration.rb +11 -8
- data/lib/parse/webhooks.rb +11 -8
- data/lib/parse-stack.rb +3 -0
- data/parse-stack.gemspec +10 -8
- metadata +16 -4
@@ -1,3 +1,52 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Member name Value Description
|
5
|
+
# OtherCause -1 Error code indicating that an unknown error or an error unrelated to Parse occurred.
|
6
|
+
# InternalServerError 1 Error code indicating that something has gone wrong with the server. If you get this error code, it is Parse's fault. Please report the bug to https://parse.com/help.
|
7
|
+
# ConnectionFailed 100 Error code indicating the connection to the Parse servers failed.
|
8
|
+
# ObjectNotFound 101 Error code indicating the specified object doesn't exist.
|
9
|
+
# InvalidQuery 102 Error code indicating you tried to query with a datatype that doesn't support it, like exact matching an array or object.
|
10
|
+
# InvalidClassName 103 Error code indicating a missing or invalid classname. Classnames are case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the only valid characters.
|
11
|
+
# MissingObjectId 104 Error code indicating an unspecified object id.
|
12
|
+
# InvalidKeyName 105 Error code indicating an invalid key name. Keys are case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the only valid characters.
|
13
|
+
# InvalidPointer 106 Error code indicating a malformed pointer. You should not see this unless you have been mucking about changing internal Parse code.
|
14
|
+
# InvalidJSON 107 Error code indicating that badly formed JSON was received upstream. This either indicates you have done something unusual with modifying how things encode to JSON, or the network is failing badly.
|
15
|
+
# CommandUnavailable 108 Error code indicating that the feature you tried to access is only available internally for testing purposes.
|
16
|
+
# NotInitialized 109 You must call Parse.initialize before using the Parse library.
|
17
|
+
# IncorrectType 111 Error code indicating that a field was set to an inconsistent type.
|
18
|
+
# InvalidChannelName 112 Error code indicating an invalid channel name. A channel name is either an empty string (the broadcast channel) or contains only a-zA-Z0-9_ characters and starts with a letter.
|
19
|
+
# PushMisconfigured 115 Error code indicating that push is misconfigured.
|
20
|
+
# ObjectTooLarge 116 Error code indicating that the object is too large.
|
21
|
+
# OperationForbidden 119 Error code indicating that the operation isn't allowed for clients.
|
22
|
+
# CacheMiss 120 Error code indicating the result was not found in the cache.
|
23
|
+
# InvalidNestedKey 121 Error code indicating that an invalid key was used in a nested JSONObject.
|
24
|
+
# InvalidFileName 122 Error code indicating that an invalid filename was used for ParseFile. A valid file name contains only a-zA-Z0-9_. characters and is between 1 and 128 characters.
|
25
|
+
# InvalidACL 123 Error code indicating an invalid ACL was provided.
|
26
|
+
# Timeout 124 Error code indicating that the request timed out on the server. Typically this indicates that the request is too expensive to run.
|
27
|
+
# InvalidEmailAddress 125 Error code indicating that the email address was invalid.
|
28
|
+
# DuplicateValue 137 Error code indicating that a unique field was given a value that is already taken.
|
29
|
+
# InvalidRoleName 139 Error code indicating that a role's name is invalid.
|
30
|
+
# ExceededQuota 140 Error code indicating that an application quota was exceeded. Upgrade to resolve.
|
31
|
+
# ScriptFailed 141 Error code indicating that a Cloud Code script failed.
|
32
|
+
# ValidationFailed 142 Error code indicating that a Cloud Code validation failed.
|
33
|
+
# FileDeleteFailed 153 Error code indicating that deleting a file failed.
|
34
|
+
# RequestLimitExceeded 155 Error code indicating that the application has exceeded its request limit.
|
35
|
+
# InvalidEventName 160 Error code indicating that the provided event name is invalid.
|
36
|
+
# UsernameMissing 200 Error code indicating that the username is missing or empty.
|
37
|
+
# PasswordMissing 201 Error code indicating that the password is missing or empty.
|
38
|
+
# UsernameTaken 202 Error code indicating that the username has already been taken.
|
39
|
+
# EmailTaken 203 Error code indicating that the email has already been taken.
|
40
|
+
# EmailMissing 204 Error code indicating that the email is missing, but must be specified.
|
41
|
+
# EmailNotFound 205 Error code indicating that a user with the specified email was not found.
|
42
|
+
# SessionMissing 206 Error code indicating that a user object without a valid session could not be altered.
|
43
|
+
# MustCreateUserThroughSignup 207 Error code indicating that a user can only be created through signup.
|
44
|
+
# AccountAlreadyLinked 208 Error code indicating that an an account being linked is already linked to another user.
|
45
|
+
# InvalidSessionToken 209 Error code indicating that the current session token is invalid.
|
46
|
+
# LinkedIdMissing 250 Error code indicating that a user cannot be linked to an account because that account's id could not be found.
|
47
|
+
# InvalidLinkedSession 251 Error code indicating that a user with a linked (e.g. Facebook) account has an invalid session.
|
48
|
+
# UnsupportedService 252 Error code indicating that a service being linked (e.g. Facebook or Twitter) is unsupported.
|
49
|
+
|
1
50
|
require 'active_support'
|
2
51
|
require 'active_support/json'
|
3
52
|
# This is the model that represents a response from Parse. A Response can also
|
@@ -13,11 +62,15 @@ module Parse
|
|
13
62
|
ERROR_TIMEOUT = 124
|
14
63
|
ERROR_EXCEEDED_BURST_LIMIT = 155
|
15
64
|
ERROR_OBJECT_NOT_FOUND = 101
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
65
|
+
ERROR_USERNAME_MISSING = 200
|
66
|
+
ERROR_PASSWORD_MISSING = 201
|
67
|
+
ERROR_USERNAME_TAKEN = 202
|
68
|
+
ERROR_EMAIL_TAKEN = 203
|
69
|
+
|
70
|
+
ERROR = "error"
|
71
|
+
CODE = "code"
|
72
|
+
RESULTS = "results"
|
73
|
+
COUNT = "count"
|
21
74
|
# A response has a result or (a code and an error)
|
22
75
|
attr_accessor :parse_class, :code, :error, :result, :http_status
|
23
76
|
attr_accessor :request # capture request that created result
|
@@ -67,7 +120,7 @@ module Parse
|
|
67
120
|
# if batch response, generate array based on the response hash.
|
68
121
|
@result.map do |r|
|
69
122
|
next r unless r.is_a?(Hash)
|
70
|
-
hash = r["success"
|
123
|
+
hash = r["success"] || r[ERROR]
|
71
124
|
Parse::Response.new hash
|
72
125
|
end
|
73
126
|
end
|
data/lib/parse/client.rb
CHANGED
@@ -19,22 +19,34 @@ require_relative "api/all"
|
|
19
19
|
module Parse
|
20
20
|
|
21
21
|
# This is an exception that is thrown if there is a client connectivity issue
|
22
|
-
class ConnectionError <
|
23
|
-
class TimeoutError <
|
24
|
-
class ProtocolError <
|
25
|
-
class ServerError <
|
26
|
-
class ServiceUnavailableError <
|
27
|
-
class AuthenticationError <
|
28
|
-
class RequestLimitExceededError <
|
29
|
-
class InvalidSessionTokenError <
|
22
|
+
class ConnectionError < StandardError; end;
|
23
|
+
class TimeoutError < StandardError; end;
|
24
|
+
class ProtocolError < StandardError; end;
|
25
|
+
class ServerError < StandardError; end;
|
26
|
+
class ServiceUnavailableError < StandardError; end;
|
27
|
+
class AuthenticationError < StandardError; end;
|
28
|
+
class RequestLimitExceededError < StandardError; end;
|
29
|
+
class InvalidSessionTokenError < StandardError; end;
|
30
30
|
|
31
31
|
# helper method to get the config variables.
|
32
32
|
def self.config(s = :default)
|
33
|
-
Parse::Client.
|
33
|
+
Parse::Client.client(s).config
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.set_config(field, value, s = :default)
|
37
|
+
Parse::Client.client(s).update_config({ field => value })
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.update_config(params, s = :default)
|
41
|
+
Parse::Client.client(s).update_config(params)
|
34
42
|
end
|
35
43
|
|
36
44
|
def self.config!(s = :default)
|
37
|
-
Parse::Client.
|
45
|
+
Parse::Client.client(s).config!
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.client(conn = :default)
|
49
|
+
Parse::Client.client(conn)
|
38
50
|
end
|
39
51
|
|
40
52
|
# Main class for the client. The client class is based on a Faraday stack.
|
@@ -61,23 +73,37 @@ module Parse
|
|
61
73
|
# The client can support multiple sessions. The first session created, will be placed
|
62
74
|
# under the default session tag. The :default session will be the default client to be used
|
63
75
|
# by the other classes including Parse::Query and Parse::Objects
|
64
|
-
|
76
|
+
@clients = { default: nil }
|
77
|
+
class << self
|
78
|
+
attr_reader :clients
|
79
|
+
def session?(v = :default)
|
80
|
+
puts '[Warning] Parse::Client#session is DEPRECATED. Please use Parse::Client#client instead.'
|
81
|
+
self.client? v
|
82
|
+
end
|
65
83
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
84
|
+
# DEPRECATED
|
85
|
+
# get a session for a given tag. This will also create a new one for the tag if not specified.
|
86
|
+
def session(connection = :default)
|
87
|
+
puts '[Warning] Parse::Client#session is DEPRECATED. Please use Parse::Client#client instead.'
|
88
|
+
self.client(connection)
|
89
|
+
end
|
90
|
+
|
91
|
+
def client?(v = :default)
|
92
|
+
@clients[v].present?
|
93
|
+
end
|
94
|
+
|
95
|
+
def client(connection = :default)
|
96
|
+
@clients[connection] ||= self.new
|
97
|
+
end
|
98
|
+
|
99
|
+
def setup(opts = {})
|
100
|
+
# If Proc.new is called from inside a method without any arguments of
|
101
|
+
# its own, it will return a new Proc containing the block given to
|
102
|
+
# its surrounding method.
|
103
|
+
# http://mudge.name/2011/01/26/passing-blocks-in-ruby-without-block.html
|
104
|
+
@clients[:default] = self.new(opts, &Proc.new)
|
105
|
+
end
|
74
106
|
|
75
|
-
def self.setup(opts = {})
|
76
|
-
# If Proc.new is called from inside a method without any arguments of
|
77
|
-
# its own, it will return a new Proc containing the block given to
|
78
|
-
# its surrounding method.
|
79
|
-
# http://mudge.name/2011/01/26/passing-blocks-in-ruby-without-block.html
|
80
|
-
@@sessions[:default] = self.new(opts, &Proc.new)
|
81
107
|
end
|
82
108
|
|
83
109
|
# This builds a new Parse::Client stack. The options are:
|
@@ -108,7 +134,7 @@ module Parse
|
|
108
134
|
#Configure Faraday
|
109
135
|
opts[:faraday] ||= {}
|
110
136
|
opts[:faraday].merge!(:url => @server_url)
|
111
|
-
@
|
137
|
+
@conn = Faraday.new(opts[:faraday]) do |conn|
|
112
138
|
#conn.request :json
|
113
139
|
|
114
140
|
conn.response :logger if opts[:logging]
|
@@ -134,7 +160,7 @@ module Parse
|
|
134
160
|
|
135
161
|
if opts[:cache].present? && opts[:expires].to_i > 0
|
136
162
|
unless opts[:cache].is_a?(Moneta::Transformer)
|
137
|
-
raise "Parse::Client option :cache needs to be a type of Moneta::Transformer store."
|
163
|
+
raise ArgumentError, "Parse::Client option :cache needs to be a type of Moneta::Transformer store."
|
138
164
|
end
|
139
165
|
self.cache = opts[:cache]
|
140
166
|
conn.use Parse::Middleware::Caching, self.cache, {expires: opts[:expires].to_i }
|
@@ -145,7 +171,7 @@ module Parse
|
|
145
171
|
conn.adapter opts[:adapter]
|
146
172
|
|
147
173
|
end
|
148
|
-
|
174
|
+
Parse::Client.clients[:default] ||= self
|
149
175
|
self
|
150
176
|
end
|
151
177
|
|
@@ -154,7 +180,7 @@ module Parse
|
|
154
180
|
end
|
155
181
|
|
156
182
|
def url_prefix
|
157
|
-
@
|
183
|
+
@conn.url_prefix
|
158
184
|
end
|
159
185
|
|
160
186
|
def clear_cache!
|
@@ -189,10 +215,10 @@ module Parse
|
|
189
215
|
# http method
|
190
216
|
method = method.downcase.to_sym
|
191
217
|
# set the User-Agent
|
192
|
-
headers["User-Agent"
|
218
|
+
headers["User-Agent"] = "Parse-Stack v#{Parse::Stack::VERSION}"
|
193
219
|
|
194
220
|
if opts[:cache] == false
|
195
|
-
headers[Parse::Middleware::Caching::CACHE_CONTROL] = "no-cache"
|
221
|
+
headers[Parse::Middleware::Caching::CACHE_CONTROL] = "no-cache"
|
196
222
|
elsif opts[:cache].is_a?(Numeric)
|
197
223
|
# specify the cache duration of this request
|
198
224
|
headers[Parse::Middleware::Caching::CACHE_EXPIRES_DURATION] = opts[:cache].to_i
|
@@ -202,16 +228,18 @@ module Parse
|
|
202
228
|
headers[Parse::Middleware::Authentication::DISABLE_MASTER_KEY] = "true"
|
203
229
|
end
|
204
230
|
|
205
|
-
|
231
|
+
token = opts[:session_token]
|
232
|
+
if token.present?
|
233
|
+
token = token.session_token if token.respond_to?(:session_token)
|
206
234
|
headers[Parse::Middleware::Authentication::DISABLE_MASTER_KEY] = "true"
|
207
|
-
headers[Parse::Protocol::SESSION_TOKEN] =
|
235
|
+
headers[Parse::Protocol::SESSION_TOKEN] = token
|
208
236
|
end
|
209
237
|
|
210
238
|
#if it is a :get request, then use query params, otherwise body.
|
211
239
|
params = (method == :get ? query : body) || {}
|
212
240
|
# if the path does not start with the '/1/' prefix, then add it to be nice.
|
213
241
|
# actually send the request and return the body
|
214
|
-
response_env = @
|
242
|
+
response_env = @conn.send(method, uri, params, headers)
|
215
243
|
response = response_env.body
|
216
244
|
response.request = _request
|
217
245
|
|
@@ -296,7 +324,7 @@ module Parse
|
|
296
324
|
end
|
297
325
|
|
298
326
|
def send_request(req) #Parse::Request object
|
299
|
-
raise "Object not of Parse::Request type." unless req.is_a?(Parse::Request)
|
327
|
+
raise ArgumentError, "Object not of Parse::Request type." unless req.is_a?(Parse::Request)
|
300
328
|
request req.method, req.path, req.body, req.headers
|
301
329
|
end
|
302
330
|
|
@@ -313,7 +341,7 @@ module Parse
|
|
313
341
|
module ClassMethods
|
314
342
|
attr_accessor :client
|
315
343
|
def client
|
316
|
-
@client ||= Parse::Client.
|
344
|
+
@client ||= Parse::Client.client #defaults to :default tag
|
317
345
|
end
|
318
346
|
end
|
319
347
|
|
@@ -335,16 +363,18 @@ module Parse
|
|
335
363
|
end
|
336
364
|
|
337
365
|
# Helper method to call cloud functions and get results
|
338
|
-
def self.trigger_job(name, body
|
339
|
-
|
340
|
-
|
366
|
+
def self.trigger_job(name, body = {}, **opts)
|
367
|
+
conn = opts[:session] || opts[:client] || :default
|
368
|
+
response = Parse::Client.client(conn).trigger_job(name, body)
|
369
|
+
return response if opts[:raw].present?
|
341
370
|
response.error? ? nil : response.result["result"]
|
342
371
|
end
|
343
372
|
|
344
373
|
# Helper method to call cloud functions and get results
|
345
|
-
def self.call_function(name, body
|
346
|
-
|
347
|
-
|
374
|
+
def self.call_function(name, body = {}, **opts)
|
375
|
+
conn = opts[:session] || opts[:client] || :default
|
376
|
+
response = Parse::Client.client(conn).call_function(name, body)
|
377
|
+
return response if opts[:raw].present?
|
348
378
|
response.error? ? nil : response.result["result"]
|
349
379
|
end
|
350
380
|
|
data/lib/parse/model/acl.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
1
3
|
|
2
4
|
# An ACL represents the Parse Permissions object used for each record. In Parse,
|
3
5
|
# it is composed a hash-like object that represent Parse::User objectIds and/or Parse::Role
|
@@ -12,12 +14,28 @@
|
|
12
14
|
# of dirty tracking.
|
13
15
|
module Parse
|
14
16
|
|
15
|
-
class
|
16
|
-
|
17
|
-
attr_accessor :permissions, :delegate
|
17
|
+
# Base class for supporting custom data types
|
18
|
+
class DataType
|
18
19
|
include ::ActiveModel::Model
|
19
20
|
include ::ActiveModel::Serializers::JSON
|
20
|
-
|
21
|
+
def attributes
|
22
|
+
{}
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.typecast(value, **opts)
|
26
|
+
value
|
27
|
+
end
|
28
|
+
|
29
|
+
def as_json(*args)
|
30
|
+
{}.as_json
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class ACL < DataType
|
36
|
+
# The internal permissions hash and delegate accessors
|
37
|
+
attr_accessor :permissions, :delegate
|
38
|
+
PUBLIC = "*" # Public priviledges are '*' key in Parse
|
21
39
|
|
22
40
|
# provide a set of acls and the delegate (for dirty tracking)
|
23
41
|
# { '*' => { "read": true, "write": true } }
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
1
4
|
require_relative '../pointer'
|
2
5
|
require_relative 'collection_proxy'
|
3
6
|
require_relative 'pointer_collection_proxy'
|
@@ -32,6 +35,11 @@ module Parse
|
|
32
35
|
opts = {as: key, field: key.to_s.camelize(:lower), required: false}.merge(opts)
|
33
36
|
klassName = opts[:as].to_parse_class
|
34
37
|
parse_field = opts[:field].to_sym
|
38
|
+
|
39
|
+
ivar = :"@#{key}"
|
40
|
+
will_change_method = :"#{key}_will_change!"
|
41
|
+
set_attribute_method = :"#{key}_set_attribute!"
|
42
|
+
|
35
43
|
if self.fields[key].present? && Parse::Properties::BASE_FIELD_MAP[key].nil?
|
36
44
|
raise Parse::Properties::DefinitionError, "Belongs relation #{self}##{key} already defined with type #{klassName}"
|
37
45
|
end
|
@@ -56,7 +64,7 @@ module Parse
|
|
56
64
|
|
57
65
|
# We generate the getter method
|
58
66
|
define_method(key) do
|
59
|
-
|
67
|
+
|
60
68
|
val = instance_variable_get ivar
|
61
69
|
# We provide autofetch functionality. If the value is nil and the
|
62
70
|
# current Parse::Object is a pointer, then let's auto fetch it
|
@@ -68,37 +76,41 @@ module Parse
|
|
68
76
|
# if for some reason we retrieved either from store or fetching a
|
69
77
|
# hash, lets try to buid a Pointer of that type.
|
70
78
|
|
71
|
-
if val.is_a?(Hash) && ( val["__type"]
|
72
|
-
val = Parse::Object.build val, ( val[
|
79
|
+
if val.is_a?(Hash) && ( val["__type"] == "Pointer" || val["__type"] == "Object" )
|
80
|
+
val = Parse::Object.build val, ( val[Parse::Model::KEY_CLASS_NAME] || klassName )
|
73
81
|
instance_variable_set ivar, val
|
74
82
|
end
|
75
83
|
val
|
76
84
|
end
|
77
85
|
|
86
|
+
if self.method_defined?("#{key}?")
|
87
|
+
puts "Creating belongs_to helper :#{key}?. Will overwrite existing method #{self}##{key}?."
|
88
|
+
end
|
89
|
+
|
78
90
|
define_method("#{key}?") do
|
79
91
|
self.send(key).is_a?(Parse::Pointer)
|
80
92
|
end
|
81
93
|
|
82
94
|
# A proxy setter method that has dirty tracking enabled.
|
83
95
|
define_method("#{key}=") do |val|
|
84
|
-
send
|
96
|
+
send set_attribute_method, val, true
|
85
97
|
end
|
86
98
|
|
87
99
|
# We only support pointers, either by object or by transforming a hash.
|
88
|
-
define_method(
|
100
|
+
define_method(set_attribute_method) do |val, track = true|
|
89
101
|
if val == Parse::Properties::DELETE_OP
|
90
102
|
val = nil
|
91
|
-
elsif val.is_a?(Hash) && ( val["__type"]
|
92
|
-
val = Parse::Object.build val, ( val[
|
103
|
+
elsif val.is_a?(Hash) && ( val["__type"] == "Pointer" || val["__type"] == "Object" )
|
104
|
+
val = Parse::Object.build val, ( val[Parse::Model::KEY_CLASS_NAME] || klassName )
|
93
105
|
end
|
94
106
|
|
95
107
|
if track == true
|
96
|
-
send
|
108
|
+
send will_change_method unless val == instance_variable_get( ivar )
|
97
109
|
end
|
98
110
|
|
99
111
|
# Never set an object that is not a Parse::Pointer
|
100
112
|
if val.nil? || val.is_a?(Parse::Pointer)
|
101
|
-
instance_variable_set(
|
113
|
+
instance_variable_set(ivar, val)
|
102
114
|
else
|
103
115
|
warn "[#{self.class}] Invalid value #{val} set for belongs_to field #{key}"
|
104
116
|
end
|
@@ -110,7 +122,7 @@ module Parse
|
|
110
122
|
if self.method_defined?(parse_field) == false
|
111
123
|
alias_method parse_field, key
|
112
124
|
alias_method "#{parse_field}=", "#{key}="
|
113
|
-
alias_method "#{parse_field}_set_attribute!",
|
125
|
+
alias_method "#{parse_field}_set_attribute!", set_attribute_method
|
114
126
|
elsif parse_field.to_sym != :objectId
|
115
127
|
warn "Alias belongs_to method #{self}##{parse_field} already defined."
|
116
128
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
1
4
|
require 'active_model'
|
2
5
|
require 'active_support'
|
3
6
|
require 'active_support/inflector'
|
@@ -18,7 +21,7 @@ module Parse
|
|
18
21
|
class CollectionProxy
|
19
22
|
include ::ActiveModel::Model
|
20
23
|
include ::ActiveModel::Dirty
|
21
|
-
|
24
|
+
include ::Enumerable
|
22
25
|
attr_accessor :collection, :delegate, :loaded
|
23
26
|
attr_reader :delegate, :key
|
24
27
|
attr_accessor :parse_class
|
@@ -195,6 +198,16 @@ module Parse
|
|
195
198
|
collection.each &Proc.new
|
196
199
|
end
|
197
200
|
|
201
|
+
def map
|
202
|
+
return collection.enum_for(:map) unless block_given?
|
203
|
+
collection.map &Proc.new
|
204
|
+
end
|
205
|
+
|
206
|
+
def select
|
207
|
+
return collection.enum_for(:select) unless block_given?
|
208
|
+
collection.select &Proc.new
|
209
|
+
end
|
210
|
+
|
198
211
|
def inspect
|
199
212
|
"#<#{self.class} changed?=#{changed?} @collection=#{@collection.inspect} >"
|
200
213
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
1
4
|
require_relative '../pointer'
|
2
5
|
require_relative 'collection_proxy'
|
3
6
|
require_relative 'pointer_collection_proxy'
|
@@ -55,16 +58,75 @@ module Parse
|
|
55
58
|
# By default, all associations are stored in 'through: :array' form. If you are working with a Parse Relation, you
|
56
59
|
# should specify the 'through: :relation' property instead. This will switch the internal storage mechanisms
|
57
60
|
# from using a PointerCollectionProxy to a RelationCollectionProxy.
|
61
|
+
def has_many_queried(key, scope = nil, **opts)
|
62
|
+
# key will be the name of the property
|
63
|
+
# the remote class is either key or as.
|
64
|
+
opts[:scope_only] ||= false
|
65
|
+
klassName = (opts[:as] || key).to_parse_class singularize: true
|
66
|
+
foreign_field = (opts[:field] || parse_class.columnize ).to_sym
|
67
|
+
|
68
|
+
define_method(key) do |*args, &block|
|
69
|
+
return [] if @id.nil?
|
70
|
+
query = Parse::Query.new(klassName, limit: :max)
|
71
|
+
|
72
|
+
query.where(foreign_field => self) unless opts[:scope_only] == true
|
73
|
+
|
74
|
+
if scope.is_a?(Proc)
|
75
|
+
# magic, override the singleton method_missing with accessing object level methods
|
76
|
+
# that don't collide with Parse::Query instance. Still accessible under :i
|
77
|
+
instance = self
|
78
|
+
query.define_singleton_method(:method_missing) { |m, *args, &block| instance.send(m, *args, &block) }
|
79
|
+
query.define_singleton_method(:i) { instance }
|
80
|
+
# if the scope takes no arguments, assume arguments are additional conditions
|
81
|
+
if scope.arity.zero?
|
82
|
+
query.instance_exec(&scope)
|
83
|
+
query.conditions(*args) if args.present?
|
84
|
+
else
|
85
|
+
query.instance_exec(*args,&scope)
|
86
|
+
end
|
87
|
+
instance = nil # help clean up ruby gc
|
88
|
+
elsif args.present?
|
89
|
+
query.conditions(*args)
|
90
|
+
end
|
91
|
+
|
92
|
+
query.define_singleton_method(:method_missing) do |m, *args, &block|
|
93
|
+
klass = Parse::Model.find_class klassName
|
94
|
+
if klass.present? && klass.respond_to?(m)
|
95
|
+
klass_scope = klass.send(m, *args, &block)
|
96
|
+
return klass_scope.is_a?(Parse::Query) ?
|
97
|
+
self.add_constraints( klass_scope.constraints ) :
|
98
|
+
klass_scope
|
99
|
+
end
|
100
|
+
self.results.send(m, *args, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
return query if block.nil?
|
104
|
+
query.results(&block)
|
105
|
+
end
|
58
106
|
|
59
|
-
|
60
|
-
|
107
|
+
end
|
108
|
+
|
109
|
+
def has_many(key, scope = nil, **opts)
|
110
|
+
opts[:through] ||= :query
|
111
|
+
|
112
|
+
if opts[:through] == :query
|
113
|
+
return has_many_queried(key, scope, opts)
|
114
|
+
end
|
115
|
+
|
116
|
+
# below this is the same
|
117
|
+
opts.reverse_merge!({
|
61
118
|
field: key.to_s.camelize(:lower),
|
62
119
|
required: false,
|
63
|
-
as: key}
|
120
|
+
as: key})
|
64
121
|
|
65
122
|
klassName = opts[:as].to_parse_class singularize: true
|
66
|
-
parse_field = opts[:field].to_sym
|
123
|
+
parse_field = opts[:field].to_sym # name of the column (local or remote)
|
67
124
|
access_type = opts[:through].to_sym
|
125
|
+
|
126
|
+
ivar = :"@#{key}"
|
127
|
+
will_change_method = :"#{key}_will_change!"
|
128
|
+
set_attribute_method = :"#{key}_set_attribute!"
|
129
|
+
|
68
130
|
# verify that the user did not duplicate properties or defined different properties with the same name
|
69
131
|
if self.fields[key].present? && Parse::Properties::BASE_FIELD_MAP[key].nil?
|
70
132
|
raise Parse::Properties::DefinitionError, "Has_many property #{self}##{key} already defined with type #{klassName}"
|
@@ -96,7 +158,6 @@ module Parse
|
|
96
158
|
|
97
159
|
# The first method to be defined is a getter.
|
98
160
|
define_method(key) do
|
99
|
-
ivar = :"@#{key}"
|
100
161
|
val = instance_variable_get(ivar)
|
101
162
|
# if the value for this is nil and we are a pointer, then autofetch
|
102
163
|
if val.nil? && pointer?
|
@@ -116,11 +177,11 @@ module Parse
|
|
116
177
|
|
117
178
|
# proxy setter that forwards with dirty tracking
|
118
179
|
define_method("#{key}=") do |val|
|
119
|
-
send
|
180
|
+
send set_attribute_method, val, true
|
120
181
|
end
|
121
182
|
|
122
183
|
# This will set the content of the proxy.
|
123
|
-
define_method(
|
184
|
+
define_method(set_attribute_method) do |val, track = true|
|
124
185
|
# If it is a hash, with a __type of Relation, createa a new RelationCollectionProxy, regardless
|
125
186
|
# of what is defined because we must have gotten this from Parse.
|
126
187
|
|
@@ -132,14 +193,14 @@ module Parse
|
|
132
193
|
|
133
194
|
if val.is_a?(Hash) && val["__type"] == "Relation"
|
134
195
|
relation_objects = val["objects"] || []
|
135
|
-
val = Parse::RelationCollectionProxy.new relation_objects, delegate: self, key: key, parse_class: (val[
|
196
|
+
val = Parse::RelationCollectionProxy.new relation_objects, delegate: self, key: key, parse_class: (val[Parse::Model::KEY_CLASS_NAME] || klassName)
|
136
197
|
elsif val.is_a?(Hash) && val["__op"] == "AddRelation" && val["objects"].present?
|
137
|
-
_collection = proxyKlass.new [], delegate: self, key: key, parse_class: (val[
|
198
|
+
_collection = proxyKlass.new [], delegate: self, key: key, parse_class: (val[Parse::Model::KEY_CLASS_NAME] || klassName)
|
138
199
|
_collection.loaded = true
|
139
200
|
_collection.add val["objects"].parse_objects
|
140
201
|
val = _collection
|
141
202
|
elsif val.is_a?(Hash) && val["__op"] == "RemoveRelation" && val["objects"].present?
|
142
|
-
_collection = proxyKlass.new [], delegate: self, key: key, parse_class: (val[
|
203
|
+
_collection = proxyKlass.new [], delegate: self, key: key, parse_class: (val[Parse::Model::KEY_CLASS_NAME] || klassName)
|
143
204
|
_collection.loaded = true
|
144
205
|
_collection.remove val["objects"].parse_objects
|
145
206
|
val = _collection
|
@@ -150,11 +211,11 @@ module Parse
|
|
150
211
|
|
151
212
|
# send dirty tracking if set
|
152
213
|
if track == true
|
153
|
-
send
|
214
|
+
send will_change_method unless val == instance_variable_get( ivar )
|
154
215
|
end
|
155
216
|
# TODO: Only allow empty proxy collection class as a value or nil.
|
156
217
|
if val.is_a?(Parse::CollectionProxy)
|
157
|
-
instance_variable_set(
|
218
|
+
instance_variable_set(ivar, val)
|
158
219
|
else
|
159
220
|
warn "[#{self.class}] Invalid value #{val} for :has_many field #{key}. Should be an Array or a CollectionProxy"
|
160
221
|
end
|
@@ -185,7 +246,7 @@ module Parse
|
|
185
246
|
if self.method_defined?(parse_field) == false
|
186
247
|
alias_method parse_field, key
|
187
248
|
alias_method "#{parse_field}=", "#{key}="
|
188
|
-
alias_method "#{parse_field}_set_attribute!",
|
249
|
+
alias_method "#{parse_field}_set_attribute!", set_attribute_method
|
189
250
|
elsif parse_field.to_sym != :objectId
|
190
251
|
warn "Alias has_many method #{self}##{parse_field} already defined."
|
191
252
|
end
|
@@ -199,14 +260,14 @@ module Parse
|
|
199
260
|
self.class.relations
|
200
261
|
end
|
201
262
|
|
202
|
-
# returns a
|
263
|
+
# returns a hash of all the relation changes that have been performed on this
|
203
264
|
# instance.
|
204
265
|
def relation_updates
|
205
266
|
h = {}
|
206
267
|
changed.each do |key|
|
207
268
|
next unless relations[key.to_sym].present? && send(key).changed?
|
208
269
|
remote_field = self.field_map[key.to_sym] || key
|
209
|
-
h[remote_field] = send key
|
270
|
+
h[remote_field] = send key # we still need to send a proxy collection
|
210
271
|
end
|
211
272
|
h
|
212
273
|
end
|