parse-stack 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +15 -1
  3. data/Gemfile.lock +10 -10
  4. data/README.md +23 -9
  5. data/bin/console +3 -0
  6. data/lib/parse/api/analytics.rb +1 -1
  7. data/lib/parse/api/objects.rb +1 -1
  8. data/lib/parse/api/users.rb +1 -1
  9. data/lib/parse/client.rb +77 -40
  10. data/lib/parse/client/caching.rb +9 -5
  11. data/lib/parse/client/protocol.rb +47 -0
  12. data/lib/parse/client/request.rb +66 -37
  13. data/lib/parse/client/response.rb +39 -21
  14. data/lib/parse/model/acl.rb +4 -9
  15. data/lib/parse/model/associations/belongs_to.rb +97 -9
  16. data/lib/parse/model/associations/collection_proxy.rb +89 -29
  17. data/lib/parse/model/associations/has_many.rb +301 -28
  18. data/lib/parse/model/associations/has_one.rb +98 -4
  19. data/lib/parse/model/associations/pointer_collection_proxy.rb +48 -16
  20. data/lib/parse/model/associations/relation_collection_proxy.rb +61 -36
  21. data/lib/parse/model/bytes.rb +11 -5
  22. data/lib/parse/model/classes/installation.rb +50 -3
  23. data/lib/parse/model/classes/role.rb +7 -2
  24. data/lib/parse/model/classes/session.rb +21 -4
  25. data/lib/parse/model/classes/user.rb +122 -22
  26. data/lib/parse/model/core/actions.rb +7 -3
  27. data/lib/parse/model/core/properties.rb +14 -13
  28. data/lib/parse/model/core/querying.rb +16 -10
  29. data/lib/parse/model/core/schema.rb +2 -3
  30. data/lib/parse/model/date.rb +18 -12
  31. data/lib/parse/model/file.rb +77 -19
  32. data/lib/parse/model/geopoint.rb +70 -12
  33. data/lib/parse/model/model.rb +84 -8
  34. data/lib/parse/model/object.rb +225 -94
  35. data/lib/parse/model/pointer.rb +94 -13
  36. data/lib/parse/model/push.rb +76 -4
  37. data/lib/parse/query.rb +356 -41
  38. data/lib/parse/query/constraints.rb +399 -29
  39. data/lib/parse/query/ordering.rb +21 -8
  40. data/lib/parse/stack.rb +1 -0
  41. data/lib/parse/stack/version.rb +2 -1
  42. data/lib/parse/webhooks.rb +0 -24
  43. data/lib/parse/webhooks/payload.rb +54 -1
  44. data/lib/parse/webhooks/registration.rb +13 -2
  45. metadata +2 -2
@@ -19,4 +19,51 @@ module Parse
19
19
  CONTENT_TYPE_FORMAT = 'application/json; charset=utf-8'
20
20
  end
21
21
 
