parse-stack 1.5.2 → 1.5.3

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +5 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +40 -80
  5. data/lib/parse/api/all.rb +7 -0
  6. data/lib/parse/api/analytics.rb +8 -3
  7. data/lib/parse/api/apps.rb +29 -1
  8. data/lib/parse/api/batch.rb +14 -129
  9. data/lib/parse/api/cloud_functions.rb +9 -0
  10. data/lib/parse/api/config.rb +10 -1
  11. data/lib/parse/api/files.rb +7 -2
  12. data/lib/parse/api/hooks.rb +45 -2
  13. data/lib/parse/api/objects.rb +43 -6
  14. data/lib/parse/api/push.rb +6 -1
  15. data/lib/parse/api/schemas.rb +15 -1
  16. data/lib/parse/api/sessions.rb +5 -0
  17. data/lib/parse/api/users.rb +64 -5
  18. data/lib/parse/client/authentication.rb +25 -8
  19. data/lib/parse/client/batch.rb +206 -0
  20. data/lib/parse/client/body_builder.rb +12 -6
  21. data/lib/parse/client/caching.rb +42 -10
  22. data/lib/parse/client/protocol.rb +51 -46
  23. data/lib/parse/client/response.rb +1 -47
  24. data/lib/parse/client.rb +171 -42
  25. data/lib/parse/model/acl.rb +184 -39
  26. data/lib/parse/model/associations/belongs_to.rb +1 -0
  27. data/lib/parse/model/classes/role.rb +7 -1
  28. data/lib/parse/model/classes/session.rb +7 -3
  29. data/lib/parse/model/classes/user.rb +107 -0
  30. data/lib/parse/model/core/actions.rb +166 -115
  31. data/lib/parse/model/core/fetching.rb +105 -0
  32. data/lib/parse/model/core/properties.rb +40 -13
  33. data/lib/parse/model/core/querying.rb +123 -39
  34. data/lib/parse/model/core/schema.rb +22 -32
  35. data/lib/parse/model/object.rb +26 -20
  36. data/lib/parse/model/pointer.rb +1 -0
  37. data/lib/parse/query/constraint.rb +65 -27
  38. data/lib/parse/query/constraints.rb +0 -3
  39. data/lib/parse/query/operation.rb +33 -22
  40. data/lib/parse/query/ordering.rb +10 -5
  41. data/lib/parse/stack/generators/rails.rb +5 -1
  42. data/lib/parse/stack/generators/templates/model_installation.rb +1 -1
  43. data/lib/parse/stack/generators/templates/model_role.rb +1 -1
  44. data/lib/parse/stack/generators/templates/model_session.rb +2 -2
  45. data/lib/parse/stack/generators/templates/model_user.rb +1 -1
  46. data/lib/parse/stack/generators/templates/parse.rb +0 -1
  47. data/lib/parse/stack/railtie.rb +1 -0
  48. data/lib/parse/stack/tasks.rb +3 -1
  49. data/lib/parse/stack/version.rb +3 -1
  50. data/lib/parse/webhooks/registration.rb +3 -3
  51. data/lib/parse/webhooks.rb +88 -7
  52. metadata +5 -3
@@ -5,58 +5,101 @@ module Parse
5
5
 
6
6
 
7
7
  module API
8
+ # Defines the Parse webhooks interface for the Parse REST API
8
9
  module Hooks
9
10
  HOOKS_PREFIX = "hooks/"
11
+ # The allowed set of Parse triggers.
10
12
  TRIGGER_NAMES = [:beforeSave, :afterSave, :beforeDelete, :afterDelete].freeze
13
+
14
+ # @!visibility private
11
15
  def _verify_trigger(triggerName)
12
16
  triggerName = triggerName.to_s.camelize(:lower).to_sym
13
17
  raise ArgumentError, "Invalid trigger name #{triggerName}" unless TRIGGER_NAMES.include?(triggerName)
14
18
  triggerName
15
19
  end
16
20
 
21
+ # Fetch all defined cloud code functions.
22
+ # @return [Parse::Response]
17
23
  def functions
18
24
  request :get, "#{HOOKS_PREFIX}functions"
