parse-stack 1.3.1 → 1.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +2 -1
- data/Changes.md +25 -1
- data/Gemfile +10 -0
- data/Gemfile.lock +10 -9
- data/README.md +593 -254
- data/Rakefile +1 -0
- data/bin/console +5 -0
- data/lib/parse-stack.rb +2 -0
- data/lib/parse/api/batch.rb +2 -1
- data/lib/parse/api/files.rb +2 -0
- data/lib/parse/api/objects.rb +2 -0
- data/lib/parse/client.rb +31 -2
- data/lib/parse/client/authentication.rb +10 -0
- data/lib/parse/client/body_builder.rb +3 -0
- data/lib/parse/client/request.rb +1 -1
- data/lib/parse/client/response.rb +1 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +2 -2
- data/lib/parse/model/associations/relation_collection_proxy.rb +1 -0
- data/lib/parse/model/core/actions.rb +8 -8
- data/lib/parse/model/core/properties.rb +9 -0
- data/lib/parse/model/core/schema.rb +9 -0
- data/lib/parse/model/date.rb +4 -0
- data/lib/parse/model/file.rb +17 -2
- data/lib/parse/model/geopoint.rb +39 -2
- data/lib/parse/model/object.rb +2 -1
- data/lib/parse/model/pointer.rb +3 -0
- data/lib/parse/model/push.rb +2 -2
- data/lib/parse/query.rb +33 -17
- data/lib/parse/query/constraint.rb +2 -2
- data/lib/parse/query/constraints.rb +62 -6
- data/lib/parse/query/operation.rb +1 -0
- data/lib/parse/stack.rb +1 -0
- data/lib/parse/stack/tasks.rb +107 -0
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/webhooks.rb +2 -2
- data/lib/parse/webhooks/payload.rb +2 -0
- data/lib/parse/webhooks/registration.rb +15 -10
- data/parse-stack.gemspec +8 -15
- metadata +20 -118
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -4,6 +4,11 @@ require "bundler/setup"
|
|
4
4
|
require "parse/stack"
|
5
5
|
require 'dotenv'
|
6
6
|
Dotenv.load
|
7
|
+
|
8
|
+
|
9
|
+
class Song < Parse::Object
|
10
|
+
property :name
|
11
|
+
end
|
7
12
|
# You can add fixtures and/or initialization code here to make experimenting
|
8
13
|
# with your gem easier. You can also use a different console, if you like.
|
9
14
|
|
data/lib/parse-stack.rb
ADDED
data/lib/parse/api/batch.rb
CHANGED
data/lib/parse/api/files.rb
CHANGED
data/lib/parse/api/objects.rb
CHANGED
data/lib/parse/client.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
require 'faraday'
|
2
2
|
require 'faraday_middleware'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_model_serializers'
|
5
|
+
require 'active_support/inflector'
|
6
|
+
require 'active_support/core_ext/object'
|
3
7
|
require 'active_support/core_ext/string'
|
8
|
+
require 'active_support/core_ext/date/calculations'
|
9
|
+
require 'active_support/core_ext/date_time/calculations'
|
10
|
+
require 'active_support/core_ext/time/calculations'
|
11
|
+
require 'active_support/core_ext'
|
4
12
|
require_relative "client/request"
|
5
13
|
require_relative "client/response"
|
6
14
|
require_relative "client/body_builder"
|
@@ -17,6 +25,12 @@ module Parse
|
|
17
25
|
class ServerError < Exception; end;
|
18
26
|
class AuthenticationError < Exception; end;
|
19
27
|
class RequestLimitExceededError < Exception; end;
|
28
|
+
class InvalidSessionTokenError < Exception; end;
|
29
|
+
|
30
|
+
# helper method to get the config variables.
|
31
|
+
def self.config(s = :default)
|
32
|
+
Parse::Client.session(s).config
|
33
|
+
end
|
20
34
|
|
21
35
|
# Main class for the client. The client class is based on a Faraday stack.
|
22
36
|
# The Faraday stack is similar to a Rack-style application in which you can define middlewares
|
@@ -42,6 +56,9 @@ module Parse
|
|
42
56
|
# by the other classes including Parse::Query and Parse::Objects
|
43
57
|
@@sessions = { default: nil }
|
44
58
|
|
59
|
+
def self.session?(v = :default)
|
60
|
+
@@sessions[v].present?
|
61
|
+
end
|
45
62
|
# get a session for a given tag. This will also create a new one for the tag if not specified.
|
46
63
|
def self.session(connection = :default)
|
47
64
|
#warn "Please call Parse::Client.setup() to initialize your parse session" if @@sessions.empty? || @@sessions[:default].nil?
|
@@ -72,7 +89,7 @@ module Parse
|
|
72
89
|
# :host - defaults to Parse::Protocol::SERVER_URL (https://api.parse.com/1/)
|
73
90
|
def initialize(opts = {})
|
74
91
|
@server_url = opts[:server_url] || ENV["PARSE_SERVER_URL"] || Parse::Protocol::SERVER_URL
|
75
|
-
@application_id = opts[:application_id] || ENV["PARSE_APP_ID"]
|
92
|
+
@application_id = opts[:app_id] || opts[:application_id] || ENV["PARSE_APP_ID"]
|
76
93
|
@api_key = opts[:api_key] || ENV["PARSE_API_KEY"]
|
77
94
|
@master_key = opts[:master_key] || ENV["PARSE_MASTER_KEY"]
|
78
95
|
opts[:adapter] ||= Faraday.default_adapter
|
@@ -104,6 +121,10 @@ module Parse
|
|
104
121
|
# We place it after the Authentication middleware in case we need to use then
|
105
122
|
# authentication information when building request and responses.
|
106
123
|
conn.use Parse::Middleware::BodyBuilder
|
124
|
+
if opts[:logging].present? && opts[:logging] == :debug
|
125
|
+
Parse::Middleware::BodyBuilder.logging = true
|
126
|
+
end
|
127
|
+
|
107
128
|
if opts[:cache].present? && opts[:expires].to_i > 0
|
108
129
|
unless opts[:cache].is_a?(Moneta::Transformer)
|
109
130
|
raise "Parse::Client option :cache needs to be a type of Moneta::Transformer store."
|
@@ -165,7 +186,12 @@ module Parse
|
|
165
186
|
if opts[:use_master_key] == false
|
166
187
|
headers[Parse::Middleware::Authentication::DISABLE_MASTER_KEY] = "true"
|
167
188
|
end
|
168
|
-
|
189
|
+
|
190
|
+
if opts[:session_token].present?
|
191
|
+
headers[Parse::Middleware::Authentication::DISABLE_MASTER_KEY] = "true"
|
192
|
+
headers[Parse::Protocol::SESSION_TOKEN] = opts[:session_token]
|
193
|
+
end
|
194
|
+
|
169
195
|
#if it is a :get request, then use query params, otherwise body.
|
170
196
|
params = (method == :get ? query : body) || {}
|
171
197
|
# if the path does not start with the '/1/' prefix, then add it to be nice.
|
@@ -202,6 +228,9 @@ module Parse
|
|
202
228
|
elsif body.code == 155
|
203
229
|
puts "[ParseError] #{body}"
|
204
230
|
raise Parse::RequestLimitExceededError, body
|
231
|
+
elsif body.code == 209 #Error 209: invalid session token
|
232
|
+
puts "[ParseError] #{body}"
|
233
|
+
raise Parse::InvalidSessionTokenError, body
|
205
234
|
end
|
206
235
|
end
|
207
236
|
|
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'faraday'
|
2
2
|
require 'faraday_middleware'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext'
|
5
|
+
|
3
6
|
require_relative 'protocol'
|
4
7
|
# All Parse requests require authentication with specific header values.
|
5
8
|
# This middleware takes all outgoing requests and adds the proper header values
|
@@ -38,6 +41,13 @@ module Parse
|
|
38
41
|
unless @master_key.blank? || env[:request_headers][DISABLE_MASTER_KEY].present?
|
39
42
|
headers[MASTER_KEY] = @master_key
|
40
43
|
end
|
44
|
+
|
45
|
+
env[:request_headers].delete(DISABLE_MASTER_KEY)
|
46
|
+
|
47
|
+
# delete the use of master key if we are using session token.
|
48
|
+
if env[:request_headers].key?(Parse::Protocol::SESSION_TOKEN)
|
49
|
+
headers.delete(MASTER_KEY)
|
50
|
+
end
|
41
51
|
# merge the headers with the current provided headers
|
42
52
|
env[:request_headers].merge! headers
|
43
53
|
# set the content type of the request if it was not provided already.
|
@@ -2,6 +2,9 @@ require 'faraday'
|
|
2
2
|
require 'faraday_middleware'
|
3
3
|
require_relative 'response'
|
4
4
|
require_relative 'protocol'
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/core_ext'
|
7
|
+
require 'active_model_serializers'
|
5
8
|
# This middleware takes an incoming response (after an outgoing request)
|
6
9
|
# and creates a Parse::Response object.
|
7
10
|
module Parse
|
data/lib/parse/client/request.rb
CHANGED
@@ -54,11 +54,11 @@ module Parse
|
|
54
54
|
# them in parallel.
|
55
55
|
|
56
56
|
def fetch!
|
57
|
-
collection.
|
57
|
+
collection.fetch_objects!
|
58
58
|
end
|
59
59
|
|
60
60
|
def fetch
|
61
|
-
collection.
|
61
|
+
collection.fetch_objects
|
62
62
|
end
|
63
63
|
# Even though we may have full Parse Objects in the collection, when updating
|
64
64
|
# or storing them in Parse, we actually just want Parse::Pointer objects.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'active_model'
|
2
2
|
require 'active_support'
|
3
3
|
require 'active_support/inflector'
|
4
|
-
require 'active_support/core_ext
|
4
|
+
require 'active_support/core_ext'
|
5
5
|
require 'time'
|
6
6
|
require 'parallel'
|
7
7
|
require_relative '../../client/request'
|
@@ -95,11 +95,11 @@ module Parse
|
|
95
95
|
# updated_comparison_block = Proc.new { |x| x.updated_at }
|
96
96
|
|
97
97
|
anchor_date = Parse::Date.now
|
98
|
-
constraints.merge! :updated_at.
|
98
|
+
constraints.merge! :updated_at.on_or_before => anchor_date
|
99
99
|
# oldest first, so we create a reduction-cycle
|
100
100
|
constraints.merge! order: :updated_at.asc, limit: 100
|
101
101
|
update_query = query(constraints)
|
102
|
-
puts "Setting Anchor Date: #{anchor_date}"
|
102
|
+
#puts "Setting Anchor Date: #{anchor_date}"
|
103
103
|
cursor = nil
|
104
104
|
has_errors = false
|
105
105
|
loop do
|
@@ -109,7 +109,7 @@ module Parse
|
|
109
109
|
|
110
110
|
# verify we didn't get duplicates fetches
|
111
111
|
if cursor.is_a?(Parse::Object) && results.any? { |x| x.id == cursor.id }
|
112
|
-
warn "Unbounded update detected
|
112
|
+
warn "[Parse::SaveAll] Unbounded update detected with id #{cursor.id}."
|
113
113
|
has_errors = true
|
114
114
|
break cursor
|
115
115
|
end
|
@@ -124,13 +124,13 @@ module Parse
|
|
124
124
|
cursor = results.last
|
125
125
|
# slower version, but more accurate
|
126
126
|
# cursor_item = results.max_by(&updated_comparison_block).updated_at
|
127
|
-
puts "Updated #{results.count} records updated <= #{cursor.updated_at}"
|
127
|
+
# puts "[Parse::SaveAll] Updated #{results.count} records updated <= #{cursor.updated_at}"
|
128
128
|
|
129
129
|
if cursor.is_a?(Parse::Object)
|
130
130
|
update_query.where :updated_at.gte => cursor.updated_at
|
131
131
|
|
132
132
|
if cursor.updated_at.present? && cursor.updated_at > anchor_date
|
133
|
-
warn "Reached anchor date
|
133
|
+
warn "[Parse::SaveAll] Reached anchor date #{anchor_date} < #{cursor.updated_at}"
|
134
134
|
break cursor
|
135
135
|
end
|
136
136
|
|
@@ -479,7 +479,7 @@ class Array
|
|
479
479
|
# a parameter symbol can be passed indicating the lookup methodology. Default
|
480
480
|
# is parallel which fetches all objects in parallel HTTP requests.
|
481
481
|
# If nil is passed in, then all the fetching happens sequentially.
|
482
|
-
def
|
482
|
+
def fetch_objects!(lookup = :parallel)
|
483
483
|
# this gets all valid parse objects from the array
|
484
484
|
items = valid_parse_objects
|
485
485
|
|
@@ -497,7 +497,7 @@ class Array
|
|
497
497
|
# fetches all pointer objects in the array. You can pass a symbol argument
|
498
498
|
# that provides the lookup methodology, default is :parallel. Objects that have
|
499
499
|
# already been fetched (not in a pointer state) are skipped.
|
500
|
-
def
|
500
|
+
def fetch_objects(lookup = :parallel)
|
501
501
|
items = valid_parse_objects
|
502
502
|
if lookup == :parallel
|
503
503
|
items.threaded_each { |o| o.fetch }
|
@@ -1,4 +1,10 @@
|
|
1
1
|
require 'active_model'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/inflector'
|
4
|
+
require 'active_support/core_ext'
|
5
|
+
require 'active_support/core_ext/object'
|
6
|
+
require 'active_support/inflector'
|
7
|
+
require 'active_model_serializers'
|
2
8
|
require 'active_support/inflector'
|
3
9
|
require 'active_model_serializers'
|
4
10
|
require 'time'
|
@@ -94,6 +100,9 @@ module Parse
|
|
94
100
|
opts.merge!(data_type)
|
95
101
|
data_type = :string
|
96
102
|
end
|
103
|
+
|
104
|
+
# allow :bool for :boolean
|
105
|
+
data_type = :boolean if data_type == :bool
|
97
106
|
# set defaults
|
98
107
|
opts = { required: false,
|
99
108
|
alias: true,
|
@@ -2,6 +2,15 @@ require_relative "properties"
|
|
2
2
|
# This class adds methods to Parse::Objects in order to create a JSON Parse schema
|
3
3
|
# in order to support table creation and table alterations.
|
4
4
|
module Parse
|
5
|
+
|
6
|
+
def self.auto_upgrade!
|
7
|
+
klassModels = Parse::Object.descendants - [Parse::User, Parse::Installation, Parse::Role, Parse::Session]
|
8
|
+
klassModels.sort_by { |c| c.parse_class }.each do |klass|
|
9
|
+
yield(klass) if block_given?
|
10
|
+
klass.auto_upgrade!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
5
14
|
module Schema
|
6
15
|
|
7
16
|
def self.included(base)
|
data/lib/parse/model/date.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
require 'time'
|
2
|
+
require 'date'
|
2
3
|
require 'active_model'
|
3
4
|
require 'active_support'
|
4
5
|
require 'active_support/inflector'
|
5
6
|
require 'active_support/core_ext/object'
|
7
|
+
require 'active_support/core_ext/date/calculations'
|
8
|
+
require 'active_support/core_ext/date_time/calculations'
|
9
|
+
require 'active_support/core_ext/time/calculations'
|
6
10
|
require 'active_model_serializers'
|
7
11
|
require_relative 'model'
|
8
12
|
|
data/lib/parse/model/file.rb
CHANGED
@@ -20,14 +20,21 @@ module Parse
|
|
20
20
|
alias_method :__type, :parse_class
|
21
21
|
FIELD_NAME = "name".freeze
|
22
22
|
FIELD_URL = "url".freeze
|
23
|
+
class << self
|
24
|
+
attr_accessor :default_mime_type
|
23
25
|
|
26
|
+
def default_mime_type
|
27
|
+
@default_mime_type ||= "image/jpeg"
|
28
|
+
end
|
29
|
+
end
|
24
30
|
# The initializer to create a new file supports different inputs.
|
25
31
|
# If the first paramter is a string which starts with 'http', we then download
|
26
32
|
# the content of the file (and use the detected mime-type) to set the content and mime_type fields.
|
27
33
|
# If the first parameter is a hash, we assume it might be the Parse File hash format which contains url and name fields only.
|
28
34
|
# If the first paramter is a Parse::File, then we copy fields over
|
29
35
|
# Otherwise, creating a new file requires a name, the actual contents (usually from a File.open("local.jpg").read ) and the mime-type
|
30
|
-
def initialize(name, contents = nil, mime_type =
|
36
|
+
def initialize(name, contents = nil, mime_type = nil)
|
37
|
+
mime_type ||= Parse::File.default_mime_type
|
31
38
|
|
32
39
|
if name.is_a?(String) && name.start_with?("http".freeze) #could be url string
|
33
40
|
file = open( name )
|
@@ -39,7 +46,7 @@ module Parse
|
|
39
46
|
elsif name.is_a?(::File)
|
40
47
|
@contents = contents || name.read
|
41
48
|
@name = File.basename name.to_path
|
42
|
-
elsif name.is_a?(File)
|
49
|
+
elsif name.is_a?(Parse::File)
|
43
50
|
@name = name.name
|
44
51
|
@url = name.url
|
45
52
|
else
|
@@ -109,6 +116,14 @@ module Parse
|
|
109
116
|
saved?
|
110
117
|
end
|
111
118
|
|
119
|
+
def inspect
|
120
|
+
"<Parse::File @name='#{@name}' @mime_type='#{@mime_type}' @contents=#{@contents.nil?} @url='#{@url}'>"
|
121
|
+
end
|
122
|
+
|
123
|
+
def to_s
|
124
|
+
@url
|
125
|
+
end
|
126
|
+
|
112
127
|
end
|
113
128
|
|
114
129
|
end
|
data/lib/parse/model/geopoint.rb
CHANGED
@@ -10,15 +10,24 @@ module Parse
|
|
10
10
|
attr_accessor :latitude, :longitude
|
11
11
|
FIELD_LAT = "latitude".freeze
|
12
12
|
FIELD_LNG = "longitude".freeze
|
13
|
+
# Latitude should not be -90.0 or 90.0.
|
14
|
+
# Longitude should not be -180.0 or 180.0.
|
15
|
+
LAT_MIN = -90.0
|
16
|
+
LAT_MAX = 90.0
|
17
|
+
LNG_MIN = -180.0
|
18
|
+
LNG_MAX = 180.0
|
13
19
|
alias_method :lat, :latitude
|
14
20
|
alias_method :lng, :longitude
|
15
21
|
def self.parse_class; TYPE_GEOPOINT; end;
|
16
22
|
def parse_class; self.class.parse_class; end;
|
17
23
|
alias_method :__type, :parse_class
|
18
|
-
# TODO: Validate the ranges of the geo point for valid lat and lng numbers.
|
19
24
|
# To create a GeoPoint, you can either pass a hash (ex. {latitude: 32, longitue: -117})
|
20
25
|
# or an array (ex. [32,-117]) as the first parameter.
|
21
26
|
# You may also pass a GeoPoint object or both a lat/lng pair (Ex. GeoPoint.new(32, -117) )
|
27
|
+
# Points should not equal or exceed the extreme ends of the ranges.
|
28
|
+
|
29
|
+
# Attempting to use GeoPoint’s with latitude and/or longitude outside these ranges will cause an error.
|
30
|
+
|
22
31
|
def initialize(latitude = nil, longitude = nil)
|
23
32
|
@latitude = @longitude = 0.0
|
24
33
|
if latitude.is_a?(Hash) || latitude.is_a?(Array)
|
@@ -30,6 +39,22 @@ module Parse
|
|
30
39
|
@latitude = latitude.latitude
|
31
40
|
@longitude = latitude.longitude
|
32
41
|
end
|
42
|
+
|
43
|
+
_validate_point
|
44
|
+
end
|
45
|
+
|
46
|
+
def _validate_point
|
47
|
+
|
48
|
+
unless @latitude.nil? || @latitude.between?(LAT_MIN, LAT_MAX)
|
49
|
+
warn "[Parse::GeoPoint] Latitude (#{@latitude}) is not between #{LAT_MIN}, #{LAT_MAX}!"
|
50
|
+
warn "Attempting to use GeoPoint’s with latitudes outside these ranges will raise an exception in a future release."
|
51
|
+
end
|
52
|
+
|
53
|
+
unless @longitude.nil? || @longitude.between?(LNG_MIN, LNG_MAX)
|
54
|
+
warn "[Parse::GeoPoint] Longitude (#{@longitude}) is not between #{LNG_MIN}, #{LNG_MAX}!"
|
55
|
+
warn "Attempting to use GeoPoint’s with longitude outside these ranges will raise an exception in a future release."
|
56
|
+
end
|
57
|
+
|
33
58
|
end
|
34
59
|
|
35
60
|
def attributes
|
@@ -37,9 +62,20 @@ module Parse
|
|
37
62
|
end
|
38
63
|
|
39
64
|
def max_miles(m)
|
65
|
+
m = 0 if m.nil?
|
40
66
|
[@latitude,@longitude,m]
|
41
67
|
end
|
42
68
|
|
69
|
+
def latitude=(l)
|
70
|
+
@latitude = l
|
71
|
+
_validate_point
|
72
|
+
end
|
73
|
+
|
74
|
+
def longitude=(l)
|
75
|
+
@longitude = l
|
76
|
+
_validate_point
|
77
|
+
end
|
78
|
+
|
43
79
|
# Setting lat and lng for an GeoPoint can be done using a hash with the attributes set
|
44
80
|
# or with an array of two items where the first is the lat and the second is the lng (ex. [32.22,-118.81])
|
45
81
|
def attributes=(h)
|
@@ -51,6 +87,7 @@ module Parse
|
|
51
87
|
@latitude = h.first.to_f
|
52
88
|
@longitude = h.last.to_f
|
53
89
|
end
|
90
|
+
_validate_point
|
54
91
|
end
|
55
92
|
|
56
93
|
def ==(g)
|
@@ -75,7 +112,7 @@ module Parse
|
|
75
112
|
unless geopoint.is_a?(Parse::GeoPoint)
|
76
113
|
geopoint = Parse::GeoPoint.new(geopoint, lng)
|
77
114
|
end
|
78
|
-
|
115
|
+
|
79
116
|
dtor = Math::PI/180
|
80
117
|
r = 6378.14
|
81
118
|
r_lat1 = self.latitude * dtor
|
data/lib/parse/model/object.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'active_model'
|
2
2
|
require 'active_support'
|
3
3
|
require 'active_support/inflector'
|
4
|
+
require 'active_support/core_ext'
|
4
5
|
require 'active_support/core_ext/object'
|
5
6
|
require 'active_support/core_ext/string'
|
6
7
|
require 'active_model_serializers'
|
@@ -78,7 +79,7 @@ module Parse
|
|
78
79
|
def self.registered_classes
|
79
80
|
Parse::Object.descendants.map { |m| m.parse_class }.uniq
|
80
81
|
end
|
81
|
-
|
82
|
+
|
82
83
|
# Find a corresponding class for this string or symbol
|
83
84
|
def self.classify(className)
|
84
85
|
Parse::Model.find_class className.to_parse_class
|