22
+ module ErrorCodes
23
+ # OtherCause -1 Error code indicating that an unknown error or an error unrelated to Parse occurred.
24
+ # 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.
25
+ # ConnectionFailed 100 Error code indicating the connection to the Parse servers failed.
26
+ # ObjectNotFound 101 Error code indicating the specified object doesn't exist.
27
+ # InvalidQuery 102 Error code indicating you tried to query with a datatype that doesn't support it, like exact matching an array or object.
28
+ # 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.
29
+ # MissingObjectId 104 Error code indicating an unspecified object id.
30
+ # 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.
31
+ # InvalidPointer 106 Error code indicating a malformed pointer. You should not see this unless you have been mucking about changing internal Parse code.
32
+ # 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.
33
+ # CommandUnavailable 108 Error code indicating that the feature you tried to access is only available internally for testing purposes.
34
+ # NotInitialized 109 You must call Parse.initialize before using the Parse library.
35
+ # IncorrectType 111 Error code indicating that a field was set to an inconsistent type.
36
+ # 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.
37
+ # PushMisconfigured 115 Error code indicating that push is misconfigured.
38
+ # ObjectTooLarge 116 Error code indicating that the object is too large.
39
+ # OperationForbidden 119 Error code indicating that the operation isn't allowed for clients.
40
+ # CacheMiss 120 Error code indicating the result was not found in the cache.
41
+ # InvalidNestedKey 121 Error code indicating that an invalid key was used in a nested JSONObject.
42
+ # 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.
43
+ # InvalidACL 123 Error code indicating an invalid ACL was provided.
44
+ # Timeout 124 Error code indicating that the request timed out on the server. Typically this indicates that the request is too expensive to run.
45
+ # InvalidEmailAddress 125 Error code indicating that the email address was invalid.
46
+ # DuplicateValue 137 Error code indicating that a unique field was given a value that is already taken.
47
+ # InvalidRoleName 139 Error code indicating that a role's name is invalid.
48
+ # ExceededQuota 140 Error code indicating that an application quota was exceeded. Upgrade to resolve.
49
+ # ScriptFailed 141 Error code indicating that a Cloud Code script failed.
50
+ # ValidationFailed 142 Error code indicating that a Cloud Code validation failed.
51
+ # FileDeleteFailed 153 Error code indicating that deleting a file failed.
52
+ # RequestLimitExceeded 155 Error code indicating that the application has exceeded its request limit.
53
+ # InvalidEventName 160 Error code indicating that the provided event name is invalid.
54
+ # UsernameMissing 200 Error code indicating that the username is missing or empty.
55
+ # PasswordMissing 201 Error code indicating that the password is missing or empty.
56
+ # UsernameTaken 202 Error code indicating that the username has already been taken.
57
+ # EmailTaken 203 Error code indicating that the email has already been taken.
58
+ # EmailMissing 204 Error code indicating that the email is missing, but must be specified.
59
+ # EmailNotFound 205 Error code indicating that a user with the specified email was not found.
60
+ # SessionMissing 206 Error code indicating that a user object without a valid session could not be altered.
61
+ # MustCreateUserThroughSignup 207 Error code indicating that a user can only be created through signup.
62
+ # AccountAlreadyLinked 208 Error code indicating that an an account being linked is already linked to another user.
63
+ # InvalidSessionToken 209 Error code indicating that the current session token is invalid.
64
+ # LinkedIdMissing 250 Error code indicating that a user cannot be linked to an account because that account's id could not be found.
65
+ # InvalidLinkedSession 251 Error code indicating that a user with a linked (e.g. Facebook) account has an invalid session.
66
+ # UnsupportedService 252 Error code indicating that a service being linked (e.g. Facebook or Twitter) is unsupported.
67
+ end
68
+
22
69
  end
@@ -5,52 +5,81 @@ require 'active_support'
5
5
  require 'active_support/json'
6
6
 
7
7
  module Parse
8
- #This class is mainly to create a potential request - mainly for the batching API.
9
-
8
+ #This class represents a Parse request.
10
9
  class Request
11
- attr_accessor :method, :path, :body, :headers, :opts, :cache
12
- attr_accessor :tag #for tracking in bulk requests
13
- def initialize(method, uri, body: nil, headers: nil, opts: {})
14
- @tag = 0
15
- method = method.downcase.to_sym
16
- unless method == :get || method == :put || method == :post || method == :delete
17
- raise ArgumentError, "Invalid method #{method} for request : '#{uri}'"
18
- end
19
- self.method = method
20
- self.path = uri
21
- self.body = body
22
- self.headers = headers || {}
23
- self.opts = opts || {}
24
- end
10
+ # @!attribute [rw] method
11
+ # @return [String] the HTTP method used for this request.
25
12
 
13
+ # @!attribute [rw] path
14
+ # @return [String] the uri path.
26
15
 
27
- def query
28
- body if @method == :get
29
- end
16
+ # @!attribute [rw] body
17
+ # @return [Hash] the body of this request.
30
18
 
31
- def as_json
32
- signature.as_json
33
- end
19
+ # TODO: Document opts and cache options.
34
20
 