19
25
  end
20
26
 
27
+ # Fetch information about a specific registered cloud function.
28
+ # @param functionName [String] the name of the cloud code function.
29
+ # @return [Parse::Response]
21
30
  def fetch_function(functionName)
22
31
  request :get, "#{HOOKS_PREFIX}functions/#{functionName}"
23
32
  end
24
33
 
34
+ # Register a cloud code webhook function pointing to a endpoint url.
35
+ # @param functionName [String] the name of the cloud code function.
36
+ # @param url [String] the url endpoint for this cloud code function.
37
+ # @return [Parse::Response]
25
38
  def create_function(functionName, url)
26
39
  request :post, "#{HOOKS_PREFIX}functions", body: {functionName: functionName, url: url}
27
40
  end
28
41
 
42
+ # Updated the endpoint url for a registered cloud code webhook function.
43
+ # @param functionName [String] the name of the cloud code function.
44
+ # @param url [String] the new url endpoint for this cloud code function.
45
+ # @return [Parse::Response]
29
46
  def update_function(functionName, url)
30
- # interesting trick. If you add _method => "PUT" to the JSON body,
47
+ # If you add _method => "PUT" to the JSON body,
31
48
  # and send it as a POST request and parse will accept it as a PUT.
32
- # They must do this because it is easier to send POST with Ajax.
33
49
  request :put, "#{HOOKS_PREFIX}functions/#{functionName}", body: { url: url }
34
50
  end
35
51
 
52
+ # Remove a registered cloud code webhook function.
53
+ # @param functionName [String] the name of the cloud code function.
54
+ # @return [Parse::Response]
36
55
  def delete_function(functionName)
37
56
  request :put, "#{HOOKS_PREFIX}functions/#{functionName}", body: { __op: "Delete" }
38
57
  end
39
58
 
59
+ # Get the set of registered triggers.
60
+ # @return [Parse::Response]
40
61
  def triggers
41
62
  request :get, "#{HOOKS_PREFIX}triggers"
42
63
  end
43
64
 
65
+ # Fetch information about a registered webhook trigger.
66
+ # @param triggerName [String] the name of the trigger. (ex. beforeSave, afterSave)
67
+ # @param className [String] the name of the Parse collection for the trigger.
68
+ # @return [Parse::Response]
69
+ # @see TRIGGER_NAMES
44
70
  def fetch_trigger(triggerName, className)
45
71
  triggerName = _verify_trigger(triggerName)
46
72
  request :get, "#{HOOKS_PREFIX}triggers/#{className}/#{triggerName}"
47
73
  end
48
74
 
75
+ # Register a new cloud code webhook trigger with an endpoint url.
76
+ # @param triggerName [String] the name of the trigger. (ex. beforeSave, afterSave)
77
+ # @param className [String] the name of the Parse collection for the trigger.
78
+ # @param url [String] the url endpoint for this webhook trigger.
79
+ # @return [Parse::Response]
80
+ # @see Parse::API::Hooks::TRIGGER_NAMES
49
81
  def create_trigger(triggerName, className, url)
50
82
  triggerName = _verify_trigger(triggerName)
51
83
  body = {className: className, triggerName: triggerName, url: url }
52
84
  request :post, "#{HOOKS_PREFIX}triggers", body: body
53
85
  end
54
86
 
87
+ # Updated the registered endpoint for this cloud code webhook trigger.
88
+ # @param triggerName [String] the name of the trigger. (ex. beforeSave, afterSave)
89
+ # @param className [String] the name of the Parse collection for the trigger.
90
+ # @param url [String] the new url endpoint for this webhook trigger.
91
+ # @return [Parse::Response]
92
+ # @see Parse::API::Hooks::TRIGGER_NAMES
55
93
  def update_trigger(triggerName, className, url)
56
94
  triggerName = _verify_trigger(triggerName)
57
95
  request :put, "#{HOOKS_PREFIX}triggers/#{className}/#{triggerName}", body: { url: url }
58
96
  end
59
97
 
