parse-stack 1.3.7 → 1.3.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36eda4b2e4b7cfabe79550eb3c33345f6d9fdaaf
4
- data.tar.gz: efdef75bc9457d307563f5e0e254733cb2c638fa
3
+ metadata.gz: 18eab2bb5c64686e9b7713d4083db00fd5b7d026
4
+ data.tar.gz: 21cc6c542518c284edcd90c8ff8c09df4883dd30
5
5
  SHA512:
6
- metadata.gz: dc156d8689d64921540fb44c3fc41446c4bfc67ea869d6f6c73e28a32ae0398089bdd2b7691c9df0ccce6cfcc893abbf872090439906580edc7c3bd9e1568075
7
- data.tar.gz: b07ef6631868a733b0967fcf0e3cad592f46e179d451e61d1ea3826130f0f00a9e85861796d1a49d2db0bbe160fb1e680a6884e7c46749f5026beb9d9b8090bc
6
+ metadata.gz: 38a5decb6fbbb89caa454ac583ea9cfbad0929a59bbfa78b86befc00bb53a861f040951ce9bb6013a6b03bb0fb086bd98438c00927da2030a66cb57798159858
7
+ data.tar.gz: 8c16ed21c5c450270382d8223d010978de8950cbd9cfaf3172d2915f1e81d73a151b55176b61e41f1fbd662fa6a8c1554e620650c9a92aec84cdb26446fd26b3
data/.gitignore CHANGED
@@ -37,3 +37,5 @@ build/
37
37
  .rvmrc
38
38
  bin/.env
39
39
  /.byebug_history
40
+ /.env
41
+ /node_modules
data/Changes.md CHANGED
@@ -1,12 +1,21 @@
1
1
  # Parse-Stack Changes
2
2
 
3
+ 1.3.8
4
+ -----------
5
+ - Support for reloading the Parse config data with `Parse.config!`.
6
+ - The Parse::Request object is now provided in the Parse::Response instance.
7
+ - The HTTP status code is provided in `http_status` accessor for a Parse::Response.
8
+ - Raised errors now provide info on the request that failed.
9
+ - Added new `ServiceUnavailableError` exception for Parse error code 2 and HTTP 503 errors.
10
+ - Upon a `ServiceUnavailableError`, we will retry the request one more time after 2 seconds.
11
+ - `:not_in` and `:contains_all` queries will format scalar values into an array.
12
+ - `:exists` and `:null` will raise `ParseConstraintError` if non-boolean values are passed.
13
+ - NEW: `:id` constraint to allow passing an objectId to a query where we will infer the class.
14
+
3
15
  1.3.7
4
16
  -----------
5
17
  - Fixes json_api loading issue between ruby json and active_model_serializers.
6
18
  - Fixes loading active_support core extensions.
7
-
8
- 1.3.5
9
- -----------
10
19
  - Support for passing a `:session_token` as part of a Parse::Query.
11
20
  - Default mime-type for Parse::File instances is `image/jpeg`. You can override the default by setting
12
21
  `Parse::File.default_mime_type`.
@@ -19,10 +28,9 @@
19
28
  - Added `$within` => `$box` GeoPoint query. (see documentation)
20
29
  - Improves support when using Parse-Server.
21
30
  - Major documentation updates.
22
- - `limit` no longer defaults to 100 in `Parse::Query`. This will allow Parse-Server to determine default limit, if any. (1.3.4)
23
- - `:bool` property type has been added as an alias to `:boolean`. (1.3.5)
24
- - You can turn off formatting field names with `Parse::Query.field_formatter = nil`. (1.3.5)
25
-
31
+ - `limit` no longer defaults to 100 in `Parse::Query`. This will allow Parse-Server to determine default limit, if any.
32
+ - `:bool` property type has been added as an alias to `:boolean`.
33
+ - You can turn off formatting field names with `Parse::Query.field_formatter = nil`.
26
34
 
27
35
  1.3.1
28
36
  -----------
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- parse-stack (1.3.7)
4
+ parse-stack (1.3.8)
5
5
  active_model_serializers (>= 0.9, < 1)
6
6
  activemodel (>= 4.2.1, < 6)
7
7
  activesupport (>= 4.2.1, < 6)
@@ -14,15 +14,15 @@ PATH
14
14
  GEM
15
15
  remote: https://rubygems.org/
16
16
  specs:
17
- actionpack (5.0.0)
18
- actionview (= 5.0.0)
19
- activesupport (= 5.0.0)
17
+ actionpack (5.0.0.1)
18
+ actionview (= 5.0.0.1)
19
+ activesupport (= 5.0.0.1)
20
20
  rack (~> 2.0)
21
21
  rack-test (~> 0.6.3)
22
22
  rails-dom-testing (~> 2.0)
23
23
  rails-html-sanitizer (~> 1.0, >= 1.0.2)
24
- actionview (5.0.0)
25
- activesupport (= 5.0.0)
24
+ actionview (5.0.0.1)
25
+ activesupport (= 5.0.0.1)
26
26
  builder (~> 3.1)
27
27
  erubis (~> 2.7.0)
28
28
  rails-dom-testing (~> 2.0)
@@ -32,9 +32,9 @@ GEM
32
32
  activemodel (>= 4.1, < 6)
33
33
  jsonapi (~> 0.1.1.beta2)
34
34
  railties (>= 4.1, < 6)
35
- activemodel (5.0.0)
36
- activesupport (= 5.0.0)
37
- activesupport (5.0.0)
35
+ activemodel (5.0.0.1)
36
+ activesupport (= 5.0.0.1)
37
+ activesupport (5.0.0.1)
38
38
  concurrent-ruby (~> 1.0, >= 1.0.2)
39
39
  i18n (~> 0.7)
40
40
  minitest (~> 5.1)
@@ -85,9 +85,9 @@ GEM
85
85
  nokogiri (~> 1.6.0)
86
86
  rails-html-sanitizer (1.0.3)
87
87
  loofah (~> 2.0)