35
- def ==(r)
36
- return false unless r.is_a?(Request)
37
- @method == r.method && @path == r.uri && @body == r.body && @headers == r.headers
38
- end
21
+ # @!attribute [rw] opts
22
+ # @return [Hash] a set of options for this request.
23
+ # @!attribute [rw] cache
24
+ # @return [Boolean]
25
+ attr_accessor :method, :path, :body, :headers, :opts, :cache
39
26
 
40
- # signature provies a way for us to compare different requests objects.
41
- # Two requests objects are the same if they have the same signature.
42
- # This also helps us serialize a request data into a hash.
43
- def signature
44
- {method: @method.upcase, path: @path, body: @body}
45
- end
27
+ # @!visibility private
28
+ # Used to correlate batching requests with their responses.
29
+ attr_accessor :tag
46
30
 
47
- def inspect
48
- "#<#{self.class} @method=#{@method} @path='#{@path}'>"
31
+ # Creates a new request
32
+ # @param method [String] the HTTP method
33
+ # @param uri [String] the API path of the request (without the host)
34
+ # @param body [Hash] the body (or parameters) of this request.
35
+ # @param headers [Hash] additional headers to send in this request.
36
+ # @param opts [Hash] additional optional parameters.
37
+ def initialize(method, uri, body: nil, headers: nil, opts: {})
38
+ @tag = 0
39
+ method = method.downcase.to_sym
40
+ unless method == :get || method == :put || method == :post || method == :delete
41
+ raise ArgumentError, "Invalid method #{method} for request : '#{uri}'"
49
42
  end
43
+ self.method = method
44
+ self.path = uri
45
+ self.body = body
46
+ self.headers = headers || {}
47
+ self.opts = opts || {}
48
+ end
50
49
 
51
- def to_s
52
- "#{@method.to_s.upcase} #{@path}"
53
- end
50
+ # The parameters of this request if the HTTP method is GET.
51
+ # @return [Hash]
52
+ def query
53
+ body if @method == :get
54
+ end
55
+
56
+ # @return [Hash] JSON encoded hash
57
+ def as_json
58
+ signature.as_json
59
+ end
60
+
61
+ # @return [Boolean]
62
+ def ==(r)
63
+ return false unless r.is_a?(Request)
64
+ @method == r.method && @path == r.uri && @body == r.body && @headers == r.headers
65
+ end
66
+
67
+ # Signature provies a way for us to compare different requests objects.
68
+ # Two requests objects are the same if they have the same signature.
69
+ # @return [Hash] A hash representing this request.
70
+ def signature
71
+ {method: @method.upcase, path: @path, body: @body}
72
+ end
73
+
74
+ # @!visibility private
75
+ def inspect
76
+ "#<#{self.class} @method=#{@method} @path='#{@path}'>"
77
+ end
78
+
79
+ # @return [String]
80
+ def to_s
81
+ "#{@method.to_s.upcase} #{@path}"
82
+ end
54
83
 
55
84
  end
56
85
 
@@ -53,7 +53,7 @@ require 'active_support/json'
53
53
  # be a set of responses (from a Batch response).
54
54
  module Parse
55
55
 
56
-
56
+ class ResponseError < StandardError; end;
57
57
  class Response
58
58
  include Enumerable
59
59
 
@@ -66,18 +66,36 @@ module Parse
66
66
  ERROR_PASSWORD_MISSING = 201
67
67
  ERROR_USERNAME_TAKEN = 202
68
68
  ERROR_EMAIL_TAKEN = 203
69
+ ERROR_EMAIL_NOT_FOUND = 205
70
+ ERROR_EMAIL_INVALID = 125
69
71
 
70
72
  ERROR = "error"
71
73
  CODE = "code"
72
74
  RESULTS = "results"
73
75
  COUNT = "count"
