parse-stack 1.4.3 → 1.5.1

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +52 -39
  3. data/Gemfile.lock +2 -2
  4. data/README.md +609 -124
  5. data/bin/console +0 -9
  6. data/lib/parse/api/all.rb +3 -0
  7. data/lib/parse/api/analytics.rb +2 -2
  8. data/lib/parse/api/apps.rb +15 -17
  9. data/lib/parse/api/batch.rb +4 -1
  10. data/lib/parse/api/cloud_functions.rb +2 -0
  11. data/lib/parse/api/config.rb +14 -2
  12. data/lib/parse/api/files.rb +6 -3
  13. data/lib/parse/api/hooks.rb +4 -4
  14. data/lib/parse/api/objects.rb +14 -11
  15. data/lib/parse/api/push.rb +4 -2
  16. data/lib/parse/api/schemas.rb +6 -5
  17. data/lib/parse/api/sessions.rb +11 -1
  18. data/lib/parse/api/users.rb +65 -15
  19. data/lib/parse/client/authentication.rb +4 -2
  20. data/lib/parse/client/body_builder.rb +11 -3
  21. data/lib/parse/client/caching.rb +17 -6
  22. data/lib/parse/client/protocol.rb +14 -8
  23. data/lib/parse/client/request.rb +4 -1
  24. data/lib/parse/client/response.rb +59 -6
  25. data/lib/parse/client.rb +72 -42
  26. data/lib/parse/model/acl.rb +22 -4
  27. data/lib/parse/model/associations/belongs_to.rb +22 -10
  28. data/lib/parse/model/associations/collection_proxy.rb +14 -1
  29. data/lib/parse/model/associations/has_many.rb +76 -15
  30. data/lib/parse/model/associations/has_one.rb +69 -0
  31. data/lib/parse/model/associations/pointer_collection_proxy.rb +13 -6
  32. data/lib/parse/model/associations/relation_collection_proxy.rb +5 -2
  33. data/lib/parse/model/bytes.rb +6 -2
  34. data/lib/parse/model/classes/installation.rb +27 -0
  35. data/lib/parse/model/classes/role.rb +20 -0
  36. data/lib/parse/model/classes/session.rb +26 -0
  37. data/lib/parse/model/classes/user.rb +185 -0
  38. data/lib/parse/model/core/actions.rb +40 -26
  39. data/lib/parse/model/core/properties.rb +126 -20
  40. data/lib/parse/model/core/querying.rb +63 -3
  41. data/lib/parse/model/core/schema.rb +9 -6
  42. data/lib/parse/model/date.rb +5 -1
  43. data/lib/parse/model/file.rb +12 -9
  44. data/lib/parse/model/geopoint.rb +6 -4
  45. data/lib/parse/model/model.rb +29 -21
  46. data/lib/parse/model/object.rb +29 -76
  47. data/lib/parse/model/pointer.rb +8 -6
  48. data/lib/parse/model/push.rb +4 -1
  49. data/lib/parse/query/constraint.rb +3 -0
  50. data/lib/parse/query/constraints.rb +6 -3
  51. data/lib/parse/query/operation.rb +3 -0
  52. data/lib/parse/query/ordering.rb +3 -0
  53. data/lib/parse/query.rb +85 -38
  54. data/lib/parse/stack/generators/rails.rb +3 -0
  55. data/lib/parse/stack/railtie.rb +2 -0
  56. data/lib/parse/stack/tasks.rb +4 -1
  57. data/lib/parse/stack/version.rb +4 -1
  58. data/lib/parse/stack.rb +3 -0
  59. data/lib/parse/webhooks/payload.rb +14 -8
  60. data/lib/parse/webhooks/registration.rb +11 -8
  61. data/lib/parse/webhooks.rb +11 -8
  62. data/lib/parse-stack.rb +3 -0
  63. data/parse-stack.gemspec +10 -8
  64. metadata +16 -4
