parsecom 0.1.0 → 0.2.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.
data/README.md CHANGED
@@ -2,6 +2,41 @@
2
2
 
3
3
  Yet-Another Parse.com Library written in Pure Ruby
4
4
 
5
+ **Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*
6
+
7
+ - [Parsecom](#parsecom)
8
+ - [Usage](#usage)
9
+ - [Preparing](#preparing)
10
+ - [Declaring Parse Classes](#declaring-parse-classes)
11
+ - [Objects](#objects)
12
+ - [Creating Objects](#creating-objects)
13
+ - [Retrieving Objects](#retrieving-objects)
14
+ - [Updating Objects](#updating-objects)
15
+ - [Counters](#counters)
16
+ - [Arrays](#arrays)
17
+ - [Relations](#relations)
18
+ - [Deleting Objects](#deleting-objects)
19
+ - [Batch Operations](#batch-operations)
20
+ - [Queries](#queries)
21
+ - [Basic Queries](#basic-queries)
22
+ - [Query Constraints](#query-constraints)
23
+ - [Queries on Array Values](#queries-on-array-values)
24
+ - [Relational Queries](#relational-queries)
25
+ - [Counting Objects](#counting-objects)
26
+ - [Compound Queries](#compound-queries)
27
+ - [Users](#users)
28
+ - [Sign up](#sign-up)
29
+ - [Log in](#log-in)
30
+ - [Requesting A Password Reset](#requesting-a-password-reset)
31
+ - [Retrieving Users](#retrieving-users)
32
+ - [Updating Users](#updating-users)
33
+ - [Querying](#querying)
34
+ - [Deleting Users](#deleting-users)
35
+ - [Linking Users](#linking-users)
36
+ - [Roles](#roles)
37
+ - [Creating Roles](#creating-roles)
38
+ - [Security](#security)
39
+
5
40
  ## Usage
6
41
 
7
42
  ### Preparing
@@ -63,9 +98,11 @@ Parse::Object.create :GameScore
63
98
 
64
99
  It may be suitable for writing code in declarative style.
65
100
 
66
- ### Creating Objects
101
+ ### Objects
102
+
103
+ #### Creating Objects
67
104
 
68
- To create new parse object, juse new and save the object.
105
+ To create new parse object, just new and save the object.
69
106
 
70
107
  ```ruby
71
108
  game_score = GameScore.new
@@ -78,7 +115,7 @@ game_score.new? # => false
78
115
  game_score.parse_object_id # => 'Ed1nuqPvcm'
79
116
  ```
80
117
 
81
- ### Retrieving Objects
118
+ #### Retrieving Objects
82
119
 
83
120
  There are two ways to retrieve objects. One is using Query objects directly and
84
121
  another is using Parse::Object as a facade of a query object.
@@ -95,30 +132,15 @@ results = GameScore.find :where => {:objectId => 'Ed1nuqPvcm'}
95
132
  result = GameScore.find_by_id 'Ed1nuqPvcm'
96
133
  ```
97
134
 
98
- More complex query
135
+ To fetch a child object, you can use the :include parameter.
99
136
 
100
137
  ```ruby
101
- # useing Query object directly
102
- results = Parse::Query.new(GameScore).
103
- limit(10).
104
- order(score).
105
- where do
106
- column(:score).gte(1000).lte(3000)
107
- column(:cheatMode).eq(false)
108
- end.
109
- invoke
110
-
111
- # using Query object through Parse::Object
112
- results = GameScore.find :limit => 10, :order => 'score',
113
- :where => proc{
114
- column(:score).gte(1000).lte(3000)
115
- column(:cheatMode).eq(false)
116
- }
138
+ results = GameScore.find :where => {:objectId => 'Ed1nuqPvcm'}, :include => 'game'
117
139
  ```
118
140
 
119
141
  To know more about retrieving objects, see spec/parse_query_spec.rb
120
142
 
121
- ### Updating Objects
143
+ #### Updating Objects
122
144
 
123
145
  To update attributes, just update the attribute and save.
124
146
 
@@ -135,7 +157,7 @@ the Parse::Client object for it.
135
157
  Parse::Client.default.update :GaemScore, 'Ed1nuqPvcm', :score => 73453
136
158
  ```
137
159
 
138
- #### Counters
160
+ ##### Counters
139
161
 
140
162
  ```ruby
141
163
  game_score = GameScore.find_by_id 'Ed1nuqPvcm'
@@ -143,7 +165,7 @@ game_score.score = Parse::Op::Increment.new 1
143
165
  game_score.save
144
166
  ```
145
167
 
146
- #### Arrays
168
+ ##### Arrays
147
169
 
148
170
  ```ruby
149
171
  game_score = GameScore.find_by_id 'Ed1nuqPvcm'
@@ -151,7 +173,7 @@ game_score.skils = Parse::Op::AddUnique.new 'flying', 'kungfu'
151
173
  game_score.save
152
174
  ```
153
175
 
154
- #### Relations
176
+ ##### Relations
155
177
 
156
178
  ```ruby
157
179
  game_score = GameScore.find_by_id 'Ed1nuqPvcm'
@@ -165,7 +187,7 @@ game_score.opponents = Parse::Op::RemoveRelation.new player.pointer
165
187
  game_score.save
166
188
  ```
167
189
 
168
- ### Deleting Objects
190
+ #### Deleting Objects
169
191
 
170
192
  ```ruby
171
193
  game_score = GameScore.find_by_id 'Ed1nuqPvcm'
@@ -178,7 +200,7 @@ game_score.opponents = Parse::Op::Delete.new
178
200
  game_score.save
179
201
  ```
180
202
 
181
- ### Batch Operations
203
+ #### Batch Operations
182
204
 
183
205
  ```ruby
184
206
  seans_score = GameScore.new 'score' => 1337, 'playerName' => 'Sean Plott'
@@ -191,18 +213,236 @@ end
191
213
  result = batch.run
192
214
  ```
193
215
 
194
- ### Sign up
216
+ ### Queries
217
+
218
+ #### Basic Queries
219
+
220
+ ```ruby
221
+ game_scores = GameScore.find :all
222
+ ```
223
+
224
+ #### Query Constraints
225
+
226
+ ```ruby
227
+ game_scores = GameScore.find :where => {"playerName" => "Sean Plott", "cheatMode" => false}
228
+ ```
229
+
230
+ ```ruby
231
+ game_scores = GameScore.find :where => proc {
232
+ column(:score).gte(1000).lte(3000)
233
+ }
234
+ ```
235
+
236
+ ```ruby
237
+ game_scores = GameScore.find :where => proc {
238
+ column(:score).in(1, 3, 5, 7, 9)
239
+ }
240
+ ```
241
+
242
+ ```ruby
243
+ game_scores = GameScore.find :where => proc {
244
+ column(:playerName).nin("Jonathan Walsh", "Dario Wunsch", "Shawn Simon")
245
+ }
246
+ ```
247
+
248
+ ```ruby
249
+ game_scores = GameScore.find :where => proc {
250
+ column(:score).exists
251
+ }
252
+ ```
253
+
254
+ ```ruby
255
+ game_scores = GameScore.find :where => proc {
256
+ column(:score).exists(false)
257
+ }
258
+ ```
259
+
260
+ ```ruby
261
+ game_scores = GameScore.find :where => proc {
262
+ subquery = subquery_for :Team
263
+ subquery.where {column(:winPct).gt(0.5)}
264
+ subquery.key = 'city'
265
+ column(:hometown).select(subquery)
266
+ }
267
+ ```
268
+
269
+ ```ruby
270
+ game_scores = GameScore.find :order => 'score'
271
+ ```
272
+
273
+ ```ruby
274
+ game_scores = GameScore.find :order => '-score'
275
+ game_scores = GameScore.find :order_desc => 'score'
276
+ ```
277
+
278
+ ```ruby
279
+ game_scores = GameScore.find :order => ['score', '-name']
280
+ ```
281
+
282
+ ```ruby
283
+ game_scores = GameScore.find :limit => 200, :skip => 400
284
+ ```
285
+
286
+ ```ruby
287
+ game_scores = GameScore.find :keys => ['score', 'playerName']
288
+ ```
289
+
290
+ #### Queries on Array Values
291
+
292
+ ```ruby
293
+ game_scores = GameScore.find :where => proc {
294
+ column(:arrayKey).contains(2)
295
+ }
296
+ ```
297
+
298
+ ```ruby
299
+ game_scores = GameScore.find :where => proc {
300
+ column(:arrayKey).all(2, 3, 4)
301
+ }
302
+ ```
303
+
304
+ #### Relational Queries
305
+
306
+ ```ruby
307
+ game_scores = GameScore.find :where => proc {
308
+ post = Parse::Object(:Post).new :objectId => '8TOXdXf3tz'
309
+ column(:post).eq(post)
310
+ }
311
+ ```
312
+
313
+ ```ruby
314
+ game_scores = GameScore.find :where => proc {
315
+ subquery = subquery_for :Post
316
+ subquery.where do
317
+ column(:image).exists(true)
318
+ end
319
+ column(:post).in_query(subquery)
320
+ }
321
+ ```
322
+
323
+ ```ruby
324
+ game_scores = GameScore.find :where => proc {
325
+ pointer = Parse::Pointer.new('className' => 'Post', 'objectId' => '8TOXdXf3tz')
326
+ related_to :likes, pointer
327
+ }
328
+ ```
329
+
330
+ #### Counting Objects
331
+
332
+ TBD
333
+
334
+ #### Compound Queries
335
+
336
+ ```ruby
337
+ game_scores = GameScore.find :where => proc {
338
+ or_condition column(:wins).gt(150), column(:wins).lt(5)
339
+ }
340
+ ```
341
+
342
+ ### Users
343
+
344
+ #### Sign up
195
345
 
196
346
  ```ruby
197
347
  user = Parse::User.sign_up 'YOUR USERNAME', 'YOUR PASSWORD'
198
348
  ```
199
349
 
200
- ### Log in
350
+ #### Log in
201
351
 
202
352
  ```ruby
203
353
  user = Parse::User.log_in 'YOUR USERNAME', 'YOUR PASSWORD'
204
354
  ```
205
355
 
356
+ #### Requesting A Password Reset
357
+
358
+ ```ruby
359
+ Parse::User.request_password_reset 'your@email.address'
360
+ ```
361
+
362
+ #### Retrieving Users
363
+
364
+ ```ruby
365
+ user = Parse::User.find_by_id :g7y9tkhB7O
366
+ ```
367
+
368
+ #### Updating Users
369
+
370
+ ```ruby
371
+ user = Parse::User.find_by_id :g7y9tkhB7O
372
+ user.phone = '415-369-6201'
373
+ user.save
374
+ ```
375
+
376
+ #### Querying
377
+
378
+ ```ruby
379
+ users = Parse::User.find :all
380
+ ```
381
+
382
+ #### Deleting Users
383
+
384
+ ```ruby
385
+ user = Parse::User.find_by_id :g7y9tkhB7O
386
+ user.delete
387
+ ```
388
+
389
+ #### Linking Users
390
+
391
+ TBD
392
+
393
+ ### Roles
394
+
395
+ #### Creating Roles
396
+
397
+ ```ruby
398
+ moderator = Parse::Role.new 'name' => 'Moderators', 'ACL' => Parse::ACL::PUBLIC_READ_ONLY
399
+ moderator.save
400
+ ```
401
+
402
+ ```ruby
403
+ moderator = Parse::Role.new 'name' => 'Moderators', 'ACL' => Parse::ACL::PUBLIC_READ_ONLY
404
+ moderator.roles.add Parse::Role.new('objectId' => 'Ed1nuqPvc')
405
+ moderator.users.add Parse::User.new('objectId' => '8TOXdXf3tz')
406
+ moderator.save
407
+ ```
408
+
409
+ #### Retrieving Roles
410
+
411
+ ```ruby
412
+ role = Parse::Role.find_by_id 'mrmBZvsErB'
413
+ role.name # => 'Moderators'
414
+ role.ACL.readable? '*' # => true
415
+ role.ACL.writable? 'role:Administrators' # => true
416
+ ```
417
+
418
+ #### Updating Roles
419
+
420
+ ```ruby
421
+ user1 = Parse::User.new 'objectId' => '8TOXdXf3tz'
422
+ user2 = Parse::User.new 'objectId' => 'g7y9tkhB7O'
423
+ role = Parse::Role.find_by_id 'mrmBZvsErB'
424
+ role.users = Parse::Op::AddRelation.new user1.pointer, user2.pointer
425
+ role.save
426
+ ```
427
+
428
+ ```ruby
429
+ removed_role = Parse::Role.new 'objectId' => 'Ed1nuqPvc'
430
+ role = Parse::Role.find_by_id 'mrmBZvsErB'
431
+ role.roles = Parse::Op::RemoveRelation.new removed_role
432
+ role.save
433
+ ```
434
+
435
+ #### Deleting Roles
436
+
437
+ ```ruby
438
+ role = Parse::Role.find_by_id 'mrmBZvsErB'
439
+ role.delete
440
+ ```
441
+
442
+ ### Files
443
+
444
+ TBD
445
+
206
446
  ### Security
207
447
 
208
448
  If you add an exclamation mark, "!" after the method name, the method is executed by using the master key.
@@ -0,0 +1,40 @@
1
+ # coding:utf-8
2
+ module Parse
3
+ class ACL
4
+ READ_ONLY = {'read' => true}
5
+ WRITE_ONLY = {'write' => true}
6
+ READ_WRITE = {'read' => true, 'write' => true}
7
+ NONE = {}
8
+ PUBLIC = '*'
9
+
10
+ def initialize hash={}, &block
11
+ @acl = hash.dup
12
+ tap &block if block
13
+ end
14
+
15
+ def readable user
16
+ (@acl[user] ||= {})['read'] = true
17
+ end
18
+
19
+ def readable? user
20
+ !!(@acl[user] ||= {})['read']
21
+ end
22
+
23
+ def writable user
24
+ (@acl[user] ||= {})['write'] = true
25
+ end
26
+
27
+ def writable? user
28
+ !!(@acl[user] ||= {})['write']
29
+ end
30
+
31
+ def to_json *args
32
+ @acl.to_json
33
+ end
34
+
35
+ PUBLIC_READ_ONLY = self.new PUBLIC => READ_ONLY
36
+ PUBLIC_WRITE_ONLY = self.new PUBLIC => WRITE_ONLY
37
+ PUBLIC_READ_WRITE = self.new PUBLIC => READ_WRITE
38
+ PUBLIC_NONE = self.new PUBLIC => NONE
39
+ end
40
+ end
@@ -28,8 +28,20 @@ module Parse
28
28
  @http_client = http_client || Parse::HttpClient.new(API_SERVER)
29
29
  end
30
30
 
31
+ def canonical_endpoint endpoint
32
+ case endpoint
33
+ when %r|/#{API_VERSION}/classes/_User|
34
+ endpoint.sub %r|/#{API_VERSION}/classes/_User|, "/#{API_VERSION}/users"
35
+ when %r|/#{API_VERSION}/classes/_Role|
36
+ endpoint.sub %r|/#{API_VERSION}/classes/_Role|, "/#{API_VERSION}/roles"
37
+ else
38
+ endpoint
39
+ end
40
+ end
41
+
31
42
  def call_api method, endpoint, body=nil, opt_headers={}, &block
32
43
  endpoint = "/#{API_VERSION}/#{endpoint}" unless endpoint[0] == '/'
44
+ endpoint = canonical_endpoint endpoint
33
45
  headers = build_headers opt_headers
34
46
  if body.is_a?(Hash)
35
47
  body = Hash[*(body.to_a.map{|k, v| [k, URI.encode(v)]}.flatten)].to_json
@@ -87,6 +87,8 @@ module Parse
87
87
  if v.is_a? Hash
88
88
  body_hash[k] =
89
89
  case v['__type']
90
+ when nil
91
+ Parse::ACL.new v
90
92
  when 'Date'
91
93
  Date.parse v['iso']
92
94
  when 'File'
@@ -145,6 +147,9 @@ module Parse
145
147
  check_deleted!
146
148
  hash = string_keyed_hash hash
147
149
  @updated_hash.update hash
150
+ @updated_hash.reject! do |k, v|
151
+ v.is_a?(Parse::Relation) && !v.changed?
152
+ end
148
153
  method = use_master_key ? :create! : :create
149
154
  parse_client.send(method, self.parse_class_name, @updated_hash) do |response|
150
155
  @parse_object_id = response['objectId']
@@ -2,7 +2,13 @@ module Parse
2
2
  module Op
3
3
  class AddRelation
4
4
  def initialize *pointers
5
- @pointers = pointers
5
+ @pointers = pointers.map do |p|
6
+ if p.is_a? Parse::Object
7
+ p.pointer
8
+ else
9
+ p
10
+ end
11
+ end
6
12
  end
7
13
 
8
14
  def to_json *args
@@ -16,8 +16,16 @@ module Parse
16
16
  @object ||= pointed_parse_class.find_by_id @raw_hash['objectId']
17
17
  end
18
18
 
19
- def to_json
20
- %Q|{"__type":"Pointer","className":"#{@raw_hash['className']}","objectId":"#{@raw_hash['objectId']}"}|
19
+ def to_h
20
+ {
21
+ "__type" => "Pointer",
22
+ "className" => "#{@raw_hash['className']}",
23
+ "objectId" => "#{@raw_hash['objectId']}"
24
+ }
25
+ end
26
+
27
+ def to_json *args
28
+ to_h.to_json
21
29
  end
22
30
 
23
31
  private
@@ -5,6 +5,24 @@ module Parse
5
5
  @parent_object = parent
6
6
  @column_name = column_name
7
7
  @raw_hash = hash
8
+ @added_pointers = []
9
+ @removed_pointers = []
10
+ end
11
+
12
+ def add item
13
+ unless @removed_pointers.empty?
14
+ raise ArgumentError.new('Please save for removed items before adding')
15
+ end
16
+ item = item.pointer if item.is_a?(Parse::Object)
17
+ @added_pointers.push item
18
+ end
19
+
20
+ def remove item
21
+ unless @added_pointers.empty?
22
+ raise ArgumentError.new('Please save for added items before removing')
23
+ end
24
+ item = item.pointer if item.is_a?(Parse::Object)
25
+ @removed_pointers.push item
8
26
  end
9
27
 
10
28
  def load parse_client=Parse::Client.default
@@ -18,5 +36,33 @@ module Parse
18
36
  end
19
37
  @objects
20
38
  end
39
+
40
+ def changed?
41
+ (@added_pointers.size + @removed_pointers.size) != 0
42
+ end
43
+
44
+ def to_h
45
+ if not @added_pointers.empty?
46
+ {
47
+ "__op" => "AddRelation",
48
+ "objects" => @added_pointers.map(&:to_h)
49
+ }
50
+ elsif not @removed_pointers.empty?
51
+ {
52
+ "__op" => "RemoveRelation",
53
+ "objects" => @removed_pointers.map(&:to_h)
54
+ }
55
+ else
56
+ {
57
+ "__op" => "AddRelation",
58
+ "objects" => [nil]
59
+ }
60
+ end
61
+ end
62
+
63
+ def to_json *args
64
+ p to_h
65
+ to_h.to_json
66
+ end
21
67
  end
22
68
  end
@@ -1,6 +1,50 @@
1
1
  # coding:utf-8
2
2
  module Parse
3
3
  class Role < Object
4
- # TODO
4
+ class << self
5
+ def parse_class_name
6
+ '_Role'
7
+ end
8
+ end
9
+
10
+ def initialize hash
11
+ super
12
+
13
+ #set_column 'roles', RoleItemArray.new(Parse::Role)
14
+ #set_column 'users', RoleItemArray.new(Parse::User)
15
+ set_column 'roles', Parse::Relation.new(self, 'roles', {'className' => '_Role'})
16
+ set_column 'users', Parse::Relation.new(self, 'users', {'className' => '_User'})
17
+ end
18
+
19
+ class RoleItemArray < Array
20
+ def initialize klass
21
+ @klass = klass
22
+ end
23
+
24
+ def add item
25
+ push \
26
+ case item
27
+ when String, Symbol
28
+ @klass.new('objectId' => item).pointer
29
+ when @klass
30
+ item.pointer
31
+ when Pointer
32
+ super
33
+ else
34
+ raise ArgumentError.new("wrong type: #{item.class.name}")
35
+ end
36
+ end
37
+
38
+ def to_h
39
+ {
40
+ "__op" => "AddRelation",
41
+ "objects" => map {|r| JSON.parse(r.to_json)}
42
+ }
43
+ end
44
+
45
+ def to_json *args
46
+ to_h.to_json
47
+ end
48
+ end
5
49
  end
6
50
  end
@@ -14,6 +14,10 @@ module Parse
14
14
  def log_in username, password
15
15
  self.new(username, password).log_in
16
16
  end
17
+
18
+ def request_password_reset email
19
+ Parse::Client.default.call_api :post, 'requestPasswordReset', "email" => email
20
+ end
17
21
  end
18
22
 
19
23
  def initialize username=nil, password=nil, hash={}
@@ -1,3 +1,3 @@
1
1
  module Parse
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -11,6 +11,7 @@ require 'parse/ext/string'
11
11
  require 'parse/http_client'
12
12
  require 'parse/client'
13
13
  require 'parse/query'
14
+ require 'parse/acl'
14
15
  require 'parse/object'
15
16
  require 'parse/user'
16
17
  require 'parse/role'
@@ -59,7 +59,7 @@ http_interactions:
59
59
  recorded_at: Tue, 08 Oct 2013 13:18:37 GMT
60
60
  - request:
61
61
  method: get
62
- uri: https://api.parse.com/1/classes/_User/5VuUwoEe0j
62
+ uri: https://api.parse.com/1/users/5VuUwoEe0j
63
63
  body:
64
64
  encoding: US-ASCII
65
65
  string: ''
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parsecom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-15 00:00:00.000000000 Z
12
+ date: 2013-10-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -72,6 +72,7 @@ files:
72
72
  - LICENSE.txt
73
73
  - README.md
74
74
  - Rakefile
75
+ - lib/parse/acl.rb
75
76
  - lib/parse/batch.rb
76
77
  - lib/parse/batch_http_client.rb
77
78
  - lib/parse/client.rb