parsecom 0.1.0 → 0.2.0

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