@@ -0,0 +1,69 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../pointer'
5
+ require_relative 'collection_proxy'
6
+ require_relative 'pointer_collection_proxy'
7
+ require_relative 'relation_collection_proxy'
8
+ # a given Parse Pointer. The key of the property is implied to be the
9
+ # name of the class/parse table that contains the foreign associated record.
10
+ # All belongs to relationship column types have the special data type of :pointer.
11
+ module Parse
12
+ module Associations
13
+
14
+ module HasOne
15
+
16
+ def self.included(base)
17
+ base.extend(ClassMethods)
18
+ end
19
+
20
+ module ClassMethods
21
+
22
+ # has one are not property but instance scope methods
23
+ def has_one(key, scope = nil, **opts)
24
+
25
+ opts.reverse_merge!({as: key, field: parse_class.columnize, scope_only: false})
26
+ klassName = opts[:as].to_parse_class
27
+ foreign_field = opts[:field].to_sym
28
+ ivar = :"@_has_one_#{key}"
29
+
30
+ if self.method_defined?(key)
31
+ puts "Creating has_one :#{key} association. Will overwrite existing method #{self}##{key}."
32
+ end
33
+
34
+ define_method(key) do |*args, &block|
35
+ return nil if @id.nil?
36
+ query = Parse::Query.new(klassName, limit: 1)
37
+ query.where(foreign_field => self) unless opts[:scope_only] == true
38
+
39
+ if scope.is_a?(Proc)
40
+ # any method not part of Query, gets delegated to the instance object
41
+ instance = self
42
+ query.define_singleton_method(:method_missing) { |m, *args, &block| instance.send(m, *args, &block) }
43
+ query.define_singleton_method(:i) { instance }
44
+
45
+ if scope.arity.zero?
46
+ query.instance_exec(&scope)
47
+ query.conditions(*args) if args.present?
48
+ else
49
+ query.instance_exec(*args,&scope)
50
+ end
51
+ instance = nil # help clean up ruby gc
52
+ elsif args.present?
53
+ query.conditions(*args)
54
+ end
55
+ # query.define_singleton_method(:method_missing) do |m, *args, &block|
56
+ # self.first.send(m, *args, &block)
57
+ # end
58
+ return query.first if block.nil?
59
+ block.call(query.first)
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+ end
@@ -1,3 +1,6 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
1
4
  require 'active_model'
2
5
  require 'active_support'
3
6
  require 'active_support/inflector'
@@ -21,8 +24,8 @@ module Parse
21
24
  # If they are not, and it is a hash, we check to see if it is a Parse hash.
22
25
  def add(*items)
23
26
  notify_will_change! if items.count > 0
24
- items.flatten.parse_pointers.each do |item|
25
- collection.push(item)
27
+ items.flatten.parse_objects.each do |item|
28
+ collection.push(item) if item.is_a?(Parse::Pointer)
26
29
  end
27
30
  @collection
28
31
  end
@@ -30,20 +33,20 @@ module Parse
30
33
  # removes items from the collection
31
34
  def remove(*items)
32
35
  notify_will_change! if items.count > 0
33
- items.flatten.parse_pointers.each do |item|
36
+ items.flatten.parse_objects.each do |item|
34
37
  collection.delete item
35
38
  end
36
39
  @collection
37
40
  end
38
-
41
+
39
42
  def add!(*items)
40
43
  super(items.flatten.parse_pointers)
41
44
  end
42
-
45
+
43
46
  def add_unique!(*items)
44
47
  super(items.flatten.parse_pointers)
45
48
  end
46
-
49
+
47
50
  def remove!(*items)
48
51
  super(items.flatten.parse_pointers)
49
52
  end
@@ -66,6 +69,10 @@ module Parse
66
69
  collection.parse_pointers.as_json
67
70
  end
68
71
 
72
+ def parse_pointers
73
+ collection.parse_pointers
74
+ end
75
+
69
76
  end
70
77
 
71
78
  end
@@ -1,3 +1,6 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
1
4
  require 'active_support'
2
5
  require 'active_support/inflector'
3
6
  require 'active_support/core_ext/object'
@@ -66,7 +69,7 @@ module Parse
66
69
  # is adding it to the @additions array and making sure it is
