parse-ruby-client 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|