parse-stack 1.4.3 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
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