98
+ # Remove a registered cloud code webhook trigger.
99
+ # @param triggerName [String] the name of the trigger. (ex. beforeSave, afterSave)
100
+ # @param className [String] the name of the Parse collection for the trigger.
101
+ # @return [Parse::Response]
102
+ # @see Parse::API::Hooks::TRIGGER_NAMES
60
103
  def delete_trigger(triggerName, className)
61
104
  triggerName = _verify_trigger(triggerName)
62
105
  request :put, "#{HOOKS_PREFIX}triggers/#{className}/#{triggerName}", body: { __op: "Delete" }
@@ -9,19 +9,25 @@ module Parse
9
9
  module API
10
10
  # REST API methods for fetching CRUD operations on Parse objects.
11
11
  module Objects
12
-
12
+ # The class prefix for fetching objects.
13
13
  CLASS_PATH_PREFIX = "classes/"
14
+ # The class prefix mapping for fetching objects.
14
15
  PREFIX_MAP = { installation: "installations", _installation: "installations",
15
16
  user: "users", _user: "users",
16
17
  role: "roles", _role: "roles",
17
18
  session: "sessions", _session: "sessions"
18
19
  }.freeze
19
20
 
21
+ # @!visibility private
20
22
  def self.included(base)
21
23
  base.extend(ClassMethods)
22
24
  end
23
25
 
24
26
  module ClassMethods
27
+ # Get the API path for this class.
28
+ # @param className [String] the name of the Parse collection.
29
+ # @param id [String] optional objectId to add at the end of the path.
30
+ # @return [String] the API uri path
25
31
  def uri_path(className, id = nil)
26
32
  if className.is_a?(Parse::Pointer)
27
33
  id = className.id
@@ -37,39 +43,70 @@ module Parse
37
43
 
38
44
  end
39
45
 
46
+ # Get the API path for this class.
47
+ # @param className [String] the name of the Parse collection.
48
+ # @param id [String] optional objectId to add at the end of the path.
49
+ # @return [String] the API uri path
40
50
  def uri_path(className, id = nil)
41
51
  self.class.uri_path(className, id)
42
52
  end
43
53
 
44
- # /1/classes/<className> POST Creating Objects
54
+ # Create an object in a collection.
55
+ # @param className [String] the name of the Parse collection.
56
+ # @param body [Hash] the body of the request.
57
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
58
+ # @param headers [Hash] additional HTTP headers to send with the request.
59
+ # @return [Parse::Response]
45
60
  def create_object(className, body = {}, headers: {}, **opts)
46
61
  response = request :post, uri_path(className) , body: body, headers: headers, opts: opts
47
62
  response.parse_class = className if response.present?
48
63
  response
49
64
  end
50
65
 
51
- # /1/classes/<className>/<objectId> DELETE Deleting Objects
66
+ # Delete an object in a collection.
67
+ # @param className [String] the name of the Parse collection.
68
+ # @param id [String] The objectId of the record in the collection.
69
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
70
+ # @param headers [Hash] additional HTTP headers to send with the request.
71
+ # @return [Parse::Response]
52
72
  def delete_object(className, id, headers: {}, **opts)
53
73
  response = request :delete, uri_path(className, id), headers: headers, opts: opts
54
74
  response.parse_class = className if response.present?
55
75
  response
56
76
  end
57
77
 
58
- # /1/classes/<className>/<objectId> GET Retrieving Objects
78
+ # Fetch a specific object from a collection.
79
+ # @param className [String] the name of the Parse collection.
80
+ # @param id [String] The objectId of the record in the collection.
81
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
82
+ # @param headers [Hash] additional HTTP headers to send with the request.
83
+ # @return [Parse::Response]
59
84
  def fetch_object(className, id, headers: {}, **opts)
60
85
  response = request :get, uri_path(className, id), headers: headers, opts: opts
61
86
  response.parse_class = className if response.present?
62
87
  response
63
88
  end
64
89
 
65
- # /1/classes/<className> GET Queries
90
+ # Fetch a set of matching objects for a query.
91
+ # @param className [String] the name of the Parse collection.
92
+ # @param query [Hash] The set of query constraints.
93
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
94
+ # @param headers [Hash] additional HTTP headers to send with the request.
95
+ # @return [Parse::Response]
96
+ # @see Parse::Query
66
97
  def find_objects(className, query = {}, headers: {}, **opts)