74
- # A response has a result or (a code and an error)
75
- attr_accessor :parse_class, :code, :error, :result, :http_status
76
- attr_accessor :request # capture request that created result
76
+
77
+ # @!attribute [rw] parse_class
78
+ # @return [String] the Parse class for this request
79
+ # @!attribute [rw] code
80
+ # @return [Integer] the error code
81
+ # @!attribute [rw] error
82
+ # @return [Integer] the error message
83
+ # @!attribute [rw] result
84
+ # @return [Hash] the body of the response result.
85
+ # @!attribute [rw] http_status
86
+ # @return [Integer] the HTTP status code from the response.
87
+ # @!attribute [rw] request
88
+ # @return [Integer] the Parse::Request that generated this response.
89
+ # @see Parse::Request
90
+ attr_accessor :parse_class, :code, :error, :result, :http_status,
91
+ :request
77
92
  # You can query Parse for counting objects, which may not actually have
78
93
  # results.
94
+ # @return [Integer] the count result from a count query request.
79
95
  attr_reader :count
80
96
 
97
+ # Create an instance with a Parse response JSON hash.
98
+ # @param res [Hash] the JSON hash
81
99
  def initialize(res = {})
82
100
  @http_status = 0
83
101
  @count = 0
@@ -86,7 +104,7 @@ module Parse
86
104
  # If a string is used for initializing, treat it as JSON
87
105
  res = JSON.parse(res) if res.is_a?(String)
88
106
  # If it is a hash (or parsed JSON), then parse the result.
89
- parse_result(res) if res.is_a?(Hash)
107
+ parse_result!(res) if res.is_a?(Hash)
90
108
  # if the result is an Array, then most likely it is a set of responses
91
109
  # from using a Batch API.
92
110
  if res.is_a?(Array)
@@ -99,21 +117,14 @@ module Parse
99
117
 
100
118
  end
101
119
 
120
+ # true if this was a batch response.
102
121
  def batch?
103
122
  @batch_response
104
123
  end
105
- #batch response
106
- #
107
- # [
108
- # {
109
- # "success":{"createdAt":"2015-11-22T19:04:16.104Z","objectId":"s4tEzOVQFc"}
110
- # },
111
- # {
112
- # "error":{"code":101,"error":"object not found for update"}
113
- # }
114
- # ]
124
+
115
125
  # If it is a batch respnose, we'll create an array of Response objects for each
116
126
  # of the ones in the batch.
127
+ # @return [Array] an array of Response objects.
117
128
  def batch_responses
118
129
 
119
130
  return [@result] unless @batch_response
@@ -128,8 +139,7 @@ module Parse
128
139
  # This method takes the result hash and determines if it is a regular
129
140
  # parse query result, object result or a count result. The response should
130
141
  # be a hash either containing the result data or the error.
131
-
132
- def parse_result(h)
142
+ def parse_result!(h)
133
143
  @result = {}
134
144
  return unless h.is_a?(Hash)
135
145
  @code = h[CODE]
@@ -143,31 +153,38 @@ module Parse
143
153
  end
144
154
 
145
155
  end
156
+ alias_method :parse_results!, :parse_result!
146
157
 
147
- # determines if the response is successful.
158
+ # true if the response is successful.
159
+ # @see #error?
148
160
  def success?
149
161
  @code.nil? && @error.nil?
150
162
  end
151
163
 
164
+ # true if the response has an error code.
165
+ # @see #success?
152
166
  def error?
153
167
  ! success?
154
168
  end
155
169
 
170
+ # true if the response has an error code of 'object not found'
171
+ # @see ERROR_OBJECT_NOT_FOUND
156
172
  def object_not_found?
157
173
  @code == ERROR_OBJECT_NOT_FOUND
158
174
  end
159
175
 
160
- # returns the result data from the response. Always returns an array.
176
+ # @return [Array] the result data from the response.
161
177
  def results
162
178
  return [] if @result.nil?
163
179
  @result.is_a?(Array) ? @result : [@result]
164
180
  end
165
181
 
166
- # returns the first thing in the array.
182
+ # @return [Object] the first thing in the result array.
167
183
  def first
168
184
  @result.is_a?(Array) ? @result.first : @result
169
185
  end
