parse-ruby-client 0.0.1
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/Gemfile +13 -0
- data/Gemfile.lock +20 -0
- data/LICENSE.txt +20 -0
- data/README.md +110 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/example.rb +30 -0
- data/lib/parse.rb +19 -0
- data/lib/parse/client.rb +103 -0
- data/lib/parse/datatypes.rb +74 -0
- data/lib/parse/error.rb +27 -0
- data/lib/parse/object.rb +119 -0
- data/lib/parse/protocol.rb +122 -0
- data/lib/parse/query.rb +79 -0
- data/lib/parse/util.rb +49 -0
- data/parse-ruby-client.gemspec +69 -0
- data/pkg/parse-ruby-client-0.0.1.gem +0 -0
- data/pkg/parse-ruby-client-1-0.0.1.gem +0 -0
- data/pkg/parse-ruby-client.gem +0 -0
- data/test/helper.rb +18 -0
- data/test/test_parse-ruby-client.rb +7 -0
- metadata +116 -0
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "shoulda", ">= 0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.6.4"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.2.5)
|
5
|
+
jeweler (1.6.4)
|
6
|
+
bundler (~> 1.0)
|
7
|
+
git (>= 1.2.5)
|
8
|
+
rake
|
9
|
+
rake (0.9.2.2)
|
10
|
+
rcov (0.9.11)
|
11
|
+
shoulda (2.11.3)
|
12
|
+
|
13
|
+
PLATFORMS
|
14
|
+
ruby
|
15
|
+
|
16
|
+
DEPENDENCIES
|
17
|
+
bundler (~> 1.0.0)
|
18
|
+
jeweler (~> 1.6.4)
|
19
|
+
rcov
|
20
|
+
shoulda
|
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,110 @@
|
|
1
|
+
The original creator of parse-ruby-client, [aalpern](http://github.com/aalpern), has decided to stop work on the project. I'm going to give the project new life, first by maintaining the project as a gem, and second by eventually making it power [parse_resource](http://github.com/adelevie/parse_resource) under the hood.
|
2
|
+
|
3
|
+
# Ruby Client for parse.com REST API
|
4
|
+
|
5
|
+
This file implements a simple Ruby client library for using Parse's REST API.
|
6
|
+
Rather than try to implement an ActiveRecord-compatible library, it tries to
|
7
|
+
match the structure of the iOS and Android SDKs from Parse.
|
8
|
+
|
9
|
+
So far it supports simple GET, PUT, and POST operations on objects. Enough
|
10
|
+
to read & write simple data.
|
11
|
+
|
12
|
+
## Dependencies
|
13
|
+
|
14
|
+
This currently depends on the gems 'json' and 'patron' for JSON support and HTTP, respectively.
|
15
|
+
|
16
|
+
# Getting Started
|
17
|
+
|
18
|
+
---
|
19
|
+
|
20
|
+
To get started, load the parse.rb file and call Parse.init to initialize the client object with
|
21
|
+
your application ID and API key values, which you can obtain from the parse.com dashboard.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
Parse.init :application_id => "<your_app_id>",
|
25
|
+
:api_key => "<your_api_key>"
|
26
|
+
```
|
27
|
+
|
28
|
+
## Creating and Saving Objects
|
29
|
+
|
30
|
+
Create an instance of ```Parse::Object``` with your class name supplied as a string, set some keys, and call ```save()```.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
game_score = Parse::Object.new "GameScore"
|
34
|
+
game_score["score"] = 1337
|
35
|
+
game_score["playerName"] = "Sean Plott"
|
36
|
+
game_score["cheatMode"] = false
|
37
|
+
game_score.save
|
38
|
+
```
|
39
|
+
|
40
|
+
Alternatively, you can initialize the object's initial values with a hash.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
game_score = Parse::Object.new "GameScore", {
|
44
|
+
"score" => 1337, "playerName" => "Sean Plott", "cheatMode" => false
|
45
|
+
}
|
46
|
+
```
|
47
|
+
|
48
|
+
Or if you prefer, you can use symbols for the hash keys - they will be converted to strings
|
49
|
+
by ```Parse::Object.initialize()```.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
game_score = Parse::Object.new "GameScore", {
|
53
|
+
:score => 1337, :playerName => "Sean Plott", :cheatMode => false
|
54
|
+
}
|
55
|
+
```
|
56
|
+
|
57
|
+
## Retrieving Objects
|
58
|
+
|
59
|
+
Individual objects can be retrieved with a single call to ```Parse.get()``` supplying the class and object id.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
game_score = Parse.get "GameScore", "xWMyZ4YEGZ"
|
63
|
+
```
|
64
|
+
|
65
|
+
All the objects in a given class can be retrieved by omitting the object ID. Use caution if you have lots
|
66
|
+
and lots of objects of that class though.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
all_scores = Parse.get "GameScore"
|
70
|
+
```
|
71
|
+
|
72
|
+
## Queries
|
73
|
+
|
74
|
+
Queries are supported by the ```Parse::Query``` class.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# Create some simple objects to query
|
78
|
+
(1..100).each { |i|
|
79
|
+
score = Parse::Object.new "GameScore"
|
80
|
+
score["score"] = i
|
81
|
+
score.save
|
82
|
+
}
|
83
|
+
|
84
|
+
# Retrieve all scores between 10 & 20 inclusive
|
85
|
+
Parse::Query.new("GameScore") \
|
86
|
+
.greater_eq("score", 10) \
|
87
|
+
.less_eq("score", 20) \
|
88
|
+
.get
|
89
|
+
|
90
|
+
# Retrieve a set of specific scores
|
91
|
+
Parse::Query.new("GameScore") \
|
92
|
+
.value_in("score", [10, 20, 30, 40]) \
|
93
|
+
.get
|
94
|
+
|
95
|
+
```
|
96
|
+
|
97
|
+
# TODO
|
98
|
+
|
99
|
+
- Add some form of debug logging
|
100
|
+
- ~~Support for Date, Pointer, and Bytes API [data types](https://www.parse.com/docs/rest#objects-types)~~
|
101
|
+
- Users
|
102
|
+
- ACLs
|
103
|
+
- Login
|
104
|
+
|
105
|
+
|
106
|
+
# Resources
|
107
|
+
|
108
|
+
- parse.com [REST API documentation](https://parse.com/docs/rest)
|
109
|
+
- [parse_resource](https://github.com/adelevie/parse_resource) , an ActiveRecord-compatible wrapper
|
110
|
+
for the API for seamless integration into Rails.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "parse-ruby-client"
|
18
|
+
gem.homepage = "http://github.com/adelevie/parse-ruby-client"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{A simple Ruby client for the parse.com REST API}
|
21
|
+
gem.description = %Q{A simple Ruby client for the parse.com REST API}
|
22
|
+
gem.email = "adelevie@gmail.com"
|
23
|
+
gem.authors = ["Alan deLevie", "Adam Alpern"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "parse-ruby-client #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/example.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Parse.init :application_id => "your_application_id",
|
2
|
+
:api_key => "your_api_key"
|
3
|
+
|
4
|
+
profile = Parse::Object.new "Profile"
|
5
|
+
profile["first_name"] = "John"
|
6
|
+
profile["last_name"] = "Doe"
|
7
|
+
profile["username"] = "jdoe"
|
8
|
+
profile["email_address"] = "jdoe@fubar.com"
|
9
|
+
profile["birthday"] = Parse::Date.new "1980-12-25"
|
10
|
+
profile.save
|
11
|
+
|
12
|
+
profile.increment "login_count"
|
13
|
+
|
14
|
+
# Queries
|
15
|
+
cls = "GameScore"
|
16
|
+
(1..100).each { |i|
|
17
|
+
score = Parse::Object.new cls
|
18
|
+
score["score"] = i
|
19
|
+
score.save
|
20
|
+
}
|
21
|
+
|
22
|
+
Parse::Query.new(cls) \
|
23
|
+
.greater_eq("score", 10) \
|
24
|
+
.less_eq("score", 20) \
|
25
|
+
.get
|
26
|
+
|
27
|
+
Parse::Query.new(cls) \
|
28
|
+
.value_in("score", [10, 20, 30, 40]) \
|
29
|
+
.get
|
30
|
+
|
data/lib/parse.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
## ----------------------------------------------------------------------
|
2
|
+
##
|
3
|
+
## Ruby client for parse.com
|
4
|
+
## A quick library for playing with parse.com's REST API for object storage.
|
5
|
+
## See https://parse.com/docs/rest for full documentation on the API.
|
6
|
+
##
|
7
|
+
## ----------------------------------------------------------------------
|
8
|
+
|
9
|
+
require 'json'
|
10
|
+
require 'patron'
|
11
|
+
require 'date'
|
12
|
+
require 'cgi'
|
13
|
+
|
14
|
+
cwd = Pathname(__FILE__).dirname
|
15
|
+
$:.unshift(cwd.to_s) unless $:.include?(cwd.to_s) || $:.include?(cwd.expand_path.to_s)
|
16
|
+
|
17
|
+
require 'parse/object'
|
18
|
+
require 'parse/query'
|
19
|
+
require 'parse/datatypes'
|
data/lib/parse/client.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'parse/protocol'
|
2
|
+
require 'parse/error'
|
3
|
+
require 'parse/util'
|
4
|
+
|
5
|
+
module Parse
|
6
|
+
|
7
|
+
# A class which encapsulates the HTTPS communication with the Parse
|
8
|
+
# API server. Currently uses the Patron library for low-level HTTP
|
9
|
+
# communication.
|
10
|
+
class Client
|
11
|
+
attr_accessor :host
|
12
|
+
attr_accessor :application_id
|
13
|
+
attr_accessor :api_key
|
14
|
+
attr_accessor :session
|
15
|
+
|
16
|
+
def initialize(data = {})
|
17
|
+
@host = data[:host] || Protocol::HOST
|
18
|
+
@application_id = data[:application_id]
|
19
|
+
@api_key = data[:api_key]
|
20
|
+
@session = Patron::Session.new
|
21
|
+
|
22
|
+
@session.base_url = "https://#{host}"
|
23
|
+
@session.headers["Content-Type"] = "application/json"
|
24
|
+
@session.headers["Accept"] = "application/json"
|
25
|
+
@session.headers["User-Agent"] = "Parse for Ruby, 0.0"
|
26
|
+
@session.headers[Protocol::HEADER_API_KEY] = @api_key
|
27
|
+
@session.headers[Protocol::HEADER_APP_ID] = @application_id
|
28
|
+
end
|
29
|
+
|
30
|
+
# Perform an HTTP request for the given uri and method
|
31
|
+
# with common basic response handling. Will raise a
|
32
|
+
# ParseProtocolError if the response has an error status code,
|
33
|
+
# and will return the parsed JSON body on success, if there is one.
|
34
|
+
def request(uri, method = :get, body = nil, query = nil)
|
35
|
+
options = {}
|
36
|
+
if body
|
37
|
+
options[:data] = body
|
38
|
+
end
|
39
|
+
if query
|
40
|
+
options[:query] = query
|
41
|
+
end
|
42
|
+
|
43
|
+
response = @session.request(method, uri, {}, options)
|
44
|
+
if response.status >= 400
|
45
|
+
raise ParseProtocolError, response
|
46
|
+
else
|
47
|
+
if response.body
|
48
|
+
return JSON.parse response.body
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def get(uri)
|
54
|
+
request(uri)
|
55
|
+
end
|
56
|
+
|
57
|
+
def post(uri, body)
|
58
|
+
request(uri, :post, body)
|
59
|
+
end
|
60
|
+
|
61
|
+
def put(uri, body)
|
62
|
+
request(uri, :put, body)
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete(uri)
|
66
|
+
request(uri, :delete)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Module methods
|
73
|
+
# ------------------------------------------------------------
|
74
|
+
|
75
|
+
# A singleton client for use by methods in Object.
|
76
|
+
# Always use Parse.client to retrieve the client object.
|
77
|
+
@@client = nil
|
78
|
+
|
79
|
+
# Initialize the singleton instance of Client which is used
|
80
|
+
# by all API methods. Parse.init must be called before saving
|
81
|
+
# or retrieving any objects.
|
82
|
+
def Parse.init(data)
|
83
|
+
@@client = Client.new(data)
|
84
|
+
end
|
85
|
+
|
86
|
+
def Parse.client
|
87
|
+
if !@@client
|
88
|
+
raise ParseError, "API not initialized"
|
89
|
+
end
|
90
|
+
@@client
|
91
|
+
end
|
92
|
+
|
93
|
+
# Perform a simple retrieval of a simple object, or all objects of a
|
94
|
+
# given class. If object_id is supplied, a single object will be
|
95
|
+
# retrieved. If object_id is not supplied, then all objects of the
|
96
|
+
# given class will be retrieved and returned in an Array.
|
97
|
+
def Parse.get(class_name, object_id = nil)
|
98
|
+
data = Parse.client.get( Protocol.class_uri(class_name, object_id) )
|
99
|
+
Parse.parse_json class_name, data
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Parse
|
4
|
+
|
5
|
+
# Pointer
|
6
|
+
# ------------------------------------------------------------
|
7
|
+
|
8
|
+
class Pointer
|
9
|
+
attr_accessor :parse_object_id
|
10
|
+
attr_accessor :class_name
|
11
|
+
|
12
|
+
def initialize(data)
|
13
|
+
@class_name = data[Protocol::KEY_CLASS_NAME]
|
14
|
+
@parse_object_id = data[Protocol::KEY_OBJECT_ID]
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_json(*a)
|
18
|
+
{
|
19
|
+
Protocol::KEY_TYPE => Protocol::TYPE_POINTER,
|
20
|
+
Protocol::KEY_CLASS_NAME => @class_name,
|
21
|
+
Protocol::KEY_OBJECT_ID => @parse_object_id
|
22
|
+
}.to_json(*a)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Retrieve the Parse object referenced by this pointer.
|
26
|
+
def get
|
27
|
+
Parse.get @class_name, @parse_object_id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Date
|
32
|
+
# ------------------------------------------------------------
|
33
|
+
|
34
|
+
class Date
|
35
|
+
attr_accessor :value
|
36
|
+
|
37
|
+
def initialize(data)
|
38
|
+
if data.is_a? DateTime
|
39
|
+
@value = data
|
40
|
+
elsif data.is_a? Hash
|
41
|
+
@value = DateTime.parse data["iso"]
|
42
|
+
elsif data.is_a? String
|
43
|
+
@value = DateTime.parse data
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_json(*a)
|
48
|
+
{
|
49
|
+
Protocol::KEY_TYPE => Protocol::TYPE_DATE,
|
50
|
+
"iso" => value.iso8601
|
51
|
+
}.to_json(*a)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Bytes
|
56
|
+
# ------------------------------------------------------------
|
57
|
+
|
58
|
+
class Bytes
|
59
|
+
attr_accessor :value
|
60
|
+
|
61
|
+
def initialize(data)
|
62
|
+
bytes = data["base64"]
|
63
|
+
value = Base64.decode(bytes)
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_json(*a)
|
67
|
+
{
|
68
|
+
Protocol::KEY_TYPE => Protocol::TYPE_BYTES,
|
69
|
+
"base64" => Base64.encode(@value)
|
70
|
+
}.to_json(*a)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
data/lib/parse/error.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Parse
|
2
|
+
|
3
|
+
# Base exception class for errors thrown by the Parse
|
4
|
+
# client library. ParseError will be raised by any
|
5
|
+
# network operation if Parse.init() has not been called.
|
6
|
+
class ParseError < Exception
|
7
|
+
end
|
8
|
+
|
9
|
+
# An exception class raised when the REST API returns an error.
|
10
|
+
# The error code and message will be parsed out of the HTTP response,
|
11
|
+
# which is also included in the response attribute.
|
12
|
+
class ParseProtocolError < ParseError
|
13
|
+
attr_accessor :code
|
14
|
+
attr_accessor :error
|
15
|
+
attr_accessor :response
|
16
|
+
|
17
|
+
def initialize(response)
|
18
|
+
@response = response
|
19
|
+
if response.body
|
20
|
+
data = JSON.parse response.body
|
21
|
+
@code = data["code"]
|
22
|
+
@message = data["error"]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/parse/object.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'parse/protocol'
|
2
|
+
require 'parse/client'
|
3
|
+
require 'parse/error'
|
4
|
+
|
5
|
+
module Parse
|
6
|
+
|
7
|
+
# Represents an individual Parse API object.
|
8
|
+
class Object < Hash
|
9
|
+
attr_reader :parse_object_id
|
10
|
+
attr_reader :class_name
|
11
|
+
attr_reader :created_at
|
12
|
+
attr_reader :updated_at
|
13
|
+
|
14
|
+
def initialize(class_name, data = nil)
|
15
|
+
@class_name = class_name
|
16
|
+
if data
|
17
|
+
parse data
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def uri
|
22
|
+
Protocol.class_uri @class_name, @parse_object_id
|
23
|
+
end
|
24
|
+
|
25
|
+
def pointer
|
26
|
+
Parse::Pointer.new self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Merge a hash parsed from the JSON representation into
|
30
|
+
# this instance. This will extract the reserved fields,
|
31
|
+
# merge the hash keys, and then insure that the reserved
|
32
|
+
# fields do not occur in the underlying hash storage.
|
33
|
+
def parse(data)
|
34
|
+
if !data
|
35
|
+
return
|
36
|
+
end
|
37
|
+
|
38
|
+
@parse_object_id ||= data[Protocol::KEY_OBJECT_ID]
|
39
|
+
|
40
|
+
if data.has_key? Protocol::KEY_CREATED_AT
|
41
|
+
@created_at = DateTime.parse data[Protocol::KEY_CREATED_AT]
|
42
|
+
end
|
43
|
+
|
44
|
+
if data.has_key? Protocol::KEY_UPDATED_AT
|
45
|
+
@updated_at = DateTime.parse data[Protocol::KEY_UPDATED_AT]
|
46
|
+
end
|
47
|
+
|
48
|
+
data.each { |k,v|
|
49
|
+
if k.is_a? Symbol
|
50
|
+
k = k.to_s
|
51
|
+
end
|
52
|
+
if !Protocol::RESERVED_KEYS.include? k
|
53
|
+
self[k] = v
|
54
|
+
end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
private :parse
|
58
|
+
|
59
|
+
# Write the current state of the local object to the API.
|
60
|
+
# If the object has never been saved before, this will create
|
61
|
+
# a new object, otherwise it will update the existing stored object.
|
62
|
+
def save
|
63
|
+
method = @parse_object_id ? :put : :post
|
64
|
+
body = self.to_json
|
65
|
+
|
66
|
+
data = Parse.client.request(self.uri, method, body)
|
67
|
+
if data
|
68
|
+
parse data
|
69
|
+
end
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
# Update the fields of the local Parse object with the current
|
74
|
+
# values from the API.
|
75
|
+
def refresh
|
76
|
+
if @parse_object_id
|
77
|
+
data = Parse.client.get self.uri
|
78
|
+
if data
|
79
|
+
parse data
|
80
|
+
end
|
81
|
+
end
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
# Delete the remote Parse API object.
|
86
|
+
def parse_delete
|
87
|
+
if @parse_object_id
|
88
|
+
response = Parse.client.delete self.uri
|
89
|
+
end
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Increment the given field by an amount, which defaults to 1.
|
94
|
+
def increment(field, amount = 1)
|
95
|
+
value = (self[field] || 0) + amount
|
96
|
+
self[field] = value
|
97
|
+
if !@parse_object_id
|
98
|
+
# TODO - warn that the object must be stored first
|
99
|
+
return nil
|
100
|
+
end
|
101
|
+
|
102
|
+
if amount != 0
|
103
|
+
op = amount > 0 ? Protocol::OP_INCREMENT : Protocol::OP_DECREMENT
|
104
|
+
body = "{\"#{field}\": {\"#{Protocol::KEY_OP}\": \"#{op}\", \"#{Protocol::KEY_AMOUNT}\" : #{amount.abs}}}"
|
105
|
+
data = Parse.client.request( self.uri, :put, body)
|
106
|
+
parse data
|
107
|
+
end
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# Decrement the given field by an amount, which defaults to 1.
|
112
|
+
# A synonym for increment(field, -amount).
|
113
|
+
def decrement(field, amount = 1)
|
114
|
+
increment field, -amount
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Parse
|
2
|
+
# A module which encapsulates the specifics of Parse's REST API.
|
3
|
+
module Protocol
|
4
|
+
|
5
|
+
# Basics
|
6
|
+
# ----------------------------------------
|
7
|
+
|
8
|
+
# The default hostname for communication with the Parse API.
|
9
|
+
HOST = "api.parse.com"
|
10
|
+
|
11
|
+
# The version of the REST API implemented by this module.
|
12
|
+
VERSION = 1
|
13
|
+
|
14
|
+
# HTTP Headers
|
15
|
+
# ----------------------------------------
|
16
|
+
|
17
|
+
# The HTTP header used for passing your application ID to the
|
18
|
+
# Parse API.
|
19
|
+
HEADER_APP_ID = "X-Parse-Application-Id"
|
20
|
+
|
21
|
+
# The HTTP header used for passing your API key to the
|
22
|
+
# Parse API.
|
23
|
+
HEADER_API_KEY = "X-Parse-REST-API-Key"
|
24
|
+
|
25
|
+
# JSON Keys
|
26
|
+
# ----------------------------------------
|
27
|
+
|
28
|
+
# The JSON key used to store the class name of an object
|
29
|
+
# in a Pointer datatype.
|
30
|
+
KEY_CLASS_NAME = "className"
|
31
|
+
|
32
|
+
# The JSON key used to store the ID of Parse objects
|
33
|
+
# in their JSON representation.
|
34
|
+
KEY_OBJECT_ID = "objectId"
|
35
|
+
|
36
|
+
# The JSON key used to store the creation timestamp of
|
37
|
+
# Parse objects in their JSON representation.
|
38
|
+
KEY_CREATED_AT = "createdAt"
|
39
|
+
|
40
|
+
# The JSON key used to store the last modified timestamp
|
41
|
+
# of Parse objects in their JSON representation.
|
42
|
+
KEY_UPDATED_AT = "updatedAt"
|
43
|
+
|
44
|
+
# The JSON key used in the top-level response object
|
45
|
+
# to indicate that the response contains an array of objects.
|
46
|
+
RESPONSE_KEY_RESULTS = "results"
|
47
|
+
|
48
|
+
# The JSON key used to identify an operator in the increment/decrement
|
49
|
+
# API call.
|
50
|
+
KEY_OP = "__op"
|
51
|
+
|
52
|
+
# The JSON key used to identify the datatype of a special value.
|
53
|
+
KEY_TYPE = "__type"
|
54
|
+
|
55
|
+
# The JSON key used to specify the numerical value in the
|
56
|
+
# increment/decrement API call.
|
57
|
+
KEY_AMOUNT = "amount"
|
58
|
+
|
59
|
+
RESERVED_KEYS = [ KEY_CLASS_NAME, KEY_CREATED_AT, KEY_OBJECT_ID ]
|
60
|
+
|
61
|
+
# Other Constants
|
62
|
+
# ----------------------------------------
|
63
|
+
|
64
|
+
# Operation name for incrementing an objects field value remotely
|
65
|
+
OP_INCREMENT = "Increment"
|
66
|
+
|
67
|
+
# Operation name for decrementing an objects field value remotely
|
68
|
+
OP_DECREMENT = "Decrement"
|
69
|
+
|
70
|
+
|
71
|
+
# The data type name for special JSON objects representing a reference
|
72
|
+
# to another Parse object.
|
73
|
+
TYPE_POINTER = "Pointer"
|
74
|
+
|
75
|
+
# The data type name for special JSON objects containing an array of
|
76
|
+
# encoded bytes.
|
77
|
+
TYPE_BYTES = "Bytes"
|
78
|
+
|
79
|
+
# The data type name for special JSON objects representing a date/time.
|
80
|
+
TYPE_DATE = "Date"
|
81
|
+
|
82
|
+
# The data type name for special JSON objects representing a
|
83
|
+
# location specified as a latitude/longitude pair.
|
84
|
+
TYPE_GEOPOINT = "GeoPoint"
|
85
|
+
|
86
|
+
# The data type name for special JSON objects representing
|
87
|
+
# a file.
|
88
|
+
TYPE_FILE = "File"
|
89
|
+
|
90
|
+
# The class name for User objects, when referenced by a Pointer.
|
91
|
+
CLASS_USER = "_User"
|
92
|
+
|
93
|
+
# URI Helpers
|
94
|
+
# ----------------------------------------
|
95
|
+
|
96
|
+
# Construct a uri referencing a given Parse object
|
97
|
+
# class or instance (of object_id is non-nil).
|
98
|
+
def Protocol.class_uri(class_name, object_id = nil)
|
99
|
+
if object_id
|
100
|
+
"/#{VERSION}/classes/#{class_name}/#{object_id}"
|
101
|
+
else
|
102
|
+
"/#{VERSION}/classes/#{class_name}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Construct a uri referencing a given Parse user
|
107
|
+
# instance or the users category.
|
108
|
+
def Protocol.user_uri(user_id = nil)
|
109
|
+
if user_id
|
110
|
+
"/#{VERSION}/users/#{user_id}"
|
111
|
+
else
|
112
|
+
"/#{VERSION}/users"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Construct a uri referencing a file stored by the API.
|
117
|
+
def Protocol.file_uri(file_name)
|
118
|
+
"/#{VERSION}/files/#{file_name}"
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
data/lib/parse/query.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Parse
|
4
|
+
|
5
|
+
class Query
|
6
|
+
attr_accessor :where
|
7
|
+
attr_accessor :class_name
|
8
|
+
attr_accessor :order_by
|
9
|
+
attr_accessor :order
|
10
|
+
attr_accessor :limit
|
11
|
+
attr_accessor :skip
|
12
|
+
|
13
|
+
def initialize(cls_name)
|
14
|
+
@class_name = cls_name
|
15
|
+
@where = {}
|
16
|
+
@order = :ascending
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_constraint(field, constraint)
|
20
|
+
current = where[field]
|
21
|
+
if current && current.is_a?(Hash) && constraint.is_a?(Hash)
|
22
|
+
current.merge! constraint
|
23
|
+
else
|
24
|
+
where[field] = constraint
|
25
|
+
end
|
26
|
+
end
|
27
|
+
private :add_constraint
|
28
|
+
|
29
|
+
def eq(field, value)
|
30
|
+
add_constraint field, value
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def regex(field, expression)
|
35
|
+
add_constraint field, { "$regex" => expression }
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def less_than(field, value)
|
40
|
+
add_constraint field, { "$lt" => value }
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def less_eq(field, value)
|
45
|
+
add_constraint field, { "$lte" => value }
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def greater_than(field, value)
|
50
|
+
add_constraint field, { "$gt" => value }
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def greater_eq(field, value)
|
55
|
+
add_constraint field, { "$gte" => value }
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def value_in(field, values)
|
60
|
+
add_constraint field, { "$in" => values }
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def exists(field, value = true)
|
65
|
+
add_constraint field, { "$exists" => value }
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def get
|
70
|
+
uri = Protocol.class_uri @class_name
|
71
|
+
query = { "where" => CGI.escape(@where.to_json) }
|
72
|
+
|
73
|
+
response = Parse.client.request uri, :get, nil, query
|
74
|
+
Parse.client.parse_response class_name, response
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/lib/parse/util.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Parse
|
2
|
+
|
3
|
+
# Parse a JSON representation into a fully instantiated
|
4
|
+
# class. obj can be either a string or a Hash as parsed
|
5
|
+
# by JSON.parse
|
6
|
+
# @param class_name [Object]
|
7
|
+
# @param obj [Object]
|
8
|
+
def Parse.parse_json(class_name, obj)
|
9
|
+
|
10
|
+
if obj.nil?
|
11
|
+
nil
|
12
|
+
# String
|
13
|
+
elsif obj.is_a? String
|
14
|
+
parse_json class_name, JSON.parse(obj)
|
15
|
+
|
16
|
+
# Array
|
17
|
+
elsif obj.is_a? Array
|
18
|
+
obj.collect { |o| parse_json(class_name, o) }
|
19
|
+
|
20
|
+
# Hash
|
21
|
+
elsif obj.is_a? Hash
|
22
|
+
|
23
|
+
# If it's a datatype hash
|
24
|
+
if obj.has_key?(Protocol::KEY_TYPE)
|
25
|
+
parse_datatype obj
|
26
|
+
elsif obj.size == 1 && obj.has_key?(Protocol::KEY_RESULTS) && obj[Protocol::KEY_RESULTS].is_a?(Array)
|
27
|
+
obj[Protocol::KEY_RESULTS].collect { |o| parse_json(class_name, o) }
|
28
|
+
else # otherwise it must be a regular object
|
29
|
+
Parse::Object.new class_name, obj
|
30
|
+
end
|
31
|
+
|
32
|
+
else
|
33
|
+
obj
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def Parse.parse_datatype(obj)
|
38
|
+
type = obj[Protocol::KEY_TYPE]
|
39
|
+
|
40
|
+
case type
|
41
|
+
when Protocol::TYPE_POINTER
|
42
|
+
Parse::Pointer.new obj
|
43
|
+
when Protocol::TYPE_BYTES
|
44
|
+
Parse::Bytes.new obj
|
45
|
+
when Protocol::TYPE_DATE
|
46
|
+
Parse::Date.new obj
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "parse-ruby-client"
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Alan deLevie", "Adam Alpern"]
|
12
|
+
s.date = "2012-02-05"
|
13
|
+
s.description = "A simple Ruby client for the parse.com REST API"
|
14
|
+
s.email = "adelevie@gmail.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"Gemfile",
|
21
|
+
"Gemfile.lock",
|
22
|
+
"LICENSE.txt",
|
23
|
+
"README.md",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"example.rb",
|
27
|
+
"lib/parse.rb",
|
28
|
+
"lib/parse/client.rb",
|
29
|
+
"lib/parse/datatypes.rb",
|
30
|
+
"lib/parse/error.rb",
|
31
|
+
"lib/parse/object.rb",
|
32
|
+
"lib/parse/protocol.rb",
|
33
|
+
"lib/parse/query.rb",
|
34
|
+
"lib/parse/util.rb",
|
35
|
+
"parse-ruby-client.gemspec",
|
36
|
+
"pkg/parse-ruby-client-0.0.1.gem",
|
37
|
+
"pkg/parse-ruby-client-1-0.0.1.gem",
|
38
|
+
"pkg/parse-ruby-client.gem",
|
39
|
+
"test/helper.rb",
|
40
|
+
"test/test_parse-ruby-client.rb"
|
41
|
+
]
|
42
|
+
s.homepage = "http://github.com/adelevie/parse-ruby-client"
|
43
|
+
s.licenses = ["MIT"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = "1.8.10"
|
46
|
+
s.summary = "A simple Ruby client for the parse.com REST API"
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
s.specification_version = 3
|
50
|
+
|
51
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
52
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
53
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
54
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
55
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
58
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
59
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
60
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
61
|
+
end
|
62
|
+
else
|
63
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
64
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
65
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
66
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
Binary file
|
Binary file
|
Binary file
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'parse-ruby-client-1'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: parse-ruby-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alan deLevie
|
9
|
+
- Adam Alpern
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-02-05 00:00:00.000000000Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: shoulda
|
17
|
+
requirement: &70311876454920 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *70311876454920
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: bundler
|
28
|
+
requirement: &70311876453900 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *70311876453900
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: jeweler
|
39
|
+
requirement: &70311876434760 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.6.4
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *70311876434760
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rcov
|
50
|
+
requirement: &70311876433780 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *70311876433780
|
59
|
+
description: A simple Ruby client for the parse.com REST API
|
60
|
+
email: adelevie@gmail.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files:
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
files:
|
67
|
+
- Gemfile
|
68
|
+
- Gemfile.lock
|
69
|
+
- LICENSE.txt
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- VERSION
|
73
|
+
- example.rb
|
74
|
+
- lib/parse.rb
|
75
|
+
- lib/parse/client.rb
|
76
|
+
- lib/parse/datatypes.rb
|
77
|
+
- lib/parse/error.rb
|
78
|
+
- lib/parse/object.rb
|
79
|
+
- lib/parse/protocol.rb
|
80
|
+
- lib/parse/query.rb
|
81
|
+
- lib/parse/util.rb
|
82
|
+
- parse-ruby-client.gemspec
|
83
|
+
- pkg/parse-ruby-client-0.0.1.gem
|
84
|
+
- pkg/parse-ruby-client-1-0.0.1.gem
|
85
|
+
- pkg/parse-ruby-client.gem
|
86
|
+
- test/helper.rb
|
87
|
+
- test/test_parse-ruby-client.rb
|
88
|
+
homepage: http://github.com/adelevie/parse-ruby-client
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
hash: -1143289306669216906
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 1.8.10
|
113
|
+
signing_key:
|
114
|
+
specification_version: 3
|
115
|
+
summary: A simple Ruby client for the parse.com REST API
|
116
|
+
test_files: []
|