67
98
  response = request :get, uri_path(className), query: query, headers: headers, opts: opts
68
99
  response.parse_class = className if response.present?
69
100
  response
70
101
  end
71
102
 
72
- # /1/classes/<className>/<objectId> PUT Updating Objects
103
+ # Update an object in a collection.
104
+ # @param className [String] the name of the Parse collection.
105
+ # @param id [String] The objectId of the record in the collection.
106
+ # @param body [Hash] The key value pairs to update.
107
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
108
+ # @param headers [Hash] additional HTTP headers to send with the request.
109
+ # @return [Parse::Response]
73
110
  def update_object(className, id, body = {}, headers: {}, **opts)
74
111
  response = request :put, uri_path(className,id) , body: body, headers: headers, opts: opts
75
112
  response.parse_class = className if response.present?
@@ -4,9 +4,14 @@
4
4
  module Parse
5
5
 
6
6
  module API
7
- #object fetch methods
7
+ # Defines the Parse Push notification service interface for the Parse REST API
8
8
  module Push
9
9
  PUSH_PATH = "push"
10
+
11
+ # Update the schema for a collection.
12
+ # @param payload [Hash] the paylod for the Push notification.
13
+ # @return [Parse::Response]
14
+ # @see https://parseplatform.github.io/docs/rest/guide/#sending-pushes Sending Pushes
10
15
  def push(payload = {})
11
16
  request :post, PUSH_PATH, body: payload.as_json
12
17
  end
@@ -4,17 +4,31 @@
4
4
  module Parse
5
5
 
6
6
  module API
7
- #object fetch methods
7
+ # Defines the Schema interface for the Parse REST API
8
8
  module Schema
9
9
  SCHEMAS_PATH = "schemas"
10
+
11
+ # Get the schema for a collection.
12
+ # @param className [String] the name of the remote Parse collection.
13
+ # @return [Parse::Response]
10
14
  def schema(className)
11
15
  request :get, "#{SCHEMAS_PATH}/#{className}"
12
16
  end
13
17
 
18
+ # Create a new collection with the specific schema.
19
+ # @param className [String] the name of the remote Parse collection.
20
+ # @param schema [Hash] the schema hash. This is a specific format specified by
21
+ # Parse.
22
+ # @return [Parse::Response]
14
23
  def create_schema(className, schema)
15
24
  request :post, "#{SCHEMAS_PATH}/#{className}", body: schema
16
25
  end
17
26
 
27
+ # Update the schema for a collection.
28
+ # @param className [String] the name of the remote Parse collection.
29
+ # @param schema [Hash] the schema hash. This is a specific format specified by
30
+ # Parse.
31
+ # @return [Parse::Response]
18
32
  def update_schema(className, schema)
19
33
  request :put, "#{SCHEMAS_PATH}/#{className}", body: schema
20
34
  end
@@ -4,9 +4,14 @@
4
4
  module Parse
5
5
 
6
6
  module API
7
+ # Defines the Session class interface for the Parse REST API
7
8
  module Sessions
8
9
  SESSION_PATH_PREFIX = "sessions"
9
10
 
11
+ # Fetch a session record for a given session token.
12
+ # @param session_token [String] an active session token.
13
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
14
+ # @return [Parse::Response]
10
15
  def fetch_session(session_token, **opts)
11
16
  opts.merge!({use_master_key: false, cache: false})
12
17
  headers = {Parse::Protocol::SESSION_TOKEN => session_token}
@@ -6,24 +6,38 @@ require 'open-uri'
6
6
  module Parse
7
7
 
8
8
  module API
9
+ # Defines the User class interface for the Parse REST API
9
10
  module Users
10
- # Note that Parse::Objects mainly use the objects.rb API since we can
11
- # detect class names to proper URI handlers
11
+
12
12
  USER_PATH_PREFIX = "users"
13
13
  LOGOUT_PATH = "logout"
14
14
  LOGIN_PATH = "login"
15
15
  REQUEST_PASSWORD_RESET = "requestPasswordReset"
16
16
 