67
70
  # removed from the @removals array.
68
71
  def add(*items)
69
- items = items.flatten.parse_pointers
72
+ items = items.flatten.parse_objects
70
73
  return @collection if items.empty?
71
74
 
72
75
  notify_will_change!
@@ -86,7 +89,7 @@ module Parse
86
89
  # The process of removing is deleting it from the @removals array,
87
90
  # and adding it to the @additions array.
88
91
  def remove(*items)
89
- items = items.flatten.parse_pointers
92
+ items = items.flatten.parse_objects
90
93
  return @collection if items.empty?
91
94
  notify_will_change!
92
95
  additions_will_change!
@@ -1,3 +1,6 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
1
4
  require 'active_support'
2
5
  require 'active_support/core_ext/object'
3
6
  require_relative "model"
@@ -7,6 +10,7 @@ require 'base64'
7
10
  module Parse
8
11
 
9
12
  class Bytes < Model
13
+ ATTRIBUTES = {__type: :string, base64: :string }.freeze
10
14
  attr_accessor :base64
11
15
  def parse_class; TYPE_BYTES; end;
12
16
  def parse_class; self.class.parse_class; end;
@@ -18,7 +22,7 @@ module Parse
18
22
  end
19
23
 
20
24
  def attributes
21
- {__type: :string, base64: :string }.freeze
25
+ ATTRIBUTES
22
26
  end
23
27
 
24
28
  # takes a string and base64 encodes it
@@ -35,7 +39,7 @@ module Parse
35
39
  if a.is_a?(String)
36
40
  @bytes = a
37
41
  elsif a.is_a?(Hash)
38
- @bytes = a["base64".freeze] || @bytes
42
+ @bytes = a["base64"] || @bytes
39
43
  end
40
44
  end
