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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3617333f35eb6f4b3a8aa0cba846a6535e09fa8b
4
+ data.tar.gz: 4dd626cd22ccc2bfca31a8dbf796ed5e1c47c498
5
+ SHA512:
6
+ metadata.gz: 02f350d181fe0cfa4fe1e0b6d5096ebae8af189ead246e81eb55aea4b2f735da2b6738d1f5ba804e3a65ff1bf4cf0033c544db92cb97468cb6ace653d72d2a00
7
+ data.tar.gz: 250894fa5df07bf30a2b4dad1cad27cd122af0080a231c4a72512169fa339307b60e63987cf30656667d3576aa4289a983973a3f5e66a3c075a8f6779c8538ee
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - 2.0.0
5
+ - 1.9.3
6
+ - jruby-19mode
7
+
8
+ env:
9
+ - PARSE_APPLICATION_ID=Slw1ACyMSVguo79pWvfIq15pkUjfwTLNPpL4984b PARSE_REST_API_KEY=qJKM8CGOAn70WOyR16f16YbyKWM0nBJCEbbtAMOm
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source 'https://ruby.taobao.org'
2
+
3
+ group :development do
4
+ gem 'bundler'
5
+ gem 'shoulda', '>= 0'
6
+ gem 'test-unit', '= 2.5.0'
7
+ gem 'mocha', '= 0.12.0', :require => false
8
+ gem 'jeweler', '~> 1.8.5'
9
+ gem 'simplecov', :require => false
10
+ gem 'webmock', '~> 1.9.0'
11
+ gem 'vcr'
12
+ gem 'pry'
13
+ end
14
+
15
+ gem 'faraday'
16
+ gem 'faraday_middleware'
data/Gemfile.lock ADDED
@@ -0,0 +1,86 @@
1
+ GEM
2
+ remote: https://ruby.taobao.org/
3
+ specs:
4
+ addressable (2.3.5)
5
+ builder (3.2.2)
6
+ coderay (1.1.0)
7
+ crack (0.4.1)
8
+ safe_yaml (~> 0.9.0)
9
+ faraday (0.8.7)
10
+ multipart-post (~> 1.1)
11
+ faraday_middleware (0.9.0)
12
+ faraday (>= 0.7.4, < 0.9)
13
+ git (1.2.5)
14
+ github_api (0.10.1)
15
+ addressable
16
+ faraday (~> 0.8.1)
17
+ hashie (>= 1.2)
18
+ multi_json (~> 1.4)
19
+ nokogiri (~> 1.5.2)
20
+ oauth2
21
+ hashie (2.0.5)
22
+ highline (1.6.19)
23
+ httpauth (0.2.0)
24
+ jeweler (1.8.6)
25
+ builder
26
+ bundler (~> 1.0)
27
+ git (>= 1.2.5)
28
+ github_api (= 0.10.1)
29
+ highline (>= 1.6.15)
30
+ nokogiri (= 1.5.10)
31
+ rake
32
+ rdoc
33
+ json (1.8.0)
34
+ jwt (0.1.8)
35
+ multi_json (>= 1.5)
36
+ metaclass (0.0.1)
37
+ method_source (0.8.2)
38
+ mocha (0.12.0)
39
+ metaclass (~> 0.0.1)
40
+ multi_json (1.7.7)
41
+ multi_xml (0.5.4)
42
+ multipart-post (1.2.0)
43
+ nokogiri (1.5.10)
44
+ oauth2 (0.9.2)
45
+ faraday (~> 0.8)
46
+ httpauth (~> 0.2)
47
+ jwt (~> 0.1.4)
48
+ multi_json (~> 1.0)
49
+ multi_xml (~> 0.5)
50
+ rack (~> 1.2)
51
+ pry (0.9.12.6)
52
+ coderay (~> 1.0)
53
+ method_source (~> 0.8)
54
+ slop (~> 3.4)
55
+ rack (1.5.2)
56
+ rake (10.1.0)
57
+ rdoc (4.0.1)
58
+ json (~> 1.4)
59
+ safe_yaml (0.9.7)
60
+ shoulda (2.11.3)
61
+ simplecov (0.7.1)
62
+ multi_json (~> 1.0)
63
+ simplecov-html (~> 0.7.1)
64
+ simplecov-html (0.7.1)
65
+ slop (3.4.7)
66
+ test-unit (2.5.0)
67
+ vcr (2.4.0)
68
+ webmock (1.9.3)
69
+ addressable (>= 2.2.7)
70
+ crack (>= 0.3.2)
71
+
72
+ PLATFORMS
73
+ ruby
74
+
75
+ DEPENDENCIES
76
+ bundler
77
+ faraday
78
+ faraday_middleware
79
+ jeweler (~> 1.8.5)
80
+ mocha (= 0.12.0)
81
+ pry
82
+ shoulda
83
+ simplecov
84
+ test-unit (= 2.5.0)
85
+ vcr
86
+ webmock (~> 1.9.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Alan deLevie
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,1177 @@
1
+ ## Summary
2
+
3
+ leancloud-ruby-client 从 [parse-ruby-client](https://github.com/adelevie/parse-ruby-client) 移植过来,可以调用 [leancloud.cn](https://leancloud.cn) 的 [REST API](https://leancloud.cn/docs/rest_api.html)。
4
+
5
+ 除了简单的重命名和调用地址改动之外,还做了下列事情:
6
+
7
+ * 增加短信 API `AV::Cloud.request_sms_code(params)`
8
+ * 增加 CQL 调用 `AV::Query.do_cloud_query(cql, pvalues)`
9
+ * 其他兼容性改进和测试,特别是文件
10
+ * 推送增加可以指定 iOS 生产或者测试证书功能 `production` 属性,值为 `true/false`。
11
+
12
+ ### Quick Reference
13
+
14
+ #### Installation
15
+
16
+ `gem install leancloud-ruby-client` or 添加 `gem "leancloud-ruby-client"` 到你项目的 `Gemfile.`
17
+
18
+ #### Configuration
19
+
20
+ ```ruby
21
+ require 'leancloud-ruby-client'
22
+
23
+ AV.init :application_id => "<your_app_id>",
24
+ :api_key => "<your_api_key>",
25
+ :quiet => true | false
26
+ ```
27
+
28
+ [![Gem Version](https://badge.fury.io/rb/parse-ruby-client.png)](http://badge.fury.io/rb/parse-ruby-client)
29
+
30
+ [![Build Status](https://travis-ci.org/adelevie/parse-ruby-client.png?branch=master)](https://travis-ci.org/adelevie/parse-ruby-client)
31
+
32
+ [![Code Climate](https://codeclimate.com/github/adelevie/parse-ruby-client.png)](https://codeclimate.com/github/adelevie/parse-ruby-client)
33
+
34
+ **Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*
35
+
36
+ - [Summary](#summary)
37
+ - [Quick Reference](#quick-reference)
38
+ - [Installation](#installation)
39
+ - [Configuration](#configuration)
40
+ - [Parse App Config Parameters](#parse-app-config-parameters)
41
+ - [Objects](#objects)
42
+ - [Creating Objects](#creating-objects)
43
+ - [Retrieving Objects](#retrieving-objects)
44
+ - [Updating Objects](#updating-objects)
45
+ - [Counters](#counters)
46
+ - [Arrays](#arrays)
47
+ - [Relations](#relations)
48
+ - [TODO: This method is not yet implemented.](#todo:-this-method-is-not-yet-implemented)
49
+ - [Deleting Objects](#deleting-objects)
50
+ - [TODO: This method is not yet implemented.](#todo:-this-method-is-not-yet-implemented)
51
+ - [Batch Operations](#batch-operations)
52
+ - [making a few GameScore objects](#making-a-few-gamescore-objects)
53
+ - [Data Types](#data-types)
54
+ - [Dates](#dates)
55
+ - [Bytes](#bytes)
56
+ - [Pointers](#pointers)
57
+ - [Relation](#relation)
58
+ - [TODO: There is no Ruby object representation of this type, yet.](#todo:-there-is-no-ruby-object-representation-of-this-type-yet)
59
+ - [Future data types and namespacing](#future-data-types-and-namespacing)
60
+ - [Queries](#queries)
61
+ - [Basic Queries](#basic-queries)
62
+ - [Query Contraints](#query-contraints)
63
+ - [Queries on Array Values](#queries-on-array-values)
64
+ - [Relational Queries](#relational-queries)
65
+ - [Counting Objects](#counting-objects)
66
+ - [Compound Queries](#compound-queries)
67
+ - [Users](#users)
68
+ - [Signing Up](#signing-up)
69
+ - [Logging In](#logging-in)
70
+ - [Verifying Emails](#verifying-emails)
71
+ - [Requesting A Password Reset](#requesting-a-password-reset)
72
+ - [Retrieving Users](#retrieving-users)
73
+ - [Updating Users](#updating-users)
74
+ - [Querying](#querying)
75
+ - [Deleting Users](#deleting-users)
76
+ - [Linking Users](#linking-users)
77
+ - [Signing Up and Logging In](#signing-up-and-logging-in)
78
+ - [should look something like this:](#should-look-something-like-this:)
79
+ - [Linking](#linking)
80
+ - [should look something like this:](#should-look-something-like-this:)
81
+ - [or](#or)
82
+ - [Unlinking](#unlinking)
83
+ - [should look something like this:](#should-look-something-like-this:)
84
+ - [Security](#security)
85
+ - [Roles](#roles)
86
+ - [Files](#files)
87
+ - [Uploading Files](#uploading-files)
88
+ - [Associating with Objects](#associating-with-objects)
89
+ - [Deleting Files](#deleting-files)
90
+ - [Push Notifications](#push-notifications)
91
+ - [Using Channels](#using-channels)
92
+ - [Using Advanced Targeting](#using-advanced targeting)
93
+ - [Installations](#installations)
94
+ - [GeoPoints](#geopoints)
95
+ - [GeoPoint](#geopoint)
96
+ - [GeoQueries](#geoqueries)
97
+ - [should look something like this:](#should-look-something-like-this:)
98
+ - [Caveats](#caveats)
99
+
100
+ ## Objects
101
+
102
+ The design philosophy behind parse-ruby-client is to stay out of the way as much as possible. Parse Objects, at the most basic level, act like Ruby `Hash` objects with Parse-specific methods tacked on.
103
+
104
+ ### Creating Objects
105
+
106
+ ```ruby
107
+ game_score = AV::Object.new("GameScore")
108
+ game_score["score"] = 1337
109
+ game_score["playerName"] = "Sean Plott"
110
+ game_score["cheatMode"] = false
111
+ result = game_score.save
112
+ puts result
113
+ ```
114
+
115
+ This will return:
116
+
117
+ ```ruby
118
+ {"score"=>1337,
119
+ "playerName"=>"Sean Plott",
120
+ "cheatMode"=>false,
121
+ "createdAt"=>"2013-01-19T21:01:33.562Z",
122
+ "objectId"=>"GeqPWJdNqp"}
123
+ ```
124
+
125
+ ### Retrieving Objects
126
+
127
+ The easiest way to retrieve Objects is with `AV::Query`:
128
+
129
+ ```ruby
130
+ game_score_query = AV::Query.new("GameScore")
131
+ game_score_query.eq("objectId", "GeqPWJdNqp")
132
+ game_score = game_score_query.get
133
+ puts game_score
134
+ ```
135
+
136
+ This will return:
137
+
138
+ ```ruby
139
+ [{"score"=>1337,
140
+ "playerName"=>"Sean Plott",
141
+ "createdAt"=>"2013-01-19T21:01:33.562Z",
142
+ "updatedAt"=>"2013-01-19T21:01:33.562Z",
143
+ "objectId"=>"GeqPWJdNqp"}]
144
+ ```
145
+
146
+ Notice that this is an `Array` of results. For more information on queries, see TODO.
147
+
148
+ When retrieving objects that have pointers to children, you can fetch child objects by setting the `include` attribute. For instance, to fetch the object pointed to by the "game" key:
149
+
150
+ ```ruby
151
+ game_score_query = AV::Query.new("GameScore")
152
+ game_score_query.eq("objectId", "GeqPWJdNqp")
153
+ game_score_query.include = "game"
154
+ game_score = game_score_query.get
155
+ ```
156
+
157
+ You can include multiple children pointers by separating key names by commas:
158
+
159
+ ```ruby
160
+ game_score_query.include = "game,genre"
161
+ ```
162
+
163
+ ### Updating Objects
164
+
165
+ To change the data on an object that already exists, just call `AV::Object#save` on it. Any keys you don't specify will remain unchanged, so you can update just a subset of the object's data. For example, if we wanted to change the score field of our object:
166
+
167
+ ```ruby
168
+ game_score = AV::Query.new("GameScore").eq("objectId", "GeqPWJdNqp").get.first
169
+ game_score["score"] = 73453
170
+ result = game_score.save
171
+ puts result
172
+ ```
173
+
174
+ This will return:
175
+
176
+ ```ruby
177
+ {"score"=>73453,
178
+ "playerName"=>"Sean Plott",
179
+ "createdAt"=>"2013-01-19T21:01:33.562Z",
180
+ "updatedAt"=>"2013-01-19T21:16:34.395Z",
181
+ "objectId"=>"GeqPWJdNqp"}
182
+ ```
183
+
184
+ #### Counters
185
+
186
+ To help with storing counter-type data, Parse provides the ability to atomically increment (or decrement) any number field. So, we can increment the score field like so:
187
+
188
+ ```ruby
189
+ game_score = AV::Query.new("GameScore").eq("objectId", "GeqPWJdNqp").get.first
190
+ game_score["score"] = AV::Increment.new(1)
191
+ game_score.save
192
+ ```
193
+
194
+ You can also use a negative amount to decrement.
195
+
196
+ #### Arrays
197
+
198
+ To help with storing array data, there are three operations that can be used to atomically change an array field:
199
+
200
+ 1. `AV::Object#array_add(field, value)` appends the given array of objects to the end of an array field.
201
+ 2. `AV::Object#array_add_unique(field, value)` adds only the given objects which aren't already contained in an array field to that field. The position of the insert is not guaranteed.
202
+ 3. `AV::Object#array_remove(field, value)` removes all instances of each given object from an array field.
203
+
204
+ Each method takes an array of objects to add or remove in the "objects" key. For example, we can add items to the set-like "skills" field like so:
205
+
206
+ ```ruby
207
+ game_score = AV::Query.new("GameScore").eq("objectId", "5iEEIxM4MW").get.first
208
+ game_score.array_add_unique("skills", ["flying", "kungfu"])
209
+ game_score.save
210
+ puts game_score["skills"]
211
+ ```
212
+
213
+ This will return:
214
+
215
+ ```ruby
216
+ [["flying", "kungfu"]]
217
+ ```
218
+
219
+ #### Relations
220
+
221
+ In order to update Relation types, Parse provides special operators to atomically add and remove objects to a relation. So, we can add an object to a relation like so:
222
+
223
+ ```ruby
224
+ game_score = AV::Query.new("GameScore").eq("objectId", "5iEEIxM4MW").get.first
225
+ player = AV::Query.new("Player").eq("objectId", "GLtvtEaGKa").get.first
226
+ game_score.array_add_relation("opponents", player.pointer)
227
+ game_score.save
228
+ game_score["opponents"] #=> #<AV::ArrayOp:0x007fbe98931508 @operation="AddRelation", @objects=[Player:GLtvtEaGKa]>
229
+ game_score["opponents"].objects.first #=> Player:GLtvtEaGKa
230
+ ```
231
+
232
+ To remove an object from a relation, you can do:
233
+
234
+ ```ruby
235
+ # TODO: This method is not yet implemented.
236
+ ```
237
+
238
+ ### Deleting Objects
239
+
240
+ To delete an object from the Parse Cloud, call `AV::Object#parse_delete`. For example:
241
+
242
+ ```ruby
243
+ game_score = AV::Query.new("GameScore").eq("objectId", "5iEEIxM4MW").get.first
244
+ game_score.parse_delete
245
+ AV::Query.new("GameScore").eq("objectId", "5iEEIxM4MW").get.length #=> 0
246
+ ```
247
+
248
+ You can delete a single field from an object by using the `AV::Object#delete_field` operation:
249
+
250
+ ```ruby
251
+ # TODO: This method is not yet implemented.
252
+ ```
253
+
254
+ ### Batch Operations
255
+
256
+ To reduce the amount of time spent on network round trips, you can create, update, or delete several objects in one call, using the batch endpoint.
257
+
258
+ parse-ruby-client provides a "manual" way to construct Batch Operations, as well as some convenience methods. The commands are run in the order they are given. For example, to create a couple of GameScore objects using the "manual" style, use `AV::Batch#add_request`. `#add_request` takes a `Hash` with `"method"`, `"path"`, and `"body"` keys that specify the HTTP command that would normally be used for that command.
259
+
260
+ ```ruby
261
+ batch = AV::Batch.new
262
+ batch.add_request({
263
+ "method" => "POST",
264
+ "path" => "/1/classes/GameScore"
265
+ "body" => {
266
+ "score" => 1337,
267
+ "playerName" => "Sean Plott"
268
+ }
269
+ })
270
+ batch.add_request({
271
+ "method" => "POST",
272
+ "path" => "/1/classes/GameScore"
273
+ "body" => {
274
+ "score" => 1338,
275
+ "playerName" => "ZeroCool"
276
+ }
277
+ })
278
+ batch.run!
279
+ ```
280
+
281
+ Because manually constructing `"path"` values is repetitive, you can use `AV::Batch#create_object`, `AV::Batch#update_object`, and `AV::Batch#delete_object`. Each of these methods takes an instance of `AV::Object` as the only argument. Then you just call `AV::Batch#run!`. For example:
282
+
283
+ ```ruby
284
+ batch = AV::Batch.new
285
+ # making a few GameScore objects and adding them to the batch operation.
286
+ [1, 2, 3, 4, 5].each do |i|
287
+ gs = AV::Object.new("GameScore")
288
+ gs["score"] = "#{i}"
289
+ batch.create_object(gs)
290
+ end
291
+ batch.run!
292
+ ```
293
+
294
+ The response from batch will be an `Array` with the same number of elements as the input list. Each item in the `Array` with be a `Hash` with either the `"success"` or `"error"` field set. The value of success will be the normal response to the equivalent REST command:
295
+
296
+ ```ruby
297
+ {
298
+ "success" => {
299
+ "createdAt" => "2012-06-15T16:59:11.276Z",
300
+ "objectId" => "YAfSAWwXbL"
301
+ }
302
+ }
303
+ ```
304
+
305
+ The value of `"error"` will be a `Hash` with a numeric code and error string:
306
+
307
+ ```ruby
308
+ {
309
+ "error" => {
310
+ "code" => 101,
311
+ "error" => "object not found for delete"
312
+ }
313
+ }
314
+ ```
315
+
316
+ ### Data Types
317
+
318
+ So far we have only used values that can be encoded with standard JSON. The Parse mobile client libraries also support dates, binary data, and relational data. In parse-ruby-client, these values are encoded as JSON hashes with the __type field set to indicate their type, so you can read or write these fields if you use the correct encoding. See https://github.com/adelevie/parse-ruby-client/blob/master/lib/parse/datatypes.rb for implementation details of several common data types.
319
+
320
+ #### Dates
321
+
322
+ Use `AV::Date::new` to create a date object:
323
+
324
+ ```ruby
325
+ date_time = DateTime.now
326
+ parse_date = AV::Date.new(date_time)
327
+ ```
328
+
329
+ Dates are useful in combination with the built-in createdAt and updatedAt fields. For example, to retrieve objects created since a particular time, just encode a Date in a comparison query:
330
+
331
+ ```ruby
332
+ game_score = AV::Query.new("GameScore").tap do |q|
333
+ q.greater_than("createdAt", AV::Date.new(DateTime.now)) # query options explained in more detail later in this document
334
+ end.get.first
335
+ ```
336
+
337
+ `AV::Date::new` can take a `DateTime`, iso `Hash`, or a `String` that can be parsed by `DateTime#parse` as the sole argument.
338
+
339
+ The `AV::Date` API is not set in stone and will likely change following the suggestions discussed here: https://github.com/adelevie/parse-ruby-client/issues/35. The current methods probably will not go away, but some newer, easier methods will be added.
340
+
341
+ #### Bytes
342
+
343
+ `AV::Bytes` contains an attribute, `base64`, which contains a base64 encoding of binary data. The specific base64 encoding is the one used by MIME, and does not contain whitespace.
344
+
345
+ ```ruby
346
+ data = "TG9va3MgbGlrZSB5b3UgZm91bmQgYW4gZWFzdGVyIEVnZy4gTWF5YmUgaXQn\ncyB0aW1lIHlvdSB0b29rIGEgTWluZWNyYWZ0IGJyZWFrPw==\n" # base64 encoded data
347
+ bytes = AV::Bytes.new(data)
348
+ ```
349
+
350
+ #### Pointers
351
+
352
+ The `Pointer` type is used when mobile code sets a `PFObject` (iOS SDK) or `ParseObject` (Android SDK) as the value of another object. It contains the `className` and `objectId` of the referred-to value.
353
+
354
+ ```ruby
355
+ pointer = AV::Pointer.new({"className" => "gameScore", "objectId" => "GeqPWJdNqp"})
356
+ ```
357
+
358
+ Pointers to `user` objects have a `className` of `_User`. Prefixing with an underscore is forbidden for developer-defined classes and signifies the class is a special built-in.
359
+
360
+ If you already have a `AV::Object`, you can get its `Pointer` very easily:
361
+
362
+ ```ruby
363
+ game_score.pointer
364
+ ```
365
+
366
+ #### Relation
367
+
368
+ The `Relation` type is used for many-to-many relations when the mobile uses `PFRelation` (iOS SDK) or `ParseRelation` (Android SDK) as a value. It has a `className` that is the class name of the target objects.
369
+
370
+ *Note:* The REST API embeds the configuration parameters in a key called 'params' which is omitted for you by the client.
371
+
372
+ ```ruby
373
+ # TODO: There is no Ruby object representation of this type, yet.
374
+ ```
375
+
376
+ #### Future data types and namespacing
377
+
378
+ Though this is something parse-ruby-client will take care for you, it's worth noting:
379
+
380
+ When more data types are added, they will also be represented as hashes with a `__type` field set, so you may not use this field yourself on JSON objects.
381
+
382
+
383
+ ## Queries
384
+
385
+ Queries are created like so:
386
+
387
+ ```ruby
388
+ query = AV::Query.new("GameScore")
389
+ ```
390
+
391
+
392
+
393
+ ### Basic Queries
394
+
395
+ You can retrieve multiple objects at once by calling `#get`:
396
+
397
+ ```ruby
398
+ query.get
399
+ ```
400
+
401
+ The return value is an `Array` of `AV::Object` instances:
402
+
403
+ ```ruby
404
+ [{"score"=>100,
405
+ "player"=>player:qPHDUbBbjj,
406
+ "createdAt"=>"2012-10-10T00:16:10.846Z",
407
+ "updatedAt"=>"2012-10-10T00:16:10.846Z",
408
+ "objectId"=>"6ff54A5OCY"},
409
+ {"score"=>1337,
410
+ "playerName"=>"Sean Plott",
411
+ "createdAt"=>"2013-01-05T22:51:56.033Z",
412
+ "updatedAt"=>"2013-01-05T22:51:56.033Z",
413
+ "objectId"=>"MpPBAHsqNg"},
414
+ {"score"=>1337,
415
+ "playerName"=>"Sean Plott",
416
+ "createdAt"=>"2013-01-05T22:53:22.609Z",
417
+ "updatedAt"=>"2013-01-05T22:53:22.609Z",
418
+ "objectId"=>"T1dj8cWwYJ"}]]
419
+ ```
420
+
421
+ ### Query Contraints
422
+
423
+ There are several ways to put constraints on the objects found, using various methods of `AV::Query`. The most basic is `AV::Query#eq`:
424
+
425
+ ```ruby
426
+ query = AV::Query.new("GameScore").eq("playerName", "Sean Plott")
427
+ ```
428
+
429
+ Other constraint methods include:
430
+
431
+ <table>
432
+ <tr>
433
+ <td>`AV::Query#less_than(field, value)`</td>
434
+ <td>Less Than</td>
435
+ </tr>
436
+ <tr>
437
+ <td>`AV::Query#less_eq(field, value)`</td>
438
+ <td>Less Than or Equal To</td>
439
+ </tr>
440
+ <tr>
441
+ <td>`AV::Query#greater_than(field, value)`</td>
442
+ <td>Greater Than</td>
443
+ </tr>
444
+ <tr>
445
+ <td>`AV::Query#greater_eq(field, value)`</td>
446
+ <td>Greater Than Or Equal To</td>
447
+ </tr>
448
+ <tr>
449
+ <td>`AV::Query#not_eq(field, value)`</td>
450
+ <td>Not Equal To</td>
451
+ </tr>
452
+ <tr>
453
+ <td>`AV::Query#value_in(field, values)`</td>
454
+ <td>Contained In</td>
455
+ </tr>
456
+ <tr>
457
+ <td>`AV::Query#value_not_in(field, values)`</td>
458
+ <td>Not Contained in</td>
459
+ </tr>
460
+ <tr>
461
+ <td>`AV::Query#exists(field, value=true)`</td>
462
+ <td>A value is set for the key</td>
463
+ </tr>
464
+ <tr>
465
+ <td>`AV::Query#contains_all(field, values)`</td>
466
+ <td>Contains all values in the array</td>
467
+ </tr>
468
+ <tr>
469
+ <td>`AV::Query#select`</td>
470
+ <td>TODO: `$select` not yet implemented. This matches a value for a key in the result of a different query</td>
471
+ </tr>
472
+ </table>
473
+
474
+ For example, to retrieve scores between 1000 and 3000, including the endpoints, we could issue:
475
+
476
+ ```ruby
477
+ scores = AV::Query.new("GameScore").tap do |q|
478
+ q.greater_eq("score", 1000)
479
+ q.less_eq("score", 3000)
480
+ end.get
481
+ ```
482
+
483
+ To retrieve scores equal to an odd number below 10, we could issue:
484
+
485
+ ```ruby
486
+ scores = AV::Query.new("GameScore").tap do |q|
487
+ q.value_in("score", [1,3,5,7,9])
488
+ end.get
489
+ ```
490
+
491
+ To retrieve scores not by a given list of players we could issue:
492
+
493
+ ```ruby
494
+ scores = AV::Query.new("GameScore").tap do |q|
495
+ q.value_not_in("playerName", ["Jonathan Walsh","Dario Wunsch","Shawn Simon"])
496
+ end.get
497
+ ```
498
+
499
+ To retrieve documents with the score set, we could issue:
500
+
501
+ ```ruby
502
+ scores = AV::Query.new("GameScore").tap do |q|
503
+ q.exists("score") # defaults to `true`
504
+ end.get
505
+ ```
506
+
507
+ To retrieve documents without the score set, we could issue:
508
+
509
+ ```ruby
510
+ scores = AV::Query.new("GameScore").tap do |q|
511
+ q.exists("score", false)
512
+ end.get
513
+ ```
514
+
515
+ If you have a class containing sports teams and you store a user's hometown in the user class, you can issue one query to find the list of users whose hometown teams have winning records. The query would look like:
516
+
517
+ ```ruby
518
+ users = AV::Query.new("_User").tap do |users_query|
519
+ users_query.eq("hometown", {
520
+ "$select" => {
521
+ "query" => {
522
+ "className" => "Team",
523
+ "where" => {
524
+ "winPct" => {"$gt" => 0.5}
525
+ }
526
+ },
527
+ "key" => "city"
528
+ }
529
+ })
530
+ end.get
531
+ ```
532
+
533
+ Currently, there is no convenience method provided for `$select` queries. However, they are still possible. This is a good example of the flexibility of parse-ruby-client. You usually do not need to wait for a feature to be added in order to user it. If you have a good idea on what a convencience method for this should look like, please file an issue, or even better, submit a pull request.
534
+
535
+ You can use the `AV::Query#order_by` method to specify a field to sort by. By default, everything is ordered ascending. Thus, to retrieve scores in ascending order:
536
+
537
+ ```ruby
538
+ scores = AV::Query.new("GameScore").tap do |q|
539
+ q.order_by = "score"
540
+ end.get
541
+ ```
542
+
543
+ And to retrieve scores in descending order:
544
+
545
+ ```ruby
546
+ scores = AV::Query.new("GameScore").tap do |q|
547
+ q.order_by = "score"
548
+ q.order = :descending
549
+ end.get
550
+ ```
551
+
552
+ You can sort by multiple fields by passing order a comma-separated list. Currently, there is no convenience method to accomplish this. However, you can still manually construct an `order` string. To retrieve documents that are ordered by scores in ascending order and the names in descending order:
553
+
554
+ ```ruby
555
+ scores = AV::Query.new("GameScore").tap do |q|
556
+ q.order_by = "score,-name"
557
+ end.get
558
+ ```
559
+
560
+ You can use the `limit` and `skip` parameters for pagination. `limit` defaults to 100, but anything from 1 to 1000 is a valid limit. Thus, to retrieve 200 objects after skipping the first 400:
561
+
562
+ ```ruby
563
+ scores = AV::Query.new("GameScore").tap do |q|
564
+ q.limit = 200
565
+ q.skip = 400
566
+ end.get
567
+ ```
568
+
569
+ All of these parameters can be used in combination with each other.
570
+
571
+ ### Queries on Array Values
572
+
573
+ For keys with an array type, you can find objects where the key's array value contains 2 by:
574
+
575
+ ```ruby
576
+ randos = AV::Query.new("RandomObject").eq("arrayKey", 2).get
577
+ ```
578
+
579
+ You can also query that the array contains multiple objects by using contains all, for example you can return objects that have the array values 2 AND 3 by:
580
+
581
+ ```ruby
582
+ randos = AV::Query.new("RandomObject").eq("arrayKey", [2, 3]).get
583
+ ```
584
+
585
+ ### Relational Queries
586
+
587
+ There are several ways to issue queries for relational data. For example, if each `Comment` has a `Post` object in its `post` field, you can fetch comments for a particular `Post`:
588
+
589
+ ```ruby
590
+ comments = AV::Query.new("Comment").tap do |q|
591
+ q.eq("post", AV::Pointer.new({
592
+ "className" => "Post",
593
+ "objectId" => "8TOXdXf3tz"
594
+ }))
595
+ end.get
596
+ ```
597
+
598
+ If you want to retrieve objects where a field contains an object that matches another query, you can use the `AV::Query#in_query(field, query=nil)` method. Note that the default limit of 100 and maximum limit of 1000 apply to the inner query as well, so with large data sets you may need to construct queries carefully to get the desired behavior. For example, imagine you have `Post` class and a `Comment` class, where each `Comment` has a relation to its parent `Post`. You can find comments on posts with images by doing:
599
+
600
+ ```ruby
601
+ comments = AV::Query.new("Comment").tap do |comments_query|
602
+ comments_query.in_query("post", AV::Query.new("Post").tap do |posts_query|
603
+ posts_query.exists("image")
604
+ end)
605
+ end.get
606
+ ```
607
+
608
+ Note: You must pass an instance of `AV::Query` as the second argument for `AV::Query#query_in`. You cannot manually construct queries for this.
609
+
610
+ TODO: Implement this:
611
+ ```
612
+ If you want to retrieve objects where a field contains an object that does not match another query, you can use the $notInQuery operator. Imagine you have Post class and a Comment class, where each Comment has a relation to its parent Post. You can find comments on posts without images by doing:
613
+ ```
614
+
615
+ If you want to retrieve objects that are members of `Relation` field of a parent object, you can use the `AV::Query#related_to(field, value)` method. Imagine you have a `Post `class and `User` class, where each `Post` can be liked by many users. If the `Users` that liked a Post was stored in a `Relation` on the post under the key likes, you, can the find the users that liked a particular post by:
616
+
617
+ ```ruby
618
+ users = AV::Query.new("_User").tap do |q|
619
+ q.related_to("likes", AV::Pointer.new({
620
+ "className" => "Post",
621
+ "objectId" => "8TOXdXf3tz"
622
+ }))
623
+ end.get
624
+ ```
625
+
626
+ In some situations, you want to return multiple types of related objects in one query. You can do this by passing the field to include in the `include` parameter. For example, let's say you are retrieving the last ten comments, and you want to retrieve their related posts at the same time:
627
+
628
+ ```ruby
629
+ comments = AV::Query.new("Comment").tap do |q|
630
+ q.order_by = "createdAt"
631
+ q.order = :descending
632
+ q.limit = 10
633
+ q.include = "post"
634
+ end.get
635
+ ```
636
+
637
+ Instead of being represented as a `Pointer`, the `post` field is now expanded into the whole object. `__type` is set to `Object` and `className` is provided as well. For example, a `Pointer` to a `Post` could be represented as:
638
+
639
+ ```ruby
640
+ {
641
+ "__type" => "Pointer",
642
+ "className" => "Post",
643
+ "objectId" => "8TOXdXf3tz"
644
+ }
645
+ ```
646
+
647
+ When the query is issued with an `include` parameter for the key holding this pointer, the pointer will be expanded to:
648
+
649
+ ```ruby
650
+ {
651
+ "__type" => "Object",
652
+ "className" => "Post",
653
+ "objectId" => "8TOXdXf3tz",
654
+ "createdAt" => "2011-12-06T20:59:34.428Z",
655
+ "updatedAt" => "2011-12-06T20:59:34.428Z",
656
+ "otherFields" => "willAlsoBeIncluded"
657
+ }
658
+ ```
659
+
660
+ You can also do multi level includes using dot notation. If you wanted to include the post for a comment and the post's author as well you can do:
661
+
662
+ ```ruby
663
+ comments = AV::Query.new("Comment").tap do |q|
664
+ q.order_by = "createdAt"
665
+ q.order = :descending
666
+ q.limit = 10
667
+ q.include = "post.author"
668
+ end.get
669
+ ```
670
+
671
+ You can issue a query with multiple fields included by passing a comma-separated list of keys as the include parameter:
672
+
673
+ ```ruby
674
+ comments = AV::Query.new("Comment").tap do |q|
675
+ q.include("post,author")
676
+ end.get
677
+ ```
678
+
679
+ ### Counting Objects
680
+
681
+ If you are limiting your query, or if there are a very large number of results, and you want to know how many total results there are without returning them all, you can use the `count` parameter. For example, if you only care about the number of games played by a particular player:
682
+
683
+ ```ruby
684
+ count = AV::Query.new("GameScore").tap do |q|
685
+ q.eq("playerName", "Jonathan Walsh")
686
+ q.limit = 0
687
+ q.count
688
+ end.get
689
+ ```
690
+
691
+ With a nonzero limit, that request would return results as well as the count.
692
+
693
+ ### Compound Queries
694
+
695
+ If you want to find objects that match one of several queries, you can use `AV::Quer#or` method, with an `Array` as its value. For instance, if you want to find players with either have a lot of wins or a few wins, you can do:
696
+
697
+ ```ruby
698
+
699
+ players = AV::Query.new("Player").tap do |q|
700
+ q.greater_than("wins", 150)
701
+ q.or(AV::Query.new("Player").tap do |or_query|
702
+ or_query.less_than("wins, 5")
703
+ end)
704
+ end.get
705
+ ```
706
+
707
+ ## Users
708
+
709
+ Many apps have a unified login that works across the mobile app and other systems. Accessing user accounts through parse-ruby-client lets you build this functionality on top of AV.
710
+
711
+ In general, users have the same features as other objects, such as the flexible schema. The differences are that user objects must have a username and password, the password is automatically encrypted and stored securely, and Parse enforces the uniqueness of the `username` and `email` fields.
712
+
713
+ ### Signing Up
714
+
715
+ Signing up a new user differs from creating a generic object in that the `username` and `password` fields are required. The password field is handled differently than the others; it is encrypted when stored in the Parse Cloud and never returned to any client request.
716
+
717
+ You can ask Parse to verify user email addresses in your application settings page. With this setting enabled, all new user registrations with an `email` field will generate an email confirmation at that address. You can check whether the user has verified their `email` with the `emailVerified` field.
718
+
719
+ To sign up a new user, create a new `AV::User` object and then call `#save` on it:
720
+
721
+ ```ruby
722
+ user = AV::User.new({
723
+ :username => "cooldude6",
724
+ :password => "p_n7!-e8",
725
+ :phone => "415-392-0202"
726
+ })
727
+ user.save
728
+ ```
729
+
730
+ The response body is a `AV::User` object containing the `objectId`, the `createdAt` timestamp of the newly-created object, and the `sessionToken` which can be used to authenticate subsequent requests as this user:
731
+
732
+ ```ruby
733
+ {"username"=>"cooldude6",
734
+ "phone"=>"415-392-0202",
735
+ "createdAt"=>"2013-01-31T15:22:40.339Z",
736
+ "objectId"=>"2bMfWZQ9Ob",
737
+ "sessionToken"=>"zrGuvs3psdndaqswhf0smupsodflkqbFdwRs"}
738
+ ```
739
+
740
+ ### Logging In
741
+
742
+ After you allow users to sign up, you need to let them log in to their account with a username and password in the future. To do this, call `AV::User#authenticate(username, password)`:
743
+
744
+ ```ruby
745
+ user = AV::User.authenticate("cooldude6", "p_n7!-e8")
746
+ ```
747
+
748
+ The response body is a `AV::User` object containing all the user-provided fields except `password`. It also contains the `createdAt`, `updatedAt`, `objectId`, and `sessionToken` fields:
749
+
750
+ ```ruby
751
+ {"username"=>"cooldude6",
752
+ "phone"=>"415-392-0202",
753
+ "createdAt"=>"2013-01-31T15:22:40.339Z",
754
+ "updatedAt"=>"2013-01-31T15:22:40.339Z",
755
+ "objectId"=>"2bMfWZQ9Ob",
756
+ "sessionToken"=>"uvs3aspasdnlksdasqu178qaq0smupso"}
757
+ ```
758
+
759
+ ### Verifying Emails
760
+
761
+ Enabling email verification in an application's settings allows the application to reserve part of its experience for users with confirmed email addresses. Email verification adds the `emailVerified` field to the `User` object. When a `User`'s `email` is set or modified, `emailVerified` is set to false. Parse then emails the user a link which will set `emailVerified` to `true`.
762
+
763
+ There are three `emailVerified` states to consider:
764
+
765
+ 1. `true` - the user confirmed his or her email address by clicking on the link Parse emailed them. `Users` can never have a `true` value when the user account is first created.
766
+
767
+ 2. `false` - at the time the `User` object was last refreshed, the user had not confirmed his or her email address. If `emailVerified` is `false`, consider refreshing the `User` object.
768
+
769
+ 3. *missing* - the `User` was created when email verification was off or the `User` does not have an `email`.
770
+
771
+ ### Requesting A Password Reset
772
+
773
+ You can initiate password resets for users who have emails associated with their account. To do this, use `AV::User::reset_password`:
774
+
775
+ ```ruby
776
+ resp = AV::User.reset_password("coolguy@iloveapps.com")
777
+ puts resp #=> {}
778
+ ```
779
+
780
+ If successful, the response body is an empty `Hash` object.
781
+
782
+ ### Retrieving Users
783
+
784
+ You can also retrieve the contents of a user object by using `AV::Query`. For example, to retrieve the user created above:
785
+
786
+ ```ruby
787
+ user = AV::Query.new("_User").eq("objectId", "2bMfWZQ9Ob").get.first
788
+ ```
789
+
790
+ The response body is a `AV::User` object containing all the user-provided fields except `password`. It also contains the `createdAt`, `updatedAt`, and `objectId` fields:
791
+
792
+ ```ruby
793
+ {"username"=>"cooldude6",
794
+ "phone"=>"415-392-0202",
795
+ "createdAt"=>"2013-01-31T15:22:40.339Z",
796
+ "updatedAt"=>"2013-01-31T15:22:40.339Z",
797
+ "objectId"=>"2bMfWZQ9Ob"}
798
+ ```
799
+
800
+ ### Updating Users
801
+
802
+ TODO: Implement this!
803
+
804
+ In normal usage, nobody except the user is allowed to modify their own data. To authenticate themselves, the user must add a `X-Parse-Session-Token` header to the request with the session token provided by the signup or login method.
805
+
806
+ To change the data on a user that already exists, send a PUT request to the user URL. Any keys you don't specify will remain unchanged, so you can update just a subset of the user's data. `username` and `password` may be changed, but the new username must not already be in use.
807
+
808
+ For example, if we wanted to change the phone number for cooldude6:
809
+
810
+ ```ruby
811
+ user = AV::Query.new("_User").eq("objectId", "2bMfWZQ9Ob").get.first
812
+ user["phone"] = "415-369-6201"
813
+ user.save
814
+ ```
815
+
816
+ Currently returns the following error:
817
+
818
+ ```
819
+ AV::AVProtocolError: 206: AV::UserCannotBeAlteredWithoutSessionError
820
+ ```
821
+
822
+ ### Querying
823
+
824
+ You can retrieve multiple users at once by using `AV::Query`:
825
+
826
+ ```ruby
827
+ users = AV::Query.new("_User").get
828
+ ```
829
+
830
+ The return value is an `Array` of `AV::User` objects:
831
+
832
+ ```ruby
833
+ [{"username"=>"fake_person",
834
+ "createdAt"=>"2012-04-20T20:07:32.295Z",
835
+ "updatedAt"=>"2012-04-20T20:07:32.295Z",
836
+ "objectId"=>"AAVwfClOx9"},
837
+ {"username"=>"fake_person222",
838
+ "createdAt"=>"2012-04-20T20:07:32.946Z",
839
+ "updatedAt"=>"2012-04-20T20:07:32.946Z",
840
+ "objectId"=>"0W1Gj1CXqU"}]
841
+ ```
842
+
843
+ All of the options for queries that work for regular objects also work for user objects, so check the section on Querying Objects for more details.
844
+
845
+ ### Deleting Users
846
+
847
+ TODO: Implement this!
848
+
849
+ Proposed api:
850
+
851
+ To delete a user from the Parse Cloud, call `#parse_delete` on it:
852
+
853
+ ```ruby
854
+ user.parse_delete
855
+ ```
856
+
857
+ ### Linking Users
858
+
859
+ TODO: Implement this! See https://leancloud.cn/docs/rest_api.html#用户账户连接
860
+
861
+ Parse allows you to link your users with services like Twitter and Facebook, enabling your users to sign up or log into your application using their existing identities. This is accomplished through the sign-up and update REST endpoints by providing authentication data for the service you wish to link to a user in the authData field. Once your user is associated with a service, the authData for the service will be stored with the user and is retrievable by logging in.
862
+
863
+ authData is a JSON object with keys for each linked service containing the data below. In each case, you are responsible for completing the authentication flow (e.g. OAuth 1.0a) to obtain the information the the service requires for linking.
864
+
865
+ Facebook authData contents:
866
+
867
+ ```ruby
868
+ {
869
+ "facebook" => {
870
+ "id" => "user's Facebook id number as a string",
871
+ "access_token" => "an authorized Facebook access token for the user",
872
+ "expiration_date" => "token expiration date of the format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
873
+ }
874
+ }
875
+ ```
876
+
877
+ Twitter authData contents:
878
+
879
+ ```ruby
880
+ {
881
+ "twitter" => {
882
+ "id" => "user's Twitter id number as a string",
883
+ "screen_name" => "user's Twitter screen name",
884
+ "consumer_key" => "your application's consumer key",
885
+ "consumer_secret" => "your application's consumer secret",
886
+ "auth_token" => "an authorized Twitter token for the user with your application",
887
+ "auth_token_secret" => "the secret associated with the auth_token"
888
+ }
889
+ }
890
+ ```
891
+
892
+ Anonymous user authData contents:
893
+
894
+ ```ruby
895
+ {
896
+ "anonymous" => {
897
+ "id" => "random UUID with lowercase hexadecimal digits"
898
+ }
899
+ }
900
+ ```
901
+
902
+ #### Signing Up and Logging In
903
+
904
+ Todo: Implement this!
905
+
906
+ Signing a user up with a linked service and logging them in with that service uses the same POST request, in which the authData for the user is specified. For example, to sign up or log in with a user's Twitter account:
907
+
908
+ ```ruby
909
+ # should look something like this:
910
+ twitter_user = AV::User::Twitter.new({
911
+ "id" => "12345678",
912
+ "screen_name" => "ParseIt",
913
+ "consumer_key" => "SaMpLeId3X7eLjjLgWEw",
914
+ "consumer_secret" => "SaMpLew55QbMR0vTdtOACfPXa5UdO2THX1JrxZ9s3c",
915
+ "auth_token" => "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU",
916
+ "auth_token_secret" => "SaMpLeEb13SpRzQ4DAIzutEkCE2LBIm2ZQDsP3WUU"
917
+ })
918
+ twitter_user.save
919
+ ```
920
+
921
+ Parse then verifies that the provided authData is valid and checks to see if a user is already associated with this data. If so, it returns a status code of 200 OK and the details (including a sessionToken for the user).
922
+
923
+ With a response body like:
924
+
925
+ ```ruby
926
+ {
927
+ "username" => "Parse",
928
+ "createdAt" => "2012-02-28T23:49:36.353Z",
929
+ "updatedAt" => "2012-02-28T23:49:36.353Z",
930
+ "objectId" => "uMz0YZeAqc",
931
+ "sessionToken" => "samplei3l83eerhnln0ecxgy5",
932
+ "authData" => {
933
+ "twitter" => {
934
+ "id" => "12345678",
935
+ "screen_name" => "ParseIt",
936
+ "consumer_key" => "SaMpLeId3X7eLjjLgWEw",
937
+ "consumer_secret" => "SaMpLew55QbMR0vTdtOACfPXa5UdO2THX1JrxZ9s3c",
938
+ "auth_token" => "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU",
939
+ "auth_token_secret" => "SaMpLeEb13SpRzQ4DAIzutEkCE2LBIm2ZQDsP3WUU"
940
+ }
941
+ }
942
+ }
943
+ ```
944
+
945
+ If the user has never been linked with this account, you will instead receive a status code of 201 Created, indicating that a new user was created.
946
+
947
+ The body of the response will contain the objectId, createdAt, sessionToken, and an automatically-generated unique username. For example:
948
+
949
+ ```ruby
950
+ {
951
+ "username" => "iwz8sna7sug28v4eyu7t89fij",
952
+ "createdAt" => "2012-02-28T23:49:36.353Z",
953
+ "objectId" => "uMz0YZeAqc",
954
+ "sessionToken" => "samplei3l83eerhnln0ecxgy5"
955
+ }
956
+ ```
957
+
958
+ #### Linking
959
+
960
+ TODO: Implement this!
961
+
962
+ Linking an existing user with a service like Facebook or Twitter uses a PUT request to associate authData with the user. For example, linking a user with a Facebook account would use a request like this:
963
+
964
+ ```ruby
965
+ # should look something like this:
966
+
967
+ user = AV::Query.new("_User").eq("objectId", "2bMfWZQ9Ob").get.first
968
+ user.link_to_facebook!({
969
+ "id" => "123456789",
970
+ "access_token" => "SaMpLeAAibS7Q55FSzcERWIEmzn6rosftAr7pmDME10008bWgyZAmv7mziwfacNOhWkgxDaBf8a2a2FCc9Hbk9wAsqLYZBLR995wxBvSGNoTrEaL",
971
+ "expiration_date" => "2012-02-28T23:49:36.353Z"
972
+ })
973
+
974
+ # or
975
+
976
+ user.link_to_twitter!({...})
977
+ ```
978
+
979
+ After linking your user to a service, you can authenticate them using matching authData.
980
+
981
+
982
+ #### Unlinking
983
+
984
+ TODO: Implement this!
985
+
986
+ Unlinking an existing user with a service also uses a PUT request to clear authData from the user by setting the authData for the service to null. For example, unlinking a user with a Facebook account would use a request like this:
987
+
988
+ ```ruby
989
+ # should look something like this:
990
+
991
+ user = AV::Query.new("_User").eq("objectId", "2bMfWZQ9Ob").get.first
992
+ user.unlink_from_facebook!
993
+ ```
994
+
995
+ ### Security
996
+
997
+ TODO: Implement this!
998
+
999
+ When you access Parse via the REST API key, access can be restricted by ACL just like in the iOS and Android SDKs. You can still read and modify acls via the REST API, just be accessing the "ACL" key of an object.
1000
+
1001
+ The ACL is formatted as a JSON object where the keys are either object ids or the special key "*" to indicate public access permissions. The values of the ACL are "permission objects", JSON objects whose keys are the permission name and the value is always true.
1002
+
1003
+ For example, if you want the user with id "3KmCvT7Zsb" to have read and write access to an object, plus the object should be publicly readable, that corresponds to an ACL of:
1004
+
1005
+ ```json
1006
+ {
1007
+ "3KmCvT7Zsb": {
1008
+ "read": true,
1009
+ "write": true
1010
+ },
1011
+ "*": {
1012
+ "read": true
1013
+ }
1014
+ }
1015
+ ```
1016
+
1017
+ If you want to access your data ignoring all ACLs, you can use the master key provided on the Dashboard. Instead of the X-Parse-REST-API-Key header, set the X-Parse-Master-Key header. For backward compatibility, you can also do master-level authentication using HTTP Basic Auth, passing the application id as the username and the master key as the password. For security, the master key should not be distributed to end users, but if you are running code in a trusted environment, feel free to use the master key for authentication.
1018
+
1019
+ ## Roles
1020
+
1021
+ TODO: Implement this!
1022
+
1023
+ See https://leancloud.cn/docs/rest_api.html#角色-1
1024
+
1025
+ ## Files
1026
+
1027
+ ### Uploading Files
1028
+
1029
+ To upload a file to Parse, use `AV::File`. You must include the `"Content-Type"` parameter when instantiating. Keep in mind that files are limited to 10 megabytes. Here's a simple example that'll create a file named `hello.txt` containing a string:
1030
+
1031
+ ```ruby
1032
+ file = AV::File.new({
1033
+ :body => "Hello World!",
1034
+ :local_filename => "hello.txt",
1035
+ :content_type => "text/plain"
1036
+ })
1037
+ file.save
1038
+ ```
1039
+
1040
+ The response body is a `Hash` object containing the name of the file, which is the original file name prefixed with a unique identifier in order to prevent name collisions. This means, you can save files by the same name, and the files will not overwrite one another.
1041
+
1042
+ ```ruby
1043
+ {"url"=>
1044
+ "http://files.parse.com/372fcbb9-7eae-4b9a-abc8-6da97fcac50d/98f06e15-d6e6-42a9-a9cd-7d28ec98052c-hello.txt",
1045
+ "name"=>"98f06e15-d6e6-42a9-a9cd-7d28ec98052c-hello.txt"}
1046
+ ```
1047
+
1048
+ To upload an image, the syntax is a little bit different. Here's an example that will upload the image parsers.jpg from the current directory:
1049
+
1050
+ ```ruby
1051
+ photo = AV::File.new({
1052
+ :body => IO.read("test/parsers.jpg"),
1053
+ :local_filename => "parsers.jpg",
1054
+ :content_type => "image/jpeg"
1055
+ })
1056
+ photo.save
1057
+ ```
1058
+
1059
+ ### Associating with Objects
1060
+
1061
+ After files are uploaded, you can associate them with Parse objects:
1062
+
1063
+ ```ruby
1064
+ photo = AV::File.new({
1065
+ :body => IO.read("test/parsers.jpg"),
1066
+ :local_filename => "parsers.jpg",
1067
+ :content_type => "image/jpeg"
1068
+ })
1069
+ photo.save
1070
+ player_profile = AV::Object.new("PlayerProfile").tap do |p|
1071
+ p["name"] = "All the Parsers"
1072
+ p["picture"] = photo
1073
+ end.save
1074
+ ```
1075
+
1076
+ ### Deleting Files
1077
+
1078
+ TODO: Implement this!
1079
+
1080
+ ## Push Notifications
1081
+
1082
+ Parse allows you send Push Notifications to iOS and Android devices.
1083
+
1084
+ Notifications by default are set for iOS and Android. You can set certain notifications to only be sent to iOS or Android by setting the `type` to `ios` or `android`.
1085
+
1086
+ For config/installation: https://leancloud.cn/docs/push_guide.html#使用_REST_API_推送消息
1087
+
1088
+ ### Using Channels
1089
+
1090
+ To send a notification to the "Giants" channel, as given at: https://leancloud.cn/docs/push_guide.html
1091
+ ```ruby
1092
+ data = { :alert => "This is a notification from Parse" }
1093
+ push = AV::Push.new(data, "Giants")
1094
+ push.type = "ios"
1095
+ push.save
1096
+ ```
1097
+
1098
+ ### Using Advanced Targeting
1099
+
1100
+ To send a notification to installations where `injuryReports` is `true`, as given at: https://leancloud.cn/docs/push_guide.html
1101
+
1102
+
1103
+ ```ruby
1104
+ data = { :alert => "This is a notification from Parse" }
1105
+ push = AV::Push.new(data)
1106
+ push.type = "ios"
1107
+ query = AV::Query.new(AV::Protocol::CLASS_INSTALLATION).eq('injuryReports', true)
1108
+ push.where = query.where
1109
+ push.save
1110
+ ```
1111
+
1112
+ ## Installations
1113
+
1114
+ #### Retrieving Installations
1115
+
1116
+ ```ruby
1117
+ installation = AV::Installation.get "objectId"
1118
+ # Same as
1119
+ installation = AV::Installation.new "objectId"
1120
+ installation.get
1121
+ ```
1122
+
1123
+ #### Updating installations
1124
+
1125
+ ```ruby
1126
+ installation = AV::Installation.new "objectId"
1127
+ installation.channels = ["", "my-channel-name"]
1128
+ installation.badge = 5
1129
+ installation.save
1130
+ ```
1131
+
1132
+ ## GeoPoints
1133
+
1134
+ Parse allows you to associate real-world latitude and longitude coordinates with an object. Adding a GeoPoint data type to a class allows queries to take into account the proximity of an object to a reference point. This allows you to easily do things like find out what user is closest to another user or which places are closest to a user.
1135
+
1136
+ ### GeoPoint
1137
+
1138
+ To associate a point with an object you will need to embed a GeoPoint data type into your object. This is done by using a JSON object with __type set to the string GeoPoint and numeric values being set for the latitude and longitude keys. For example, to create an object containing a point under the "location" key with a latitude of 40.0 degrees and -30.0 degrees longitude:
1139
+
1140
+ ```ruby
1141
+ place = AV::Object.new("PlaceObject").tap do |p|
1142
+ p["location"] = AV::GeoPoint.new({
1143
+ "latitude" => 40.0,
1144
+ "longitude" => -30.0
1145
+ })
1146
+ end.save
1147
+ ```
1148
+
1149
+ ### GeoQueries
1150
+
1151
+ TODO: Implement this!
1152
+
1153
+ Now that you have a bunch of objects with spatial coordinates, it would be nice to find out which objects are closest to a point. This can be done by using a GeoPoint data type with query on the field using $nearSphere. Getting a list of ten places that are closest to a user may look something like:
1154
+
1155
+ ```ruby
1156
+ # should look something like this:
1157
+ places = AV::Query.new("PlaceObject").tap do |q|
1158
+ q.near("location", {
1159
+ "latitude" => 30.0,
1160
+ "longitude" => -20.0
1161
+ })
1162
+ end.get
1163
+ ```
1164
+
1165
+ See https://leancloud.cn/docs/rest_api.html#地理查询 for the rest of the geo query types to implement.
1166
+
1167
+ ### Caveats
1168
+
1169
+ At the moment there are a couple of things to watch out for:
1170
+
1171
+ 1. Each PFObject class may only have one key with a PFGeoPoint object.
1172
+
1173
+ 2. Points should not equal or exceed the extreme ends of the ranges. Latitude should not be -90.0 or 90.0. Longitude should not be -180.0 or 180.0. Attempting to use GeoPoint's with latitude and/or longitude outside these ranges will cause an error.
1174
+
1175
+ # 原始文档
1176
+
1177
+ [parse-ruby-client](https://github.com/adelevie/parse-ruby-client)