17
+ # Fetch a {Parse::User} for a given objectId.
18
+ # @param id [String] the user objectid
19
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
20
+ # @param headers [Hash] additional HTTP headers to send with the request.
21
+ # @return [Parse::Response]
17
22
  def fetch_user(id, headers: {}, **opts)
18
23
  request :get, "#{USER_PATH_PREFIX}/#{id}", headers: headers, opts: opts
19
24
  end
20
25
 
26
+ # Find users matching a set of constraints.
27
+ # @param query [Hash] query parameters.
28
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
29
+ # @param headers [Hash] additional HTTP headers to send with the request.
30
+ # @return [Parse::Response]
21
31
  def find_users(query = {}, headers: {}, **opts)
22
32
  response = request :get, USER_PATH_PREFIX, query: query, headers: headers, opts: opts
23
33
  response.parse_class = Parse::Model::CLASS_USER
24
34
  response
25
35
  end
26
36
 
37
+ # Find user matching this active session token.
38
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
39
+ # @param headers [Hash] additional HTTP headers to send with the request.
40
+ # @return [Parse::Response]
27
41
  def current_user(session_token, headers: {}, **opts)
28
42
  headers.merge!({Parse::Protocol::SESSION_TOKEN => session_token})
29
43
  response = request :get, "#{USER_PATH_PREFIX}/me", headers: headers, opts: opts
@@ -31,6 +45,11 @@ module Parse
31
45
  response
32
46
  end
33
47
 
48
+ # Create a new user.
49
+ # @param body [Hash] a hash of values related to your _User schema.
50
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
51
+ # @param headers [Hash] additional HTTP headers to send with the request.
52
+ # @return [Parse::Response]
34
53
  def create_user(body, headers: {}, **opts)
35
54
  headers.merge!({ Parse::Protocol::REVOCABLE_SESSION => '1'})
36
55
  if opts[:session_token].present?
@@ -41,27 +60,56 @@ module Parse
41
60
  response
42
61
  end
43
62
 
63
+ # Update a {Parse::User} record given an objectId.
64
+ # @param id [String] the Parse user objectId.
65
+ # @param body [Hash] the body of the API request.
66
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
67
+ # @param headers [Hash] additional HTTP headers to send with the request.
68
+ # @return [Parse::Response]
44
69
  def update_user(id, body = {}, headers: {}, **opts)
45
70
  response = request :put, "#{USER_PATH_PREFIX}/#{id}", body: body, opts: opts
46
71
  response.parse_class = Parse::Model::CLASS_USER
47
72
  response
48
73
  end
49
74
 
50
- # deleting or unlinking is done by setting the authData of the service name to nil
75
+ # Set the authentication service OAUth data for a user. Deleting or unlinking
76
+ # is done by setting the authData of the service name to nil.
77
+ # @param id [String] the Parse user objectId.
78
+ # @param service_name [Symbol] the name of the OAuth service.
79
+ # @param auth_data [Hash] the hash data related to the third-party service.
80
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
81
+ # @param headers [Hash] additional HTTP headers to send with the request.
82
+ # @return [Parse::Response]
51
83
  def set_service_auth_data(id, service_name, auth_data, headers: {}, **opts)
52
84
  body = { authData: { service_name => auth_data } }
53
85
  update_user(id, body, opts)
54
86
  end
55
87
 
88
+ # Delete a {Parse::User} record given an objectId.
89
+ # @param id [String] the Parse user objectId.
90
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
91
+ # @param headers [Hash] additional HTTP headers to send with the request.
92
+ # @return [Parse::Response]
56
93
  def delete_user(id, headers: {}, **opts)
57
94
  request :delete, "#{USER_PATH_PREFIX}/#{id}", headers: headers, opts: opts
58
95
  end
59
96
 
60
- def request_password_reset(email, **opts)
97
+ # Request a password reset for a registered email.
98
+ # @param email [String] the Parse user email.
99
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
100
+ # @param headers [Hash] additional HTTP headers to send with the request.
101
+ # @return [Parse::Response]
102
+ def request_password_reset(email, headers: {}, **opts)
61
103
  body = {email: email}