170
-
186
+ # Iterate through each result item.
187
+ # @yieldparam [Object] a result entry.
171
188
  def each
172
189
  return enum_for(:each) unless block_given?
173
190
  results.each(&Proc.new)
@@ -182,6 +199,7 @@ module Parse
182
199
  end
183
200
  end
184
201
 
202
+ # @return [String] JSON encoded object, or an error string.
185
203
  def to_s
186
204
  return "[E-#{@code}] #{@request} : #{@error} (#{@http_status})" if error?
187
205
  @result.to_json
@@ -12,9 +12,9 @@
12
12
  # JSON structure.
13
13
  # The class below also implements a type of delegate pattern in order to inform the main Parse::Object
14
14
  # of dirty tracking.
15
+
15
16
  module Parse
16
17
 
17
- # Base class for supporting custom data types
18
18
  class DataType
19
19
  include ::ActiveModel::Model
20
20
  include ::ActiveModel::Serializers::JSON
@@ -33,12 +33,10 @@ module Parse
33
33
  end
34
34
 
35
35
  class ACL < DataType
36
- # The internal permissions hash and delegate accessors
36
+
37
37
  attr_accessor :permissions, :delegate
38
- PUBLIC = "*" # Public priviledges are '*' key in Parse
38
+ PUBLIC = "*"
39
39
 
40
- # provide a set of acls and the delegate (for dirty tracking)
41
- # { '*' => { "read": true, "write": true } }
42
40
  def initialize(acls = {}, owner: nil)
43
41
  everyone(true, true) # sets Public read/write
44
42
  @delegate = owner
@@ -48,7 +46,6 @@ module Parse
48
46
 
49
47
  end
50
48
 
51
- # helper
52
49
  def self.permission(read, write = nil)
53
50
  ACL::Permission.new(read, write)
54
51
  end
@@ -63,15 +60,13 @@ module Parse
63
60
  permissions.keys.all? { |per| permissions[per] == other_acl.permissions[per] }
64
61
  end
65
62
 
66
- # method to set the Public read/write priviledges ('*'). Alias is 'world'
67
63
  def everyone(read, write)
68
64
  apply(PUBLIC, read, write)
69
65
  permissions[PUBLIC]
70
66
  end
71
67
  alias_method :world, :everyone
72
68
 
73
- # dirty tracking. We will tell the delegate through the acl_will_change!
74
- # method
69
+ # dirty tracking. We will tell the delegate through the acl_will_change! method
75
70
  def will_change!
76
71
  @delegate.acl_will_change! if @delegate.respond_to?(:acl_will_change!)
77
72
  end
@@ -6,20 +6,111 @@ require_relative 'collection_proxy'
6
6
  require_relative 'pointer_collection_proxy'
7
7
  require_relative 'relation_collection_proxy'
8
8
 
9
- # BelongsTo relation is the simplies association in which the local
10
- # table constains a column that points to a foreign table record using
11
- # a given Parse Pointer. The key of the property is implied to be the
12
- # name of the class/parse table that contains the foreign associated record.
13
- # All belongs to relationship column types have the special data type of :pointer.
9
+
14
10
  module Parse
15
11
  module Associations
16
-
12
+ # This association creates a one-to-one association with another Parse model.
13
+ # BelongsTo relation is the simplies association in which the local
14
+ # Parse table constains a column that has a Parse::Pointer to a foreign table record.
15
+ #
16
+ # This association says that this class contains a foreign pointer column
17
+ # which references a different class. Utilizing the `belongs_to` method in
18
+ # defining a property in a Parse::Object subclass sets up an association
19
+ # between the local table and a foreign table. Specifying the `belongs_to`
20
+ # in the class, tells the framework that the Parse table contains a local
21
+ # column in its schema that has a reference to a record in a foreign table.
22
+ # The argument to `belongs_to` should be the singularized version of the
23
+ # foreign Parse::Object class. you should specify the foreign table as the
24
+ # snake_case singularized version of the foreign table class.
25
+ #
26
+ # Note that the reverse relationship on the foreign class is not generated automatically.
27
+ # You can use a `has_one` on the foreign model to create it.
28
+ # @example
29
+ # class Author < Parse::Object
30
+ # property :name
31
+ # end
32
+ #
33
+ #
34
+ # class Post < Parse::Object
35
+ # belongs_to :author
36
+ # end
37
+ #
38
+ # Post.references # => {:author=>"Author"}
39
+ #
40
+ # post = Post.first
41
+ # post.author? # => true if has a pointer
42
+ #
43
+ # # Follow the author pointer and get name
44
+ # post.author.name
45
+ #
46
+ # other_author = Author.first
47
+ # # change author by setting new pointer
48
+ # post.author = other_author
49
+ # post.save
50
+ #
51
+ # @see Parse::Associations::HasOne
52
+ # @see Parse::Associations::HasMany
17
53
  module BelongsTo
