leancloud-ruby-client 0.1.0

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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +9 -0
  3. data/Gemfile +16 -0
  4. data/Gemfile.lock +86 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +1177 -0
  7. data/Rakefile +45 -0
  8. data/VERSION +1 -0
  9. data/example.rb +35 -0
  10. data/features.md +1111 -0
  11. data/fixtures/vcr_cassettes/test_acls_arent_objects.yml +274 -0
  12. data/fixtures/vcr_cassettes/test_array_add.yml +213 -0
  13. data/fixtures/vcr_cassettes/test_array_add_pointerizing.yml +380 -0
  14. data/fixtures/vcr_cassettes/test_array_add_unique.yml +319 -0
  15. data/fixtures/vcr_cassettes/test_batch_create_object.yml +107 -0
  16. data/fixtures/vcr_cassettes/test_batch_delete_object.yml +637 -0
  17. data/fixtures/vcr_cassettes/test_batch_run.yml +109 -0
  18. data/fixtures/vcr_cassettes/test_batch_update_nils_delete_keys.yml +435 -0
  19. data/fixtures/vcr_cassettes/test_batch_update_object.yml +637 -0
  20. data/fixtures/vcr_cassettes/test_contains_all.yml +1143 -0
  21. data/fixtures/vcr_cassettes/test_cql.yml +99 -0
  22. data/fixtures/vcr_cassettes/test_created_at.yml +109 -0
  23. data/fixtures/vcr_cassettes/test_decrement.yml +213 -0
  24. data/fixtures/vcr_cassettes/test_deep_parse.yml +321 -0
  25. data/fixtures/vcr_cassettes/test_destroy.yml +213 -0
  26. data/fixtures/vcr_cassettes/test_empty_response.yml +1026 -0
  27. data/fixtures/vcr_cassettes/test_eq_pointerize.yml +427 -0
  28. data/fixtures/vcr_cassettes/test_equality.yml +321 -0
  29. data/fixtures/vcr_cassettes/test_get.yml +215 -0
  30. data/fixtures/vcr_cassettes/test_get_installation.yml +58 -0
  31. data/fixtures/vcr_cassettes/test_get_missing.yml +160 -0
  32. data/fixtures/vcr_cassettes/test_image_file_associate_with_object.yml +2089 -0
  33. data/fixtures/vcr_cassettes/test_image_file_save.yml +1928 -0
  34. data/fixtures/vcr_cassettes/test_include.yml +321 -0
  35. data/fixtures/vcr_cassettes/test_new_model.yml +109 -0
  36. data/fixtures/vcr_cassettes/test_new_object.yml +109 -0
  37. data/fixtures/vcr_cassettes/test_nils_delete_keys.yml +319 -0
  38. data/fixtures/vcr_cassettes/test_object_id.yml +56 -0
  39. data/fixtures/vcr_cassettes/test_parse_delete.yml +421 -0
  40. data/fixtures/vcr_cassettes/test_pointer.yml +109 -0
  41. data/fixtures/vcr_cassettes/test_request_sms.yml +48 -0
  42. data/fixtures/vcr_cassettes/test_reset_password.yml +109 -0
  43. data/fixtures/vcr_cassettes/test_retries.yml +4173 -0
  44. data/fixtures/vcr_cassettes/test_retries_404.yml +1026 -0
  45. data/fixtures/vcr_cassettes/test_retries_404_correct.yml +1026 -0
  46. data/fixtures/vcr_cassettes/test_retries_json_error.yml +2265 -0
  47. data/fixtures/vcr_cassettes/test_retries_server_error.yml +2265 -0
  48. data/fixtures/vcr_cassettes/test_save_installation.yml +58 -0
  49. data/fixtures/vcr_cassettes/test_save_with_sub_objects.yml +484 -0
  50. data/fixtures/vcr_cassettes/test_saving_boolean_values.yml +215 -0
  51. data/fixtures/vcr_cassettes/test_saving_nested_objects.yml +62 -0
  52. data/fixtures/vcr_cassettes/test_server_update.yml +586 -0
  53. data/fixtures/vcr_cassettes/test_simple_save.yml +109 -0
  54. data/fixtures/vcr_cassettes/test_text_file_save.yml +109 -0
  55. data/fixtures/vcr_cassettes/test_update.yml +213 -0
  56. data/fixtures/vcr_cassettes/test_updated_at.yml +213 -0
  57. data/fixtures/vcr_cassettes/test_user_login.yml +276 -0
  58. data/fixtures/vcr_cassettes/test_user_save.yml +109 -0
  59. data/fixtures/vcr_cassettes/test_xget.yml +603 -0
  60. data/leancloud-ruby-client.gemspec +166 -0
  61. data/lib/faraday/better_retry.rb +94 -0
  62. data/lib/faraday/extended_parse_json.rb +39 -0
  63. data/lib/faraday/get_method_override.rb +32 -0
  64. data/lib/leancloud-ruby-client.rb +34 -0
  65. data/lib/leancloud/application.rb +7 -0
  66. data/lib/leancloud/batch.rb +53 -0
  67. data/lib/leancloud/client.rb +149 -0
  68. data/lib/leancloud/cloud.rb +28 -0
  69. data/lib/leancloud/datatypes.rb +355 -0
  70. data/lib/leancloud/error.rb +42 -0
  71. data/lib/leancloud/installation.rb +57 -0
  72. data/lib/leancloud/model.rb +14 -0
  73. data/lib/leancloud/object.rb +252 -0
  74. data/lib/leancloud/protocol.rb +193 -0
  75. data/lib/leancloud/push.rb +48 -0
  76. data/lib/leancloud/query.rb +194 -0
  77. data/lib/leancloud/user.rb +38 -0
  78. data/lib/leancloud/util.rb +93 -0
  79. data/test/cloud_functions/MyCloudCode/cloud/main.js +4 -0
  80. data/test/config/global.json +14 -0
  81. data/test/helper.rb +108 -0
  82. data/test/middleware/better_retry_test.rb +57 -0
  83. data/test/middleware/extend_parse_json_test.rb +55 -0
  84. data/test/parsers.jpg +0 -0
  85. data/test/test_batch.rb +132 -0
  86. data/test/test_client.rb +183 -0
  87. data/test/test_cloud.rb +31 -0
  88. data/test/test_datatypes.rb +105 -0
  89. data/test/test_file.rb +67 -0
  90. data/test/test_init.rb +23 -0
  91. data/test/test_init_from_cloud_code.rb +8 -0
  92. data/test/test_installation.rb +49 -0
  93. data/test/test_model.rb +22 -0
  94. data/test/test_object.rb +295 -0
  95. data/test/test_push.rb +45 -0
  96. data/test/test_query.rb +198 -0
  97. data/test/test_throttle.rb +5 -0
  98. data/test/test_user.rb +60 -0
  99. metadata +298 -0