62
- request :post, REQUEST_PASSWORD_RESET, body: body, opts: opts
104
+ request :post, REQUEST_PASSWORD_RESET, body: body, opts: opts, headers: headers
63
105
  end
64
106
 
107
+ # Login a user.
108
+ # @param username [String] the Parse user username.
109
+ # @param password [String] the Parse user's associated password.
110
+ # @param headers [Hash] additional HTTP headers to send with the request.
111
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
112
+ # @return [Parse::Response]
65
113
  def login(username, password, headers: {}, **opts)
66
114
  # Probably pass Installation-ID as header
67
115
  query = { username: username, password: password }
@@ -72,6 +120,11 @@ module Parse
72
120
  response
73
121
  end
74
122
 
123
+ # Logout a user by deleting the associated session.
124
+ # @param session_token [String] the Parse user session token to delete.
125
+ # @param headers [Hash] additional HTTP headers to send with the request.
126
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
127
+ # @return [Parse::Response]
75
128
  def logout(session_token, headers: {}, **opts)
76
129
  headers.merge!({ Parse::Protocol::SESSION_TOKEN => session_token})
77
130
  opts.merge!({use_master_key: false, session_token: session_token})
@@ -79,6 +132,12 @@ module Parse
79
132
  end
80
133
 
81
134
  # Signup a user given a username, password and, optionally, their email.
135
+ # @param username [String] the Parse user username.
136
+ # @param password [String] the Parse user's associated password.
137
+ # @param email [String] the desired Parse user's email.
138
+ # @param body [Hash] additional property values to pass when creating the user record.
139
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
140
+ # @return [Parse::Response]
82
141
  def signup(username, password, email = nil, body: {}, **opts)
83
142
  body = body.merge({ username: username, password: password })
84
143
  body[:email] = email || body[:email]
@@ -7,22 +7,37 @@ require 'active_support'
7
7
  require 'active_support/core_ext'
8
8
 
9
9
  require_relative 'protocol'
10
- # All Parse requests require authentication with specific header values.
11
- # This middleware takes all outgoing requests and adds the proper header values
12
- # base on the client configuration.
10
+
13
11
  module Parse
14
12
 
15
13
  module Middleware
16
-
14
+ # This middleware handles sending the proper authentication headers to the
15
+ # Parse REST API endpoint.
17
16
  class Authentication < Faraday::Middleware
18
17
  include Parse::Protocol
19
- DISABLE_MASTER_KEY = "X-Disable-Parse-Master-Key"
18
+ DISABLE_MASTER_KEY = "X-Disable-Parse-Master-Key".freeze
19
+ # @return [String] the application id for this Parse endpoint.
20
20
  attr_accessor :application_id
21
+ # @return [String] the REST API Key for this Parse endpoint.
21
22
  attr_accessor :api_key
23
+ # The Master key API Key for this Parse endpoint. This is optional. If
24
+ # provided, it will be sent in every request.
25
+ # @return [String]
22
26
  attr_accessor :master_key
23
- # The options hash should contain the proper keys to be added to the request.
24
- def initialize(app, options = {})
25
- super(app)
27
+
28
+ #
29
+ # @param adapter [Faraday::Adapter] An instance of the Faraday adapter
30
+ # used for the connection. Defaults Faraday::Adapter::NetHttp.
31
+ # @param options [Hash] the options containing Parse authentication data.
32
+ # @option options [String] :application_id the application id.
33
+ # @option options [String] :api_key the REST API key.
34
+ # @option options [String] :master_key the Master Key for this application.
35
+ # If it is set, it will be sent on every request unless this middleware sees
36
+ # {DISABLE_MASTER_KEY} as an entry in the headers section.
37
+ # @option options [String] :content_type the content type format header. Defaults to
38
+ # {Parse::Protocol::CONTENT_TYPE_FORMAT}.
39
+ def initialize(adapter, options = {})
40
+ super(adapter)
26
41
  @application_id = options[:application_id]
27
42
  @api_key = options[:api_key]
28
43
  @master_key = options[:master_key]
@@ -30,10 +45,12 @@ module Parse
30
45
  end
31
46
 
32
47
  # we dup the call for thread-safety
48
+ # @!visibility private
33
49
  def call(env)