41
45
 
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+ require_relative '../object'
4
+
5
+ module Parse
6
+
7
+ class Installation < Parse::Object
8
+ parse_class Parse::Model::CLASS_INSTALLATION
9
+
10
+ property :gcm_sender_id, :string, field: :GCMSenderId
11
+ property :app_identifier
12
+ property :app_name
13
+ property :app_version
14
+ property :badge, :integer
15
+ property :channels, :array
16
+ property :device_token
17
+ property :device_token_last_modified, :integer
18
+ property :device_type
19
+ property :installation_id
20
+ property :locale_identifier
21
+ property :parse_version
22
+ property :push_type
23
+ property :time_zone
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+ require_relative '../object'
4
+ require_relative 'user'
5
+ module Parse
6
+
7
+ class Role < Parse::Object
8
+ parse_class Parse::Model::CLASS_ROLE
9
+ property :name
10
+
11
+ has_many :roles, through: :relation
12
+ has_many :users, through: :relation
13
+
14
+ before_save do
15
+ acl.everyone true, false
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+ require_relative '../object'
4
+
5
+ module Parse
6
+ class Session < Parse::Object
7
+ parse_class Parse::Model::CLASS_SESSION
8
+ property :created_with, :object
9
+ property :expires_at, :date
10
+ property :installation_id
11
+ property :restricted, :boolean
12
+ property :session_token
13
+
14
+ belongs_to :user
15
+
16
+ def self.session(token)
17
+ response = client.fetch_session(token)
18
+ if response.success?
19
+ reutrn Parse::Session.build response.result
20
+ end
21
+ nil
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,185 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # The User class provided by Parse with the required fields. You may
5
+ # add mixings to this class to add the app specific properties
6
+ require_relative '../object'
7
+ module Parse
8
+
9
+ class UsernameMissingError < StandardError; end; # 200
10
+ class PasswordMissingError < StandardError; end; # 201
11
+ class UsernameTakenError < StandardError; end; # 202
12
+ class EmailTakenError < StandardError; end; # 203
13
+ class EmailMissing < StandardError; end; # 204
14
+
15
+ class User < Parse::Object
16
+
17
+ parse_class Parse::Model::CLASS_USER
18
+ attr_accessor :session_token
19
+ property :auth_data, :object
20
+ property :email
21
+ property :password
22
+ property :username
23
+
24
+ before_save do
25
+ # You cannot specify user ACLs.
26
+ self.clear_attribute_change!(:acl)
27
+ end
28
+
29
+ def anonymous?
30
+ anonymous_id.nil?
31
+ end
32
+
33
+ def anonymous_id
34
+ auth_data['anonymous']['id'] if auth_data.present? && auth_data["anonymous"].is_a?(Hash)
35
+ end
36
+
37
+ def link_auth_data!(service_name, **data)
38
+ response = client.set_service_auth_data(id, service_name, data)
39
+ apply_attributes!(response.result) if response.success?
40
+ response.success?
41
+ end
42
+
43
+ def unlink_auth_data!(service_name)
44
+ response = client.set_service_auth_data(id, service_name, nil)
45
+ apply_attributes!(response.result) if response.success?
46
+ response.success?
47
+ end
48
+
49
+ # So that apply_attributes! works with session_token for login
50
+ def session_token_set_attribute!(token, track = false)
51
+ @session_token = token.to_s
52
+ end
53
+ alias_method :sessionToken_set_attribute!, :session_token_set_attribute!
54
+
55
+ def logged_in?
56
+ self.session_token.present?
57
+ end
58
+
59
+ def request_password_reset
60
+ return false if email.nil?
61
+ Parse::User.request_password_reset(email)
62
+ end
63
+
64
+ def signup!(passwd = nil)
65
+ self.password = passwd || password
66
+ if username.blank?
67
+ raise Parse::UsernameMissingError, "Signup requires an username."
68
+ end
69
+
70
+ if password.blank?
71
+ raise Parse::PasswordMissingError, "Signup requires a password."
72
+ end
73
+
74
+ signup_attrs = attribute_updates
75
+ signup_attrs.except! *Parse::Properties::BASE_FIELD_MAP.flatten
76
+
77
+ # first signup the user, then save any additional attributes
78
+ response = client.create_user signup_attrs
79
+
80
+ if response.success?
81
+ apply_attributes! response.result
82
+ return true
83
+ end
84
+
85
+ case response.code
86
+ when Parse::Response::ERROR_USERNAME_MISSING
87
+ raise Parse::UsernameMissingError, response
88
+ when Parse::Response::ERROR_PASSWORD_MISSING
89
+ raise Parse::PasswordMissingError, response
90
+ when Parse::Response::ERROR_USERNAME_TAKEN
91
+ raise Parse::UsernameTakenError, response
92
+ when Parse::Response::ERROR_EMAIL_TAKEN
93
+ raise Parse::EmailTakenError, response
94
+ end
95
+ raise response
96
+ end
97
+
98
+ def login!(passwd = nil)
99
+ self.password = passwd || self.password
100
+ response = client.login(username.to_s, password.to_s)
101
+ apply_attributes! response.result
102
+ self.session_token.present?
103
+ end
104
+
105
+ def logout
106
+ return true if self.session_token.blank?
107
+ client.logout session_token
108
+ self.session_token = nil
109
+ true
110
+ rescue => e
111
+ false
112
+ end
113
+
114
+ def session_token=(token)
115
+ @session = nil
116
+ @session_token = token
117
+ end
118
+
119
+ def session
120
+ if @session.blank? && @session_token.present?
121
+ response = client.fetch_session(@session_token)
122
+ @session ||= Parse::Session.new(response.result)
123
+ end
124
+ @session
125
+ end
126
+
127
+ def self.create(body, **opts)
128
+ response = client.create_user(body, opts: opts)
129
+ if response.success?
130
+ return Parse::User.build response.result
131
+ end
132
+
133
+ case response.code
134
+ when Parse::Response::ERROR_USERNAME_MISSING
135
+ raise Parse::UsernameMissingError, response
136
+ when Parse::Response::ERROR_PASSWORD_MISSING
137
+ raise Parse::PasswordMissingError, response
138
+ when Parse::Response::ERROR_USERNAME_TAKEN
139
+ raise Parse::UsernameTakenError, response
140
+ when Parse::Response::ERROR_EMAIL_TAKEN
141
+ raise Parse::EmailTakenError, response
142
+ end
143
+ raise response
144
+ end
145
+
146
+ # method will signup or login a user given the third-party authentication data
147
+ def self.autologin_service(service_name, auth_data, body: {})
148
+ body = body.merge({authData: {service_name => auth_data} })
149
+ self.create(body)
150
+ end
151
+
152
+ def self.signup(username, password, email = nil, body: {})
153
+ body = body.merge({username: username, password: password })
154
+ body[:email] = email if email.present?
155
+ self.create(body)
156
+ end
157
+
158
+ def self.login(username, password)
159
+ response = client.login(username.to_s, password.to_s)
160
+ response.success? ? Parse::User.build(response.result) : nil
161
+ end
162
+
163
+ def self.request_password_reset(email)
164
+ email = email.email if email.is_a?(Parse::User)
165
+ return false if email.blank?
166
+ response = client.reset_password(email)
167
+ response.success?
168
+ end
169
+
170
+ def self.session(token)
171
+ self.session! token
172
+ rescue InvalidSessionTokenError => e
173
+ nil
174
+ end
175
+
176
+ def self.session!(token)
177
+ # support Parse::Session objects
178
+ token = token.session_token if token.respond_to?(:session_token)
179
+ response = client.current_user(token)
180
+ response.success? ? Parse::User.build(response.result) : nil
181
+ end
182
+
183
+ end
184
+
185
+ end
@@ -1,3 +1,6 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
1
4
  require 'active_model'