@@ -0,0 +1,14 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module AV
3
+ class Model < AV::Object
4
+
5
+ def initialize(data=nil)
6
+ super(self.class.to_s,data)
7
+ end
8
+
9
+ def self.find(object_id)
10
+ self.new AV::Query.new(self.to_s).eq('objectId',object_id).first
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,252 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'leancloud/protocol'
3
+ require 'leancloud/client'
4
+ require 'leancloud/error'
5
+
6
+ module AV
7
+
8
+ # Represents an individual Parse API object.
9
+ class Object < Hash
10
+ attr_reader :parse_object_id
11
+ attr_reader :class_name
12
+ attr_reader :created_at
13
+ attr_reader :updated_at
14
+ alias :id :parse_object_id
15
+
16
+ def initialize(class_name, data = nil)
17
+ @class_name = class_name
18
+ @op_fields = {}
19
+ if data
20
+ parse data
21
+ end
22
+ end
23
+
24
+ def eql?(other)
25
+ AV.object_pointer_equality?(self, other)
26
+ end
27
+
28
+ alias == eql?
29
+
30
+ def hash
31
+ AV.object_pointer_hash(self)
32
+ end
33
+
34
+ def uri
35
+ Protocol.class_uri @class_name, @parse_object_id
36
+ end
37
+
38
+ def pointer
39
+ AV::Pointer.new(rest_api_hash) unless new?
40
+ end
41
+
42
+ # make it easier to deal with the ambiguity of whether you're passed a pointer or object
43
+ def get
44
+ self
45
+ end
46
+
47
+ def new?
48
+ self["objectId"].nil?
49
+ end
50
+
51
+ def update_attributes(data={})
52
+ data.each_pair { |k,v| self[k] = v }
53
+ save
54
+ end
55
+
56
+ # Write the current state of the local object to the API.
57
+ # If the object has never been saved before, this will create
58
+ # a new object, otherwise it will update the existing stored object.
59
+ def save
60
+ if @parse_object_id
61
+ method = :put
62
+ self.merge!(@op_fields) # use operations instead of our own view of the columns
63
+ else
64
+ method = :post
65
+ end
66
+
67
+ body = safe_hash.to_json
68
+ uri = self.uri
69
+ uri = uri + '?new=true' if method == :put
70
+ data = AV.client.request(uri, method, body)
71
+
72
+ if data
73
+ # array operations can return mutated view of array which needs to be parsed
74
+ parse AV.parse_json(class_name, data)
75
+ end
76
+
77
+ if @class_name == AV::Protocol::CLASS_USER
78
+ self.delete("password")
79
+ self.delete(:username)
80
+ self.delete(:password)
81
+ end
82
+
83
+ self
84
+ end
85
+
86
+ # representation of object to send on saves
87
+ def safe_hash
88
+ Hash[self.map do |key, value|
89
+ if Protocol::RESERVED_KEYS.include?(key)
90
+ nil
91
+ elsif value.is_a?(Hash) && value[Protocol::KEY_TYPE] == Protocol::TYPE_RELATION
92
+ nil
93
+ elsif value.nil?
94
+ [key, Protocol::DELETE_OP]
95
+ else
96
+ [key, AV.pointerize_value(value)]
97
+ end
98
+ end.compact]
99
+ end
100
+
101
+ # full REST api representation of object
102
+ def rest_api_hash
103
+ self.merge(AV::Protocol::KEY_CLASS_NAME => class_name)
104
+ end
105
+
106
+ # Handle the addition of Array#to_h in Ruby 2.1
107
+ def should_call_to_h?(value)
108
+ value.respond_to?(:to_h) && !value.kind_of?(Array)
109
+ end
110
+
111
+ def to_h(*a)
112
+ Hash[rest_api_hash.map do |key, value|
113
+ [key, should_call_to_h?(value) ? value.to_h : value]
114
+ end]
115
+ end
116
+ alias :as_json :to_h
117
+ alias :to_hash :to_h
118
+
119
+ def to_json(*a)
120
+ to_h.to_json(*a)
121
+ end
122
+
123
+ def to_s
124
+ "#{@class_name}:#{@parse_object_id} #{super}"
125
+ end
126
+
127
+ def inspect
128
+ "#{@class_name}:#{@parse_object_id} #{super}"
129
+ end
130
+
131
+ # Update the fields of the local Parse object with the current
132
+ # values from the API.
133
+ def refresh
134
+ if @parse_object_id
135
+ data = AV.get @class_name, @parse_object_id
136
+ clear
137
+ if data
138
+ parse data
139
+ end
140
+ end
141
+
142
+ self
143
+ end
144
+
145
+ # Delete the remote Parse API object.
146
+ def parse_delete
147
+ if @parse_object_id
148
+ response = AV.client.delete self.uri
149
+ end
150
+
151
+ self.clear
152
+ self
153
+ end
154
+
155
+ def array_add(field, value)
156
+ array_op(field, Protocol::KEY_ADD, value)
157
+ end
158
+
159
+ def array_add_relation(field, value)
160
+ array_op(field, Protocol::KEY_ADD_RELATION, value)
161
+ end
162
+
163
+ def array_remove_relation(field, value)
164
+ array_op(field, Protocol::KEY_REMOVE_RELATION, value)
165
+ end
166
+
167
+ def array_add_unique(field, value)
168
+ array_op(field, Protocol::KEY_ADD_UNIQUE, value)
169
+ end
170
+
171
+ def array_remove(field, value)
172
+ array_op(field, Protocol::KEY_REMOVE, value)
173
+ end
174
+
175
+ # Increment the given field by an amount, which defaults to 1. Saves immediately to reflect incremented
176
+ def increment(field, amount = 1)
177
+ #value = (self[field] || 0) + amount
178
+ #self[field] = value
179
+ #if !@parse_object_id
180
+ # # TODO - warn that the object must be stored first
181
+ # return nil
182
+ #end
183
+
184
+ body = {field => AV::Increment.new(amount)}.to_json
185
+ data = AV.client.request(self.uri() + '?new=true', :put, body)
186
+ parse data
187
+ self
188
+ end
189
+
190
+ # Decrement the given field by an amount, which defaults to 1. Saves immediately to reflect decremented
191
+ # A synonym for increment(field, -amount).
192
+ def decrement(field, amount = 1)
193
+ increment(field, -amount)
194
+ end
195
+
196
+ private
197
+
198
+ # Merge a hash parsed from the JSON representation into
199
+ # this instance. This will extract the reserved fields,
200
+ # merge the hash keys, and then ensure that the reserved
201
+ # fields do not occur in the underlying hash storage.
202
+ def parse(data)
203
+ if !data
204
+ return
205
+ end
206
+
207
+ @parse_object_id ||= data[Protocol::KEY_OBJECT_ID]
208
+
209
+ if data.has_key? Protocol::KEY_CREATED_AT
210
+ @created_at = DateTime.parse data[Protocol::KEY_CREATED_AT]
211
+ end
212
+
213
+ if data.has_key? Protocol::KEY_UPDATED_AT
214
+ @updated_at = DateTime.parse data[Protocol::KEY_UPDATED_AT]
215
+ end
216
+
217
+ data.each do |k,v|
218
+ if k.is_a? Symbol
219
+ k = k.to_s
220
+ end
221
+
222
+ if k != AV::Protocol::KEY_TYPE
223
+ self[k] = v
224
+ end
225
+ end
226
+
227
+ self
228
+ end
229
+
230
+ def array_op(field, operation, value)
231
+ raise "field #{field} not an array" if self[field] && !self[field].is_a?(Array)
232
+
233
+ if @parse_object_id
234
+ @op_fields[field] ||= ArrayOp.new(operation, [])
235
+ raise "only one operation type allowed per array #{field}" if @op_fields[field].operation != operation
236
+ @op_fields[field].objects << AV.pointerize_value(value)
237
+ end
238
+
239
+ # parse doesn't return column values on initial POST creation so we must maintain them ourselves
240
+ case operation
241
+ when Protocol::KEY_ADD, Protocol::KEY_ADD_RELATION
242
+ self[field] ||= []
243
+ self[field] << value
244
+ when Protocol::KEY_ADD_UNIQUE
245
+ self[field] ||= []
246
+ self[field] << value unless self[field].include?(value)
247
+ when Protocol::KEY_REMOVE, Protocol::KEY_REMOVE_RELATION
248
+ self[field].delete(value) if self[field]
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,193 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module AV
3
+ # A module which encapsulates the specifics of Parse's REST API.
4
+ module Protocol
5
+
6
+ # Basics
7
+ # ----------------------------------------
8
+
9
+ # The default hostname for communication with the Parse API.
10
+ HOST = "api.leancloud.cn"
11
+
12
+ # The version of the REST API implemented by this module.
13
+ VERSION = 1.1
14
+
15
+ # HTTP Headers
16
+ # ----------------------------------------
17
+
18
+ # The HTTP header used for passing your application ID to the
19
+ # Parse API.
20
+ HEADER_APP_ID = "X-AVOSCloud-Application-Id"
21
+
22
+ # The HTTP header used for passing your API key to the
23
+ # Parse API.
24
+ HEADER_API_KEY = "X-AVOSCloud-Application-Key"
25
+
26
+ # The HTTP header used for passing your API Master key to the
27
+ # Parse API.
28
+ HEADER_MASTER_KEY = "X-AVOSCloud-Master-Key"
29
+
30
+ # The HTTP header used for passing your authenticated session
31
+ HEADER_SESSION_TOKEN = "X-AVOSCloud-Session-Token"
32
+
33
+ # JSON Keys
34
+ # ----------------------------------------
35
+
36
+ # The JSON key used to store the class name of an object
37
+ # in a Pointer datatype.
38
+ KEY_CLASS_NAME = "className"
39
+
40
+ # The JSON key used to store the ID of Parse objects
41
+ # in their JSON representation.
42
+ KEY_OBJECT_ID = "objectId"
43
+
44
+ # The JSON key used to store the creation timestamp of
45
+ # Parse objects in their JSON representation.
46
+ KEY_CREATED_AT = "createdAt"
47
+
48
+ # The JSON key used to store the last modified timestamp
49
+ # of Parse objects in their JSON representation.
50
+ KEY_UPDATED_AT = "updatedAt"
51
+
52
+ KEY_USER_SESSION_TOKEN = "sessionToken"
53
+
54
+ # The JSON key used in the top-level response object
55
+ # to indicate that the response contains an array of objects.
56
+ RESPONSE_KEY_RESULTS = "results"
57
+ KEY_RESULTS = RESPONSE_KEY_RESULTS
58
+
59
+ # The JSON key used to identify an operator
60
+ KEY_OP = "__op"
61
+
62
+ KEY_INCREMENT = "Increment"
63
+ KEY_DELETE = "Delete"
64
+
65
+ # array ops
66
+ KEY_OBJECTS = "objects"
67
+ KEY_ADD = "Add"
68
+ KEY_ADD_RELATION = "AddRelation"
69
+ KEY_REMOVE_RELATION = "RemoveRelation"
70
+ KEY_ADD_UNIQUE = "AddUnique"
71
+ KEY_REMOVE = "Remove"
72
+
73
+ DELETE_OP = { KEY_OP => KEY_DELETE }
74
+
75
+ # The JSON key used to identify the datatype of a special value.
76
+ KEY_TYPE = "__type"
77
+
78
+ # The JSON key used to specify the numerical value in the
79
+ # increment/decrement API call.
80
+ KEY_AMOUNT = "amount"
81
+
82
+ RESERVED_KEYS = [ KEY_CLASS_NAME, KEY_CREATED_AT, KEY_OBJECT_ID, KEY_UPDATED_AT, KEY_USER_SESSION_TOKEN]
83
+
84
+ # Other Constants
85
+ # ----------------------------------------
86
+
87
+ # Operation name for incrementing an objects field value remotely
88
+ OP_INCREMENT = "Increment"
89
+
90
+ # The data type name for special JSON objects representing a full object
91
+ TYPE_OBJECT = "Object"
92
+
93
+ # The data type name for special JSON objects representing a reference
94
+ # to another Parse object.
95
+ TYPE_POINTER = "Pointer"
96
+
97
+ # The data type name for special JSON objects containing an array of
98
+ # encoded bytes.
99
+ TYPE_BYTES = "Bytes"
100
+
101
+ # The data type name for special JSON objects representing a date/time.
102
+ TYPE_DATE = "Date"
103
+
104
+ # The data type name for special JSON objects representing a
105
+ # location specified as a latitude/longitude pair.
106
+ TYPE_GEOPOINT = "GeoPoint"
107
+
108
+ # The data type name for special JSON objects representing
109
+ # a file.
110
+ TYPE_FILE = "File"
111
+
112
+ # The data type name for special JSON objects representing
113
+ # a Relation.
114
+ TYPE_RELATION = "Relation"
115
+
116
+ # The class name for User objects, when referenced by a Pointer.
117
+ CLASS_USER = "_User"
118
+
119
+ CLASS_INSTALLATION = "_Installation"
120
+
121
+ USER_LOGIN_URI = "/#{VERSION}/login"
122
+ PASSWORD_RESET_URI = "/#{VERSION}/requestPasswordReset"
123
+ SMS_URI = "/#{VERSION}/requestSmsCode"
124
+
125
+ CLOUD_FUNCTIONS_PATH = "functions"
126
+
127
+ BATCH_REQUEST_URI = "batch"
128
+
129
+ ERROR_INTERNAL = 1
130
+ ERROR_TIMEOUT = 124
131
+ ERROR_EXCEEDED_BURST_LIMIT = 155
132
+ ERROR_OBJECT_NOT_FOUND_FOR_GET = 101
133
+
134
+ # URI Helpers
135
+ # ----------------------------------------
136
+
137
+ def Protocol.config_uri
138
+ "/#{VERSION}/config"
139
+ end
140
+
141
+ # Construct a uri referencing a given Parse object
142
+ # class or instance (of object_id is non-nil).
143
+ def Protocol.class_uri(class_name, object_id = nil)
144
+ if object_id
145
+ "/#{VERSION}/classes/#{class_name}/#{object_id}"
146
+ else
147
+ "/#{VERSION}/classes/#{class_name}"
148
+ end
149
+ end
150
+
151
+ # Construct a uri referencing a given Parse installation
152
+ # class or instance (of object_id is non-nil).
153
+ def Protocol.installation_uri(object_id = nil)
154
+ if object_id
155
+ "/#{VERSION}/installations/#{object_id}"
156
+ else
157
+ "/#{VERSION}/installations"
158
+ end
159
+ end
160
+
161
+ # Construct a uri referencing a given Parse user
162
+ # instance or the users category.
163
+ def Protocol.user_uri(user_id = nil)
164
+ if user_id
165
+ "/#{VERSION}/users/#{user_id}"
166
+ else
167
+ "/#{VERSION}/users"
168
+ end
169
+ end
170
+
171
+ # Construct a uri referencing a file stored by the API.
172
+ def Protocol.file_uri(file_name)
173
+ "/#{VERSION}/files/#{file_name}"
174
+ end
175
+
176
+ # Construct a uri to send a push notification via the API.
177
+ def Protocol.push_uri
178
+ "/#{VERSION}/push"
179
+ end
180
+
181
+ def Protocol.cql_uri
182
+ "/#{VERSION}/cloudQuery"
183
+ end
184
+
185
+ def Protocol.cloud_function_uri(function_name)
186
+ "/#{VERSION}/#{CLOUD_FUNCTIONS_PATH}/#{function_name}"
187
+ end
188
+
189
+ def Protocol.batch_request_uri
190
+ "/#{VERSION}/#{BATCH_REQUEST_URI}"
191
+ end
192
+ end
193
+ end