34
50
  dup.call!(env)
35
51
  end
36
52
 
53
+ # @!visibility private
37
54
  def call!(env)
38
55
  # We add the main Parse protocol headers
39
56
  headers = {}
@@ -0,0 +1,206 @@
1
+
2
+ require_relative 'request'
3
+ require_relative 'response'
4
+
5
+
6
+ module Parse
7
+ # Create a new batch operation.
8
+ # @param reqs [Array<Parse::Request>] a set of requests to batch.
9
+ # @return [BatchOperation] a new {BatchOperation} with the given change requests.
10
+ def self.batch(reqs = nil)
11
+ BatchOperation.new(reqs)
12
+ end
13
+
14
+ # This class provides a standard way to submit, manage and process batch operations
15
+ # for Parse::Objects and associations.
16
+ #
17
+ # Batch requests are supported implicitly and intelligently through an
18
+ # extension of array. When an array of Parse::Object subclasses is saved,
19
+ # Parse-Stack will batch all possible save operations for the objects in the
20
+ # array that have changed. It will also batch save 50 at a time until all items
21
+ # in the array are saved. Note: Parse does not allow batch saving Parse::User objects.
22
+ #
23
+ # songs = Songs.first 1000 #first 1000 songs
24
+ # songs.each do |song|
25
+ # # ... modify song ...
26
+ # end
27
+ #
28
+ # # will batch save 50 items at a time until all are saved.
29
+ # songs.save
30
+ #
31
+ # The objects do not have to be of the same collection in order to be supported in the
32
+ # batch request.
33
+ # @see Array.save
34
+ # @see Array.destroy
35
+ class BatchOperation
36
+
37
+ attr_accessor :requests, :responses
38
+ include Enumerable
39
+
40
+ # @return [Parse::Client] the client to be used for the request.
41
+ def client
42
+ @client ||= Parse::Client.client
43
+ end
44
+
45
+ # @param reqs [Array<Parse::Request>] an array of requests.
46
+ def initialize(reqs = nil)
47
+ @requests = []
48
+ @responses = []
49
+ reqs = [reqs] unless reqs.is_a?(Enumerable)
50
+ reqs.each { |r| add(r) } if reqs.is_a?(Enumerable)
51
+ end
52
+
53
+ # Add an additional request to this batch.
54
+ # @overload add(req)
55
+ # @param req [Parse::Request] the request to append.
56
+ # @return [Array<Parse::Request>] the set of requests.
57
+ # @overload add(batch)
58
+ # @param req [Parse::BatchOperation] add all the requests from this batch operation.
59
+ # @return [Array<Parse::Request>] the set of requests.
60
+ def add(req)
61
+ if req.respond_to?(:change_requests)
62
+ requests = req.change_requests.select { |r| r.is_a?(Parse::Request) }
63
+ @requests += requests
64
+ elsif req.is_a?(Array)
65
+ requests = req.select { |r| r.is_a?(Parse::Request) }
66
+ @requests += requests
67
+ elsif req.is_a?(BatchOperation)
68
+ @requests += req.requests if req.is_a?(BatchOperation)
69
+ else
70
+ @requests.push(req) if req.is_a?(Parse::Request)
71
+ end
72
+ @requests
73
+ end
74
+
75
+ # This method is for interoperability with Parse::Object instances.
76
+ # @see Parse::Object#change_requests
77
+ def change_requests
78
+ @requests
79
+ end
80
+
81
+ # @return [Array]
82
+ def each
83
+ return enum_for(:each) unless block_given?
84
+ @requests.each(&Proc.new)
85
+ end
86
+
87
+ # @return [Hash] a formatted payload for the batch request.
88
+ def as_json(*args)
89
+ { requests: requests }.as_json
90
+ end
91
+
92
+ # @return [Integer] the number of requests in the batch.
93
+ def count
94
+ @requests.count
95
+ end
96
+
97
+ # Remove all requests in this batch.
98
+ # @return [Array]
99
+ def clear!
100
+ @requests.clear
101
+ end
102
+
103
+ # @return [Boolean] true if the request was successful.
104
+ def success?
105
+ return false if @responses.empty?
106
+ @responses.compact.all?(&:success?)
107
+ end
108
+
109
+ # @return [Boolean] true if the request had an error.
110
+ def error?
111
+ return false if @responses.empty?
112
+ ! success?
113
+ end
114
+
115
+ # Submit the batch operation in chunks until they are all complete. In general,
116
+ # Parse limits requests in each batch to 50 and it is possible that a {BatchOperation}
117
+ # instance contains more than 50 requests. This method will slice up the array of
118
+ # request and send them based on the `segment` amount until they have all been submitted.
119
+ # @param segment [Integer] the number of requests to send in each batch. Default 50.
120
+ # @return [Array<Parse::Response>] the corresponding set of responses for
121
+ # each request in the batch.
122
+ def submit(segment = 50)
123
+ @responses = []
124
+ @requests.uniq!(&:signature)
125
+ @requests.each_slice(segment) do |slice|
126
+ @responses << client.batch_request( BatchOperation.new(slice) )
127
+ #throttle
128
+ # sleep (slice.count.to_f / MAX_REQ_SEC.to_f )
129
+ end
130
+ @responses.flatten!
131
+ #puts "Requests: #{@requests.count} == Response: #{@responses.count}"
132
+ @requests.zip(@responses).each(&Proc.new) if block_given?
133
+ @responses
134
+ end
135
+ alias_method :save, :submit
136
+
137
+
138
+ end
139
+ end
140
+
141
+ class Array
142
+
143
+ # Submit a batch request for deleting a set of Parse::Objects.
144
+ # @example
145
+ # # assume Post and Author are Parse models
146
+ # author = Author.first
147
+ # posts = Post.all author: author
148
+ # posts.destroy # batch destroy request
149
+ # @return [Parse::BatchOperation] the batch operation performed.
150
+ # @see Parse::BatchOperation
151
+ def destroy
152
+ batch = Parse::BatchOperation.new
153
+ each do |o|
154
+ next unless o.respond_to?(:destroy_request)
155
+ r = o.destroy_request
156
+ batch.add(r) unless r.nil?
157
+ end
158
+ batch.submit
159
+ batch
160
+ end
161
+
162
+ # Submit a batch request for deleting a set of Parse::Objects.
163
+ # Batch requests are supported implicitly and intelligently through an
164
+ # extension of array. When an array of Parse::Object subclasses is saved,
165
+ # Parse-Stack will batch all possible save operations for the objects in the
166
+ # array that have changed. It will also batch save 50 at a time until all items
167
+ # in the array are saved. Note: Parse does not allow batch saving Parse::User objects.
168
+ # @note The objects of the array to be saved do not all have to be of the same collection.
169
+ # @param merge [Boolean] whether to merge the updated changes to the series of
170
+ # objects back to the original ones submitted. If you don't need the original objects
171
+ # to be updated with the changes, set this to false for improved performance.
172
+ # @param force [Boolean] Do not skip objects that do not have pending changes (dirty tracking).
173
+ # @example
174
+ # # assume Post and Author are Parse models
175
+ # author = Author.first
176
+ # posts = Post.first 100
177
+ # posts.each { |post| post.author = author }
178
+ # posts.save # batch save
179
+ # @return [Parse::BatchOperation] the batch operation performed.
180
+ # @see Parse::BatchOperation
181
+ def save(merge: true, force: false)
182
+ batch = Parse::BatchOperation.new
183
+ objects = {}
184
+ each do |o|
185
+ next unless o.is_a?(Parse::Object)
186
+ objects[o.object_id] = o
187
+ batch.add o.change_requests(force)
188
+ end
189
+ if merge == false
190
+ batch.submit
191
+ return batch
192
+ end
193
+ #rebind updates
194
+ batch.submit do |request, response|
195
+ next unless request.tag.present? && response.present? && response.success?
196
+ o = objects[request.tag]
197
+ next unless o.is_a?(Parse::Object)
198
+ result = response.result
199
+ o.id = result['objectId'] if o.id.blank?
200
+ o.set_attributes!(result)
201
+ o.clear_changes!
202
+ end
203
+ batch
204
+ end #save!
205
+
206
+ end