2
5
  require 'active_support'
3
6
  require 'active_support/inflector'
@@ -14,8 +17,8 @@ require_relative '../../client/request'
14
17
  # hash request format Parse needs in order to modify relational information for classes.
15
18
  module Parse
16
19
  class RelationAction
17
- ADD = "AddRelation".freeze
18
- REMOVE = "RemoveRelation".freeze
20
+ ADD = "AddRelation"
21
+ REMOVE = "RemoveRelation"
19
22
  attr_accessor :polarity, :key, :objects
20
23
  # provide the column name of the field, polarity (true = add, false = remove) and the
21
24
  # list of objects.
@@ -153,7 +156,7 @@ module Parse
153
156
  op_hash = { field => op_hash }.as_json
154
157
  end
155
158
 
156
- response = client.update_object(parse_class, id, op_hash )
159
+ response = client.update_object(parse_class, id, op_hash, session_token: _session_token )
157
160
  if response.error?
158
161
  puts "[#{parse_class}:#{field} Operation] #{response.error}"
159
162
  end
@@ -243,7 +246,7 @@ module Parse
243
246
  warn "[#{parse_class}] warning: #{msg}"
244
247
  end
245
248
  end
246
- response = client.update_object(parse_class, id, attribute_updates)
249
+ response = client.update_object(parse_class, id, attribute_updates, session_token: _session_token)
247
250
  if response.success?
248
251
  result = response.result
249
252
  # Because beforeSave hooks can change the fields we are saving, any items that were
@@ -263,28 +266,38 @@ module Parse
263
266
 
264
267
  # create this object in Parse
265
268
  def create
266
- res = client.create_object(parse_class, attribute_updates )
267
- unless res.error?
268
- result = res.result
269
- @id = result["objectId"] || @id
270
- @created_at = result["createdAt"] || @created_at
271
- #if the object is created, updatedAt == createdAt
272
- @updated_at = result["updatedAt"] || result["createdAt"] || @updated_at
273
- # Because beforeSave hooks can change the fields we are saving, any items that were
274
- # changed, are returned to us and we should apply those locally to be in sync.
275
- set_attributes!(result)
269
+ run_callbacks :create do
270
+ res = client.create_object(parse_class, attribute_updates, session_token: _session_token)
271
+ unless res.error?
272
+ result = res.result
273
+ @id = result[Parse::Model::OBJECT_ID] || @id
274
+ @created_at = result["createdAt"] || @created_at
275
+ #if the object is created, updatedAt == createdAt
276
+ @updated_at = result["updatedAt"] || result["createdAt"] || @updated_at
277
+ # Because beforeSave hooks can change the fields we are saving, any items that were
278
+ # changed, are returned to us and we should apply those locally to be in sync.
279
+ set_attributes!(result)
280
+ end
281
+ puts "Error creating #{self.parse_class}: #{res.error}" if res.error?
282
+ res.success?
276
283
  end