18
54
 
55
+ # @!attribute [rw] self.references
56
+ # A hash mapping of all belongs_to associations for this model.
57
+ # @return [Hash]
58
+
59
+ # @!method key?
60
+ # A dynamically generated method based on the value of `key` passed to the
61
+ # belongs_to method, which returns true if this instance has a pointer for
62
+ # this field.
63
+ # @example
64
+ #
65
+ # class Post < Parse::Object
66
+ # belongs_to :author # generates 'author?'
67
+ # end
68
+ #
69
+ # post = Post.new
70
+ # post.author? # => false
71
+ # post.author = Author.new
72
+ # post.author? # => true
73
+ # @return [Boolean] true if field contains a Parse::Pointer or subclass.
74
+
75
+
76
+ # @!method self.belongs_to(key, opts = {})
77
+ # Creates a one-to-one association with another Parse model.
78
+ # @param [Symbol] key The singularized version of the foreign class and the name of the
79
+ # local column in the remote Parse table where the pointer is stored.
80
+ # @option opts [Symbol] :field override the name of the remote column
81
+ # where the pointer is stored. By default this is inferred as
82
+ # the columnized of the key parameter.
83
+ # @option opts [Symbol] :as override the inferred Parse::Object subclass.
84
+ # By default this is inferred as the singularized camel case version of
85
+ # the key parameter. This option allows you to override the typecast of
86
+ # foreign Parse model of the association, while allowing you to have a
87
+ # different accessor name.
88
+ # @option opts [Boolean] :required Setting to `true`, automatically creates
89
+ # an ActiveModel validation of `validates_presence_of` for the
90
+ # association. This will not prevent the save, but affects the validation
91
+ # check when `valid?` is called on an instance. Default is false.
92
+ # @example
93
+ # # Assumes 'Artist' is foreign class.
94
+ # belongs_to :artist
95
+ #
96
+ # # uses Parse::User as foreign class
97
+ # belongs_to :manager, as: :user
98
+ #
99
+ # # sets attribute name to `featured_song` for foreign class Song with the remote
100
+ # # column name in Parse as 'theFeaturedSong'.
101
+ # belongs_to :featured_song, as: :song, field: :theFeaturedSong
102
+ #
103
+ # @see String#columnize
104
+ # @see #key?
105
+ # @return [Parse::Object] a Parse::Object subclass when using the accessor
106
+ # when fetching the association.
107
+
108
+ # @!visibility private
19
109
  def self.included(base)
20
110
  base.extend(ClassMethods)
21
111
  end
22
112
 
113
+ # @!visibility private
23
114
  module ClassMethods
24
115
  attr_accessor :references
25
116
  # We can keep references to all "belong_to" properties
@@ -27,9 +118,6 @@ module Parse
27
118
  @references ||= {}
28
119
  end
29
120
 
30
- # belongs_to :featured_song, as: :song, field: :featuredSong, through: :reference
31
- # belongs_to :artist
32
- # belongs_to :manager, as: :user
33
121
  # These items are added as attributes with the special data type of :pointer
34
122
  def belongs_to(key, opts = {})
35
123
  opts = {as: key, field: key.to_s.camelize(:lower), required: false}.merge(opts)