88
- railties (5.0.0)
89
- actionpack (= 5.0.0)
90
- activesupport (= 5.0.0)
88
+ railties (5.0.0.1)
89
+ actionpack (= 5.0.0.1)
90
+ activesupport (= 5.0.0.1)
91
91
  method_source
92
92
  rake (>= 0.8.7)
93
93
  thor (>= 0.18.1, < 2.0)
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Parse-Stack - A Parse-Server Ruby Client and ORM
2
- Parse Stack is an opinionated framework for ruby applications that utilize the [Parse Server Platform](https://github.com/ParsePlatform/parse-server). It provides a client adapter, a query engine, an object relational mapper (ORM) and a Cloud Code Webhooks rack application.
2
+ Parse-Stack is a [Parse Server](https://github.com/ParsePlatform/parse-server) REST API Client and ORM framework for ruby. It provides a client adapter, a query engine, an object relational mapper (ORM) and a Cloud Code Webhooks rack application.
3
3
 
4
4
  ### Code Status
5
5
  [![Gem Version](https://badge.fury.io/rb/parse-stack.svg)](https://badge.fury.io/rb/parse-stack)
@@ -17,8 +17,10 @@ Parse Stack is an opinionated framework for ruby applications that utilize the [
17
17
  - [Parse::Query](#parsequery)
18
18
  - [Parse::Object](#parseobject)
19
19
  - [Parse::Webhooks](#parsewebhooks)
20
+ - [Field Naming Conventions](#field-naming-conventions)
20
21
  - [Connection Setup](#connection-setup)
21
22
  - [Connection Options](#connection-options)
23
+ - [Parse Config](#parse-config)
22
24
  - [Core Classes](#core-classes)
23
25
  - [Parse::Pointer](#parsepointer)
24
26
  - [Parse::File](#parsefile)
@@ -26,6 +28,10 @@ Parse Stack is an opinionated framework for ruby applications that utilize the [
26
28
  - [Parse::GeoPoint](#parsegeopoint)
27
29
  - [Calculating Distances between locations](#calculating-distances-between-locations)
28
30
  - [Parse::Bytes](#parsebytes)
31
+ - [Parse::User](#parseuser)
32
+ - [Parse::Installation](#parseinstallation)
33
+ - [Parse::Session](#parsesession)
34
+ - [Parse::Role](#parserole)
29
35
  - [Modeling and Subclassing](#modeling-and-subclassing)
30
36
  - [Defining Properties](#defining-properties)
31
37
  - [Accessor Aliasing](#accessor-aliasing)
@@ -73,6 +79,7 @@ Parse Stack is an opinionated framework for ruby applications that utilize the [
73
79
  - [Reject](#reject)
74
80
  - [Matches Query](#matches-query)
75
81
  - [Excludes Query](#excludes-query)
82
+ - [Matches Object Id](#matches-object-id)
76
83
  - [Geo Queries](#geo-queries)
77
84
  - [Max Distance Constraint](#max-distance-constraint)
78
85
  - [Bounding Box Constraint](#bounding-box-constraint)
@@ -119,16 +126,27 @@ class Song < Parse::Object
119
126
  has_many :likes, as: :user, through: :relation
120
127
  end
121
128
 
122
- # create tables or add new columns (non-destructive)
123
- Song.auto_upgrade!
129
+ class Artist < Parse::Object
130
+ property :name
131
+ property :genres, :array
132
+ end
133
+
134
+ # updates schemas for your Parse app based on your models (non-destructive)
135
+ Parse.auto_upgrade!
124
136
 
137
+ artist = Artist.new(name: "Frank Sinatra", genres: ["swing", "jazz"])
138
+ artist.save
139
+
140
+ # Query
125
141
  artist = Artist.first(:name.like => /Sinatra/, :genres.in => ['swing'])
126
142
 
143
+ # more examples
127
144
  song = Song.new name: "Fly Me to the Moon"
128
145
  song.artist = artist
129
- # Parse files
146
+ # Parse files - upload a file and attach to object
130
147
  song.audio_file = Parse::File.create("http://path_to.mp3")
131
- # relations
148
+
149
+ # relations - find a User matching username and add it to relation.
132
150
  song.likes.add Parse::User.first(username: "persaud")
133
151
 
134
152
  # saves both attributes and relations
@@ -149,6 +167,7 @@ result = Parse.call_function :myFunctionName, {param: value}
149
167
  ## Main Features
150
168
  While there are many additional features of the framework, these are the main points.
151
169
 
170
+ - Support for all Parse Query constraints.
152
171
  - Object Relational Mapping with dirty tracking.
153
172
  - Easy management of Parse GeoPoints, Files and ACLs.
154
173
  - Parse Queries support with caching middleware. (Reduces API usage)
@@ -253,6 +272,19 @@ Sets the default cache expiration time (in seconds) for successful non-empty `GE
253
272
  #### `:faraday`
254
273
  You may pass a hash of options that will be passed to the `Faraday` constructor.
255
274
 
275
+ ## Parse Config
276
+ Getting your configuration variables once you have a default client setup can be done with `Parse.config`. The first time this method is called, Parse-Stack will get the configuration from Parse-Server, and cache it. To force a reload of the config, use `config!`.
277
+
278
+ ```ruby
279
+ Parse.setup( ... )
280
+
281
+ val = Parse.config["myKey"]
282
+ val = Parse.config["myKey"] # cached
283
+
284
+ # Force fetch of config!
285
+ val = Parse.config!["myKey"]
286
+
287
+ ```
256
288
 
257
289
  ## Core Classes
258
290
  While some native data types are similar to the ones supported by Ruby natively, other ones are more complex and require their dedicated classes.
@@ -381,6 +413,63 @@ The `Bytes` data type represents the storage format for binary content in a Pars
381
413
  decoded = bytes.decoded # same as Base64.decode64
382
414
  ```
383
415
 
416
+ ### Parse::User
417
+ This class represents the data and columns contained in the standard Parse `_User` collection. You may add additional properties and methods to this class. It is defined as follows:
418
+
419
+ ```ruby
420
+ class Parse::User < Parse::Object
421
+ property :auth_data, :object
422
+ property :email
423
+ property :password
424
+ property :username
425
+ end
426
+ ```
427
+
428
+ ### Parse::Installation
429
+ This class represents the data and columns contained in the standard Parse `_Installation` collection. You may add additional properties and methods to this class. It is defined as follows:
430
+
431
+ ```ruby
432
+ class Parse::Installation < Parse::Object
433
+ property :channels, :array
434
+ property :gcm_sender_id, :string, field: :GCMSenderId
435
+ property :badge, :integer
436
+ property :installation_id
437
+ property :device_token
438
+ property :device_type
439
+ property :locale_identifier
440
+ property :push_type
441
+ property :time_zone
442
+ end
443
+ ```
444
+
445
+ ### Parse::Session
446
+ This class represents the data and columns contained in the standard Parse `_Session` collection. You may add additional properties and methods to this class. It is defined as follows:
447
+
448
+ ```ruby
449
+ class Parse::Session < Parse::Object
450
+ property :created_with, :object
451
+ property :expires_at, :date
452
+ property :installation_id
453
+ property :restricted, :boolean
454
+ property :session_token
455
+
456
+ belongs_to :user
457
+ end
458
+ ```
459
+
460
+ ### Parse::Role
461
+ This class represents the data and columns contained in the standard Parse `_Role` collection. You may add additional properties and methods to this class. It is defined as follows:
462
+
463
+ ```ruby
464
+ class Parse::Role < Parse::Object
465
+ property :name
466
+
467
+ has_many :roles, through: :relation
468
+ has_many :users, through: :relation
469
+ end
470
+ ```
471
+
472
+
384
473
  ## Modeling and Subclassing
385
474
  For the general case, your Parse classes should inherit from `Parse::Object`. `Parse::Object` utilizes features from `ActiveModel` to add several features to each instance of your subclass. These include `Dirty`, `Conversion`, `Callbacks`, `Naming` and `Serializers::JSON`.
386
475
 
@@ -1134,6 +1223,17 @@ q.where :field => value
1134
1223
  # (alias) :field.eq => value
1135
1224
  ```
1136
1225
 
1226
+ If you want to see if a particular field contains a specific Parse::Object (pointer), you can use the following:
1227
+
1228
+ ```ruby
1229
+ # find rows where the `field` contains a Parse "_User" pointer with the specified objectId.
1230
+ q.where :field => Parse::Pointer.new("_User", "anObjectId")
1231
+ # alias using subclass helper
1232
+ q.where :field => Parse::User.pointer("anObjectId")
1233
+ # alias using `:id` constraint. We will infer :user maps to class "_User" (Parse::User)
1234
+ q.where :user.id => "anObjectId"
1235
+ ```
1236
+
1137
1237
  #### Less Than
1138
1238
  Equivalent to the `$lt` Parse query operation. The alias `before` is provided for readability.
1139
1239
 
@@ -1196,7 +1296,7 @@ q.where :field.exists => true|false
1196
1296
  ```
1197
1297
 
1198
1298
  #### Contained In
1199
- Equivalent to the `$in` Parse query operation. Checks whether the value in the column field is contained in the set of values in the array.
1299
+ Equivalent to the `$in` Parse query operation. Checks whether the value in the column field is contained in the set of values in the target array. If the field is an array data type, it checks whether at least one value in the field array is contained in the set of values in the target array.
1200
1300
 
1201
1301
  ```ruby
1202
1302
  # ex. :score.in => [1,3,5,7,9]
@@ -1206,7 +1306,7 @@ q.where :field.contained_in => [item1,item2,...]
1206
1306
  ```
1207
1307
 
1208
1308
  #### Not Contained In
1209
- Equivalent to the `$nin` Parse query operation. Checks whether the value in the column field is __not__ contained in the set of values in the array.
1309
+ Equivalent to the `$nin` Parse query operation. Checks whether the value in the column field is __not__ contained in the set of values in the target array. If the field is an array data type, it checks whether at least one value in the field array is __not__ contained in the set of values in the target array.
1210
1310
 
1211
1311
  ```ruby
1212
1312
  # ex. :player_name.not_in => ['Jonathan', 'Dario', 'Shawn']
@@ -1216,7 +1316,7 @@ q.where :field.not_contained_in => [item1,item2,...]
1216
1316
  ```
1217
1317
 
1218
1318
  #### Contains All
1219
- Equivalent to the `$all` Parse query operation. Checks whether the value in the column field contains all of the given values provided in the array. Note that the `field` column must be of type `Array` in your Parse class.
1319
+ Equivalent to the `$all` Parse query operation. Checks whether the value in the column field contains all of the given values provided in the array. Note that the `field` column should be of type `Array` in your Parse class.
1220
1320
 
1221
1321
  ```ruby
1222
1322
  # ex. :array_key.all => [2,3,4]
@@ -1283,6 +1383,48 @@ q.where :field.excludes => query
1283
1383
  q.where :field.not_in_query => query # alias
1284
1384
  ```
1285
1385
 
1386
+ #### Matches Object Id
1387
+ Sometimes you want to find rows where a particular Parse object exists. You can do so by passing a the Parse::Object subclass, a Parse::Pointer. You can also use the `id` constraint. This will assume that the name of the field matches a particular Parse class you have defined. Assume the following:
1388
+
1389
+ ```ruby
1390
+ # where this Parse object equals the object in the column `field`.
1391
+ q.where :field => Parse::Pointer("Field", "someObjectId")
1392
+ # alias, shorthand when we infer `:field` maps to `Field` parse class.
1393
+ q.where :field.id => "someObjectId"
1394
+ # "field":{"__type":"Pointer","className":"Field","objectId":"someObjectId"}}
1395
+
1396
+ ```
1397
+
1398
+ ##### Additional Examples
1399
+
1400
+ ```ruby
1401
+
1402
+ class Artist < Parse::Object
1403
+ # as described before
1404
+ end
1405
+
1406
+ class Song < Parse::Object
1407
+ belongs_to :artist
1408
+ end
1409
+
1410
+ artist = Artist.first # get any artist
1411
+ artist_id = artist.id # ex. artist.id
1412
+
1413
+ # find all songs for this artist object
1414
+ Song.all :artist => artist
1415
+ ```
1416
+
1417
+ In some cases, you do not have the Parse object, but you have its `objectId`. You can use the objectId in the query as follows:
1418
+
1419
+ ```ruby
1420
+ # shorthand if you are using convention. Will infer class `Artist`
1421
+ Song.all :artist.id => artist_id
1422
+
1423
+ # other approaches, same result
1424
+ Song.all :artist => Artist.pointer(artist_id)
1425
+ Song.all :artist => Parse::Pointer.new("Artist", artist_id)
1426
+ ```
1427
+
1286
1428
  ### Geo Queries
1287
1429
  Equivalent to the `$nearSphere` Parse query operation. This is only applicable if the field is of type `GeoPoint`. This will query Parse and return a list of results ordered by distance with the nearest object being first.
1288
1430
 
@@ -1446,7 +1588,7 @@ Push notifications are implemented through the `Parse::Push` class. To send push
1446
1588
  ```
1447
1589
 
1448
1590
  ## Cloud Code Webhooks
1449
- Parse Parse allows you to receive Cloud Code webhooks on your own hosted server. The `Parse::Webhooks` class is a lightweight Rack application that routes incoming Cloud Code webhook requests and payloads to locally registered handlers. The payloads are `Parse::Payload` type of objects that represent that data that Parse sends webhook handlers. You can register any of the Cloud Code webhook trigger hooks (`beforeSave`, `afterSave`, `beforeDelete`, `afterDelete`) and function hooks. If you are using the open source [Parse Server](https://github.com/ParsePlatform/parse-server), you must enable this hooks feature by enabling the environment variable `PARSE_EXPERIMENTAL_HOOKS_ENABLED` on your Parse server.
1591
+ Parse Parse allows you to receive Cloud Code webhooks on your own hosted server. The `Parse::Webhooks` class is a lightweight Rack application that routes incoming Cloud Code webhook requests and payloads to locally registered handlers. The payloads are `Parse::Payload` type of objects that represent that data that Parse sends webhook handlers. You can register any of the Cloud Code webhook trigger hooks (`beforeSave`, `afterSave`, `beforeDelete`, `afterDelete`) and function hooks.
1450
1592
 
1451
1593
  ### Cloud Code functions
1452
1594
  You can use the `route()` method to register handler blocks. The last value returned by the block will be returned back to the client in a success response. If `error!(value)` is called inside the block, we will return the correct Parse error response with the value you provided.
data/Rakefile CHANGED
@@ -11,3 +11,8 @@ Rake::TestTask.new do |t|
11
11
  end
12
12
 
13
13
  task :default => :test
14
+
15
+ task :console do
16
+ exec("./bin/console")
17
+ end
18
+ task :c => :console
@@ -3,9 +3,12 @@
3
3
  require "bundler/setup"
4
4
  require "parse/stack"
5
5
  require 'dotenv'
6
+ require 'byebug'
6
7
  Dotenv.load
7
8
 
8
9
 
10
+ Parse.setup
11
+
9
12
  class Song < Parse::Object
10
13
  property :name
11
14
  end
@@ -5,6 +5,11 @@ module Parse
5
5
  #object fetch methods
6
6
  module Config
7
7
  attr_accessor :config
8
+
9
+ def config!
10
+ @config = nil
11
+ self.config
12
+ end
8
13
 
9
14
  def config
10
15
  if @config.nil?
@@ -23,6 +23,7 @@ module Parse
23
23
  class TimeoutError < Exception; end;
24
24
  class ProtocolError < Exception; end;
25
25
  class ServerError < Exception; end;
26
+ class ServiceUnavailableError < Exception; end;
26
27
  class AuthenticationError < Exception; end;
27
28
  class RequestLimitExceededError < Exception; end;
28
29
  class InvalidSessionTokenError < Exception; end;
@@ -32,6 +33,10 @@ module Parse
32
33
  Parse::Client.session(s).config
33
34
  end
34
35
 
36
+ def self.config!(s = :default)
37
+ Parse::Client.session(s).config!
38
+ end
39
+
35
40
  # Main class for the client. The client class is based on a Faraday stack.
36
41
  # The Faraday stack is similar to a Rack-style application in which you can define middlewares
37
42
  # that will be called for each request going out and coming back in. We use this in order to setup
@@ -48,6 +53,8 @@ module Parse
48
53
  include Parse::API::Batch
49
54
  include Parse::API::Push
50
55
  include Parse::API::Schema
56
+ RETRY_COUNT = 2
57
+ RETRY_DELAY = 3 #seconds
51
58
 
52
59
  attr_accessor :session, :cache
53
60
  attr_reader :application_id, :api_key, :master_key, :server_url
@@ -89,9 +96,9 @@ module Parse
89
96
  # :host - defaults to Parse::Protocol::SERVER_URL (https://api.parse.com/1/)
90
97
  def initialize(opts = {})
91
98
  @server_url = opts[:server_url] || ENV["PARSE_SERVER_URL"] || Parse::Protocol::SERVER_URL
92
- @application_id = opts[:app_id] || opts[:application_id] || ENV["PARSE_APP_ID"]
93
- @api_key = opts[:api_key] || ENV["PARSE_API_KEY"]
94
- @master_key = opts[:master_key] || ENV["PARSE_MASTER_KEY"]
99
+ @application_id = opts[:application_id] || opts[:app_id] || ENV["PARSE_APP_ID"] || ENV['PARSE_SERVER_APPLICATION_ID']
100
+ @api_key = opts[:api_key] || opts[:rest_api_key] || ENV["PARSE_API_KEY"] || ENV["PARSE_REST_API_KEY"]
101
+ @master_key = opts[:master_key] || ENV["PARSE_MASTER_KEY"] || ENV['PARSE_SERVER_MASTER_KEY']
95
102
  opts[:adapter] ||= Faraday.default_adapter
96
103
  opts[:expires] ||= 3
97
104
  if @application_id.nil? || ( @api_key.nil? && @master_key.nil? )
@@ -142,6 +149,10 @@ module Parse
142
149
  self
143
150
  end
144
151
 
152
+ def app_id
153
+ @application_id
154
+ end
155
+
145
156
  def url_prefix
146
157
  @session.url_prefix
147
158
  end
@@ -160,15 +171,19 @@ module Parse
160
171
  # with the header: paramter (also a hash).
161
172
  # This method also takes in a Parse::Request object instead of the arguments listed above.
162
173
  def request(method, uri = nil, body: nil, query: nil, headers: nil, opts: {})
174
+ retry_count ||= RETRY_COUNT
163
175
  headers ||= {}
164
176
  # if the first argument is a Parse::Request object, then construct it
177
+ _request = nil
165
178
  if method.is_a?(Request)
166
- request = method
167
- method = request.method
168
- uri ||= request.path
169
- query ||= request.query
170
- body ||= request.body
171
- headers.merge! request.headers
179
+ _request = method
180
+ method = _request.method
181
+ uri ||= _request.path
182
+ query ||= _request.query
183
+ body ||= _request.body
184
+ headers.merge! _request.headers
185
+ else
186
+ _request = Parse::Request.new(method, uri, body: body, headers: headers, opts: opts)
172
187
  end
173
188
 
174
189
  # http method
@@ -196,46 +211,62 @@ module Parse
196
211
  params = (method == :get ? query : body) || {}
197
212
  # if the path does not start with the '/1/' prefix, then add it to be nice.
198
213
  # actually send the request and return the body
199
- response = @session.send(method, uri, params, headers)
200
- body = response.body
214
+ response_env = @session.send(method, uri, params, headers)
215
+ response = response_env.body
216
+ response.request = _request
201
217
 
202
- case response.status
218
+ case response.http_status
203
219
  when 401, 403
204
- puts "[ParseError] #{body}"
205
- raise Parse::AuthenticationError, body
220
+ puts "[Parse:AuthenticationError] #{response}"
221
+ raise Parse::AuthenticationError, response
206
222
  when 400, 408
207
- puts "[ParseError] #{body}"
208
- if body.code == 124 || body.code == 143 #"net/http: timeout awaiting response headers"
209
- raise Parse::TimeoutError, body
223
+ if response.code == Parse::Response::ERROR_TIMEOUT || response.code == 143 #"net/http: timeout awaiting response headers"
224
+ puts "[Parse:TimeoutError] #{response}"
225
+ raise Parse::TimeoutError, response
210
226
  end
211
227
  when 404
212
- unless body.object_not_found?
213
- puts "[ParseError] #{body}"
214
- raise Parse::ConnectionError, body
228
+ unless response.object_not_found?
229
+ puts "[Parse:ConnectionError] #{response}"
230
+ raise Parse::ConnectionError, response
215
231
  end
216
232
  when 405, 406
217
- puts "[ParseError] #{body}"
218
- raise Parse::ProtocolError, body
233
+ puts "[Parse:ProtocolError] #{response}"
234
+ raise Parse::ProtocolError, response
219
235
  when 500
220
- puts "[ParseError] #{body}"
221
- raise Parse::ServerError, body
236
+ puts "[Parse:ServiceUnavailableError] #{response}"
237
+ raise Parse::ServiceUnavailableError, response
238
+ when 503
239
+ puts "[Parse:ServiceUnavailableError] #{response}"
240
+ raise Parse::ServiceUnavailableError, response
222
241
  end
223
242
 
224
- if body.error?
225
- if body.code <= 100
226
- puts "[ParseError] #{body}"
227
- raise Parse::ServerError, body
228
- elsif body.code == 155
229
- puts "[ParseError] #{body}"
230
- raise Parse::RequestLimitExceededError, body
231
- elsif body.code == 209 #Error 209: invalid session token
232
- puts "[ParseError] #{body}"
233
- raise Parse::InvalidSessionTokenError, body
243
+ if response.error?
244
+ if response.code <= Parse::Response::ERROR_SERVICE_UNAVAILALBE
245
+ puts "[Parse:ServiceUnavailableError] #{response}"
246
+ raise Parse::ServiceUnavailableError, response
247
+ elsif response.code <= 100
248
+ puts "[Parse:ServerError] #{response}"
249
+ raise Parse::ServerError, response
250
+ elsif response.code == Parse::Response::ERROR_EXCEEDED_BURST_LIMIT
251
+ puts "[Parse:RequestLimitExceededError] #{response}"
252
+ raise Parse::RequestLimitExceededError, response
253
+ elsif response.code == 209 #Error 209: invalid session token
254
+ puts "[Parse:InvalidSessionTokenError] #{response}"
255
+ raise Parse::InvalidSessionTokenError, response
234
256
  end
235
257
  end
236
258
 
237
- body
259
+ response
260
+ rescue Parse::ServiceUnavailableError => e
261
+ if retry_count > 0
262
+ puts "[Parse:ServiceUnavailableError] Retries remaining #{retry_count} : #{response.request}"
263
+ sleep RETRY_DELAY
264
+ retry_count -= 1
265
+ retry
266
+ end
267
+ raise e
238
268
  rescue Faraday::Error::ClientError, Net::OpenTimeout => e
269
+ puts "[Parse:ConnectionError] Faraday/Net:OpenTimeout : #{e.message}"
239
270
  raise Parse::ConnectionError, e.message
240
271
  end
241
272
 
@@ -49,7 +49,7 @@ module Parse
49
49
  # of the env
50
50
  # TODO: CHECK FOR HTTP STATUS CODES
51
51
  if self.class.logging
52
- puts "[[Response ]] --------------------------------------"
52
+ puts "[[Response #{response_env[:status]}]] ----------------------------------"
53
53
  puts response_env.body
54
54
  puts "[[Response]] --------------------------------------\n"
55
55
  end
@@ -59,9 +59,9 @@ module Parse
59
59
  rescue Exception => e
60
60
  r = Parse::Response.new
61
61
  r.code = response_env.status
62
- r.error = "Invalid Parse Response: #{e}"
62
+ r.error = "Invalid response for #{env[:method]} #{env[:url]}: #{e}"
63
63
  end
64
-
64
+ r.http_status = response_env[:status]
65
65
  r.code ||= response_env[:status] if r.error.present?
66
66
  response_env[:body] = r
67
67
  end
@@ -10,8 +10,10 @@ module Parse
10
10
  def initialize(method, uri, body: nil, headers: nil, opts: {})
11
11
  @tag = 0
12
12
  method = method.downcase.to_sym
13
- raise "Invalid Method type #{method} " unless [:get,:put,:delete,:post].include?(method)
14
- self.method = method.downcase
13
+ unless method == :get || method == :put || method == :post || method == :delete
14
+ raise "Invalid method #{method} for request : '#{uri}'"
15
+ end
16
+ self.method = method
15
17
  self.path = uri
16
18
  self.body = body
17
19
  self.headers = headers || {}
@@ -39,6 +41,14 @@ module Parse
39
41
  {method: @method.upcase, path: @path, body: @body}
40
42
  end
41
43
 
44
+ def inspect
45
+ "#<#{self.class} @method=#{@method} @path='#{@path}'>"
46
+ end
47
+
48
+ def to_s
49
+ "#{@method.to_s.upcase} #{@path}"
50
+ end
51
+
42
52
  end
43
53
 
44
54
  end
@@ -9,6 +9,7 @@ module Parse
9
9
  include Enumerable
10
10
 
11
11
  ERROR_INTERNAL = 1
12
+ ERROR_SERVICE_UNAVAILALBE = 2
12
13
  ERROR_TIMEOUT = 124
13
14
  ERROR_EXCEEDED_BURST_LIMIT = 155
14
15
  ERROR_OBJECT_NOT_FOUND = 101
@@ -18,12 +19,14 @@ module Parse
18
19
  RESULTS = "results".freeze
19
20
  COUNT = "count".freeze
20
21
  # A response has a result or (a code and an error)
21
- attr_accessor :parse_class, :code, :error, :result
22
+ attr_accessor :parse_class, :code, :error, :result, :http_status
23
+ attr_accessor :request # capture request that created result
22
24
  # You can query Parse for counting objects, which may not actually have
23
25
  # results.
24
26
  attr_reader :count
25
27
 
26
28
  def initialize(res = {})
29
+ @http_status = 0
27
30
  @count = 0
28
31
  @batch_response = false # by default, not a batch response
29
32
  @result = nil
@@ -127,8 +130,8 @@ module Parse
127
130
  end
128
131
 
129
132
  def to_s
130
- return "E-#{@code}: #{@error}" if error?
131
- @result
133
+ return "[E-#{@code}] #{@request} : #{@error} (#{@http_status})" if error?
134
+ @result.to_json
132
135
  end
133
136
 
134
137
  end
@@ -68,7 +68,7 @@ module Parse
68
68
  return Parse::Date if str == TYPE_DATE.freeze
69
69
  # return Parse::User if str == "User".freeze
70
70
  # return Parse::Installation if str == "Installation".freeze
71
-
71
+
72
72
  str = str.to_s
73
73
  # Basically go through all Parse::Object subclasses and see who is has a parse_class
74
74
  # set to this string. We will cache the results for future use.
@@ -22,7 +22,7 @@ module Parse
22
22
  # it most likely is just the field name, so let's assume they want
23
23
  # the default equality operation.
24
24
  if operation.is_a?(Operation) == false && operation.respond_to?(:to_sym)
25
- operation = Operation.new(operation.to_sym, :eq)
25
+ operation = Operation.new(operation.to_sym, self.class.operand)
26
26
  end
27
27
  @operation = operation
28
28
  @value = value
@@ -30,15 +30,6 @@ module Parse
30
30
 
31
31
  end
32
32
 
33
- # Creates a new constraint given an operation and value.
34
- def self.create(operation, value)
35
- #default to a generic equality constraint if not passed an operation
36
- unless operation.is_a?(Parse::Operation) && operation.valid?
37
- return self.new(operation, value)
38
- end
39
- operation.constraint(value)
40
- end
41
-
42
33
  class << self
43
34
  # The class attributes keep track of the Parse key (special Parse
44
35
  # text symbol representing this operation. Ex. local method could be called
@@ -49,6 +40,18 @@ module Parse
49
40
  # The higher the more priority it will receive.
50
41
  attr_accessor :precedence
51
42
 
43
+ # Class access to store the default symbol operand action
44
+ attr_accessor :operand
45
+
46
+ # Creates a new constraint given an operation and value.
47
+ def create(operation, value)
48
+ #default to a generic equality constraint if not passed an operation
49
+ unless operation.is_a?(Parse::Operation) && operation.valid?
50
+ return self.new(operation, value)
51
+ end
52
+ operation.constraint(value)
53
+ end
54
+
52
55
  # method to set the keyword for this Constaint (subclasses)
53
56
  def contraint_keyword(k)
54
57
  @key = k
@@ -60,6 +63,29 @@ module Parse
60
63
  @precedence
61
64
  end
62
65
 
66
+ # All subclasses should register their operation and themselves
67
+ # as the handler.
68
+ def register(op, klass = self)
69
+ self.operand ||= op
70
+ Operation.register op, klass
71
+ end
72
+
73
+ def formatted_value(value)
74
+ d = value
75
+ d = { __type: "Date", iso: d.utc.iso8601(3) } if d.respond_to?(:utc)
76
+ d = { __type: "Date", iso: d.iso8601(3) } if d.respond_to?(:iso8601)
77
+ d = d.pointer if d.respond_to?(:pointer) #simplified query object
78
+ d = d.to_s if d.is_a?(Regexp)
79
+ #d = d.pointer if d.is_a?(Parse::Object) #simplified query object
80
+ # d = d.compile
81
+ if d.is_a?(Parse::Query)
82
+ compiled = d.compile(encode: false, includeClassName: true)
83
+ # compiled["className"] = d.table
84
+ d = compiled
85
+ end
86
+ d
87
+ end
88
+
63
89
  end
64
90
 
65
91
  def precedence
@@ -70,12 +96,6 @@ module Parse
70
96
  self.class.key
71
97
  end
72
98
 
73
- # All subclasses should register their operation and themselves
74
- # as the handler.
75
- def self.register(op, klass = self)
76
- Operation.register op, klass
77
- end
78
-
79
99
  def operand
80
100
  @operation.operand unless @operation.nil?
81
101
  end
@@ -115,23 +135,10 @@ module Parse
115
135
 
116
136
  # This method formats the value based on some specific data types.
117
137
  def formatted_value
118
- d = @value
119
- d = { __type: "Date", iso: d.utc.iso8601(3) } if d.respond_to?(:utc)
120
- d = { __type: "Date", iso: d.iso8601(3) } if d.respond_to?(:iso8601)
121
- d = d.pointer if d.respond_to?(:pointer) #simplified query object
122
- d = d.to_s if d.is_a?(Regexp)
123
- #d = d.pointer if d.is_a?(Parse::Object) #simplified query object
124
- # d = d.compile
125
- if d.is_a?(Parse::Query)
126
- compiled = d.compile(encode: false, includeClassName: true)
127
- # compiled["className"] = d.table
128
- d = compiled
129
- end
130
- d
138
+ self.class.formatted_value(@value)
131
139
  end
132
140
 
133
141
  register :eq, Constraint
134
- register :eql, Constraint
135
142
  precedence 100
136
143
  end
137
144
  end
@@ -7,8 +7,38 @@ require_relative 'constraint'
7
7
  # For more information: https://parse.com/docs/rest/guide#queries
8
8
  # For more information about the query design pattern from DataMapper
9
9
  # that inspired this, see http://datamapper.org/docs/find.html
10
+ class ParseConstraintError < Exception; end;
10
11
  module Parse
11
12
 
13
+ class ObjectIdConstraint < Constraint
14
+ register :id
15
+
16
+
17
+ def build
18
+ className = operand.to_parse_class
19
+ value = formatted_value
20
+ begin
21
+ klass = className.constantize
22
+ rescue NameError => e
23
+ klass = Parse::Model.find_class className
24
+ end
25
+
26
+ unless klass.present? && klass.is_a?(Parse::Object) == false
27
+ raise ParseConstraintError, "#{self.class}: No Parse class defined for #{operand} as '#{className}'"
28
+ end
29
+
30
+ # allow symbols
31
+ value = value.to_s if value.is_a?(Symbol)
32
+
33
+ unless value.is_a?(String) && value.strip.present?
34
+ raise ParseConstraintError, "#{self.class}: value must be of string type representing a Parse object id."
35
+ end
36
+ value.strip!
37
+ return { @operation.operand => klass.pointer(value) }
38
+ end
39
+
40
+ end
41
+
12
42
  class CompoundQueryConstraint < Constraint
13
43
  contraint_keyword :$or
14
44
  register :or
@@ -21,7 +51,7 @@ module Parse
21
51
 
22
52
  end
23
53
 
24
- class LessOrEqualConstraint < Constraint
54
+ class LessThanOrEqualConstraint < Constraint
25
55
  contraint_keyword :$lte
26
56
  register :lte
27
57
  register :less_than_or_equal
@@ -42,7 +72,7 @@ module Parse
42
72
  register :after
43
73
  end
44
74
 
45
- class GreaterOrEqualConstraint < Constraint
75
+ class GreaterThanOrEqualConstraint < Constraint
46
76
  contraint_keyword :$gte
47
77
  register :gte
48
78
  register :greater_than_or_equal
@@ -51,22 +81,8 @@ module Parse
51
81
 
52
82
  class NotEqualConstraint < Constraint
53
83
  contraint_keyword :$ne
54
- register :ne
55
84
  register :not
56
- end
57
-
58
- # Mapps all items contained in the array
59
- class ContainedInConstraint < Constraint
60
- contraint_keyword :$in
61
- register :in
62
- register :contained_in
63
-
64
- def build
65
- val = formatted_value
66
- val = [val].compact unless val.is_a?(Array)
67
- { @operation.operand => { key => val } }
68
- end
69
-
85
+ register :ne
70
86
  end
71
87
 
72
88
  # Nullabiliity constraint maps $exist Parse clause a bit differently
@@ -81,7 +97,12 @@ module Parse
81
97
  def build
82
98
  # if nullability is equal true, then $exists should be set to false
83
99
 
84
- if formatted_value == true
100
+ value = formatted_value
101
+ unless value == true || value == false
102
+ raise ParseConstraintError, "#{self.class}: Non-Boolean value passed, it must be either `true` or `false`"
103
+ end
104
+
105
+ if value == true
85
106
  return { @operation.operand => { key => false} }
86
107
  else
87
108
  #current bug in parse where if you want exists => true with geo queries
@@ -97,15 +118,42 @@ module Parse
97
118
  register :exists
98
119
  def build
99
120
  # if nullability is equal true, then $exists should be set to false
100
- return { @operation.operand => { key => formatted_value } }
121
+ value = formatted_value
122
+
123
+ unless value == true || value == false
124
+ raise ParseConstraintError, "#{self.class}: Non-Boolean value passed, it must be either `true` or `false`"
125
+ end
126
+
127
+ return { @operation.operand => { key => value } }
128
+ end
129
+ end
130
+
131
+ # Mapps all items contained in the array
132
+ class ContainedInConstraint < Constraint
133
+ contraint_keyword :$in
134
+ register :in
135
+ register :contained_in
136
+
137
+ def build
138
+ val = formatted_value
139
+ val = [val].compact unless val.is_a?(Array)
140
+ { @operation.operand => { key => val } }
101
141
  end
142
+
102
143
  end
103
144
 
104
145
  class NotContainedInConstraint < Constraint
105
146
  contraint_keyword :$nin
106
- register :nin
107
147
  register :not_in
148
+ register :nin
108
149
  register :not_contained_in
150
+
151
+ def build
152
+ val = formatted_value
153
+ val = [val].compact unless val.is_a?(Array)
154
+ { @operation.operand => { key => val } }
155
+ end
156
+
109
157
  end
110
158
 
111
159
  # All Things must be contained
@@ -113,6 +161,12 @@ module Parse
113
161
  contraint_keyword :$all
114
162
  register :all
115
163
  register :contains_all
164
+
165
+ def build
166
+ val = formatted_value
167
+ val = [val].compact unless val.is_a?(Array)
168
+ { @operation.operand => { key => val } }
169
+ end
116
170
  end
117
171
 
118
172
  class SelectionConstraint < Constraint
@@ -158,14 +212,14 @@ module Parse
158
212
  remote_field_name = res[:key] || remote_field_name
159
213
  query = res[:query]
160
214
  unless query.is_a?(Parse::Query)
161
- raise "Invalid Parse::Query object provided in :query field of value: #{@operation.operand}.#{$dontSelect} => #{@value}"
215
+ raise ParseConstraintError, "Invalid Parse::Query object provided in :query field of value: #{@operation.operand}.#{$dontSelect} => #{@value}"
162
216
  end
163
217
  query = query.compile(encode: false, includeClassName: true)
164
218
  elsif @value.is_a?(Parse::Query)
165
219
  # if its a query, then assume dontSelect key is the same name as operand.
166
220
  query = @value.compile(encode: false, includeClassName: true)
167
221
  else
168
- raise "Invalid `:reject` query constraint. It should follow the format: :field.reject => { key: 'key', query: '<Parse::Query>' }"
222
+ raise ParseConstraintError, "Invalid `:reject` query constraint. It should follow the format: :field.reject => { key: 'key', query: '<Parse::Query>' }"
169
223
  end
170
224
  { @operation.operand => { :$dontSelect => { key: remote_field_name, query: query } } }
171
225
  end
@@ -233,7 +287,7 @@ module Parse
233
287
  geopoint_values = formatted_value
234
288
  unless geopoint_values.is_a?(Array) && geopoint_values.count == 2 &&
235
289
  geopoint_values.first.is_a?(Parse::GeoPoint) && geopoint_values.last.is_a?(Parse::GeoPoint)
236
- raise( '[Parse::Query] Invalid query value parameter passed to `within_box` constraint. ' +
290
+ raise(ParseConstraintError, '[Parse::Query] Invalid query value parameter passed to `within_box` constraint. ' +
237
291
  'Values in array must be `Parse::GeoPoint` objects and ' +
238
292
  'it should be in an array format: [southwestPoint, northeastPoint]' )
239
293
  end
@@ -1,5 +1,5 @@
1
1
  module Parse
2
2
  module Stack
3
- VERSION = "1.3.7"
3
+ VERSION = "1.3.8"
4
4
  end
5
5
  end
@@ -214,7 +214,7 @@ module Parse
214
214
  generic_result = Parse::Webhooks.call_route(payload.trigger_name, "*", payload)
215
215
  result = generic_result if generic_result.present? && result.nil?
216
216
  else
217
- puts "[ParseWebhooks] --> Could not find mapping route for #{payload}"
217
+ puts "[ParseWebhooks] --> Could not find mapping route for #{payload.to_json}"
218
218
  end
219
219
 
220
220
  result = true if result.nil?
@@ -34,15 +34,13 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency "faraday", [">= 0.8", "< 1"]
35
35
  spec.add_runtime_dependency "faraday_middleware", [">= 0.9", "< 1"]
36
36
  spec.add_runtime_dependency "moneta", [">= 0.7", "< 1"]
37
- spec.add_runtime_dependency "rack", ["< 3"]
38
-
39
- spec.post_install_message = <<UPGRADE
40
-
41
- ** IMPORTANT **
42
- The limit field in a Parse::Query no longer defaults to 100, it must be explicitly set.
43
-
44
- Parse::Webhook exception handling has changed in 1.3.0 and later.
45
- See guide: https://github.com/modernistik/parse-stack/wiki/Webhook-Migration-1.2.x-to-1.3.0
46
-
47
- UPGRADE
37
+ spec.add_runtime_dependency "rack", "< 3"
38
+
39
+ # spec.post_install_message = <<UPGRADE
40
+ #
41
+ # ** IMPORTANT **
42
+ # Parse::Webhook exception handling has changed in 1.3.0 and later.
43
+ # See guide: https://github.com/modernistik/parse-stack/wiki/Webhook-Migration-1.2.x-to-1.3.0
44
+ #
45
+ # UPGRADE
48
46
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parse-stack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.7
4
+ version: 1.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anthony Persaud
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-09 00:00:00.000000000 Z
11
+ date: 2016-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -238,14 +238,7 @@ homepage: https://github.com/modernistik/parse-stack
238
238
  licenses:
239
239
  - MIT
240
240
  metadata: {}
241
- post_install_message: |2+
242
-
243
- ** IMPORTANT **
244
- The limit field in a Parse::Query no longer defaults to 100, it must be explicitly set.
245
-
246
- Parse::Webhook exception handling has changed in 1.3.0 and later.
247
- See guide: https://github.com/modernistik/parse-stack/wiki/Webhook-Migration-1.2.x-to-1.3.0
248
-
241
+ post_install_message:
249
242
  rdoc_options: []
250
243
  require_paths:
251
244
  - lib