277
- puts "Error creating #{self.parse_class}: #{res.error}" if res.error?
278
- res.success?
284
+ end
285
+
286
+ def _session_token
287
+ if @_session_token.respond_to?(:session_token)
288
+ @_session_token = @_session_token.session_token
289
+ end
290
+ @_session_token
279
291
  end
280
292
 
281
293
  # saves the object. If the object has not changed, it is a noop. If it is new,
282
294
  # we will create the object. If the object has an id, we will update the record.
283
295
  # You can define before and after :save callbacks
284
296
  # autoraise: set to true will automatically raise an exception if the save fails
285
- def save(autoraise: false)
297
+ def save(autoraise: false, session: nil)
286
298
  return true unless changed?
287
299
  success = false
300
+ @_session_token = session
288
301
  run_callbacks :save do
289
302
  #first process the create/update action if any
290
303
  #then perform any relation changes that need to be performed
@@ -311,21 +324,23 @@ module Parse
311
324
  end
312
325
 
313
326
  end #callbacks
327
+ @_session_token = nil
314
328
  success
315
329
  end
316
330
 
317
331
  # shortcut for raising an exception of saving this object failed.
318
- def save!
319
- save(autoraise: true)
332
+ def save!(session: nil)
333
+ save(autoraise: true, session: session)
320
334
  end
321
335
 
322
336
  # only destroy the object if it has an id. You can setup before and after
323
337
  #callback hooks on :destroy
324
- def destroy
338
+ def destroy(session: nil)
325
339
  return false if new?
340
+ @_session_token = session
326
341
  success = false
327
342
  run_callbacks :destroy do
328
- res = client.delete_object parse_class, id
343
+ res = client.delete_object parse_class, id, session_token: _session_token
329
344
  success = res.success?
330
345
  if success
331
346
  @id = nil
@@ -335,6 +350,7 @@ module Parse
335
350
  end
336
351
  # Your create action methods here
337
352
  end
353
+ @_session_token = nil
338
354
  success
339
355
  end
340
356
 
@@ -386,16 +402,14 @@ module Parse
386
402
  raise "Unable to update relations for a new object." if new?
387
403
  # get all the relational changes (both additions and removals)
388
404
  additions, removals = relation_change_operations
389
- # removal_response = client.update_object(parse_class, id, removals)
390
- # addition_response = client.update_object(parse_class, id, additions)
405
+
391
406
  responses = []
392
407
  # Send parallel Parse requests for each of the items to update.
393
408
  # since we will have multiple responses, we will track it in array
394
409
  [removals, additions].threaded_each do |ops|
395
410
  next if ops.empty? #if no operations to be performed, then we are done
396
- responses << client.update_object(parse_class, @id, ops)
411
+ responses << client.update_object(parse_class, @id, ops, session_token: _session_token)
397
412
  end
398
- #response = client.update_object(parse_class, id, relation_updates)
399
413
  # check if any of them ended up in error
400
414
  has_error = responses.any? { |response| response.error? }
401
415
  # if everything was ok, find the last response to be returned and update
@@ -410,7 +424,7 @@ module Parse
410
424
  def set_attributes!(hash, dirty_track = false)
411
425
  return unless hash.is_a?(Hash)
412
426
  hash.each do |k,v|
413
- next if k == "objectId".freeze || k == "id".freeze
427
+ next if k == Parse::Model::OBJECT_ID || k == Parse::Model::ID
414
428
  method = "#{k}_set_attribute!"
415
429
  send(method, v, dirty_track) if respond_to?(method)
416
430
  end