parsecom 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ .parse_credentials
2
+ *.swp
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in parsecom.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ando Yasushi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # Parsecom
2
+
3
+ Yet-Another Parse.com Library written in Pure Ruby
4
+
5
+ ## Usage
6
+
7
+ ### Preparing
8
+
9
+ Before using the library, you should import this and set your credentials on
10
+ the library.
11
+
12
+ ```ruby
13
+ require 'parsecom'
14
+ Parse.credentials application_id:'YOUR APPID', api_key:'YOUR APIKEY'
15
+ ```
16
+
17
+ ### Declaring Parse Classes
18
+
19
+ There are three ways to declare a parse class.
20
+
21
+ First, you can declare a ruby class inherited from Parse::Object. By using
22
+ this way, you can add your own properties and methods to the class.
23
+
24
+ ```ruby
25
+ class GameScore < Parse::Object
26
+ # ..snip..
27
+ end
28
+ ```
29
+
30
+ Secondly, you can also declare your parse class by calling the Parse::Object
31
+ method.
32
+
33
+ ```ruby
34
+ Parse::Object(:GameScore)
35
+ ```
36
+
37
+ It returns a parse class, so that you can call its class methods directly.
38
+
39
+ ```ruby
40
+ Parse::Object(:GameScore).find :limit => 3
41
+ ```
42
+
43
+ Lastly, Parse::Object class provides create method for you to declare new
44
+ class.
45
+
46
+ ```ruby
47
+ Parse::Object.create :GameScore
48
+ ```
49
+
50
+ It may suitable for writing code in declarative style.
51
+
52
+ ### Creating Objects
53
+
54
+ To create new parse object, juse new and save the object.
55
+
56
+ ```ruby
57
+ game_score = GameScore.new
58
+ game_score.score = 1337
59
+ game_score.playerName = 'Sean Plott'
60
+ game_score.cheatMode = false
61
+ game_score.new? # => true
62
+ game_score.save
63
+ game_score.new? # => false
64
+ game_score.obj_id # => 'Ed1nuqPvcm'
65
+ ```
66
+
67
+ ### Retrieving Objects
68
+
69
+ There are two ways to retrieve objects. One is using Query objects directly and
70
+ another is using Parse::Object as a facade of query objects.
71
+
72
+ ```ruby
73
+ # useing Query object directly
74
+ query = Parse::Query.new GameScore
75
+ query.where :objectId => 'Ed1nuqPvcm'
76
+ results = query.invoke
77
+
78
+ # using Query object through Parse::Object
79
+ results = GameScore.find :where => {:objectId => 'Ed1nuqPvcm'}
80
+ # if you would like to find by objectId, you can easily pass it directly
81
+ result = GameScore.find 'Ed1nuqPvcm'
82
+ ```
83
+
84
+ More complex search
85
+
86
+ ```ruby
87
+ # useing Query object directly
88
+ results = Parse::Query.new(GameScore).
89
+ limit(10).
90
+ order(score).
91
+ where do
92
+ column(:score).gte(1000).lte(3000)
93
+ column(:cheatMode).eq(false)
94
+ end.
95
+ invoke
96
+
97
+ # using Query object through Parse::Object
98
+ results = GameScore.find :limit => 10, :order => 'score',
99
+ :where => proc{
100
+ column(:score).gte(1000).lte(3000)
101
+ column(:cheatMode).eq(false)
102
+ }
103
+ ```
104
+
105
+ To know more about retrieving objects, see spec/parse_query_spec.rb
106
+
107
+ ### Updating Objects
108
+
109
+ To update attributes, just update the attribute and save.
110
+
111
+ ```ruby
112
+ result = GameScore.find 'Ed1nuqPvcm'
113
+ result.score = 73453
114
+ result.save
115
+ ```
116
+
117
+ If you want to update attributes without retrieving the object, you can use
118
+ the Parse::Client object for it.
119
+
120
+ ```ruby
121
+ score = GameScore.new :objectId => 'Ed1nuqPvcm'
122
+ Parse::Client.default_client.update score, :score => 73453
123
+ ```
124
+
125
+ ### Deleting Objects
126
+
127
+ TBD
128
+
129
+ ### Sign up
130
+
131
+ ```ruby
132
+ user = Parse::User.sign_up 'YOUR USERNAME', 'YOUR PASSWORD'
133
+ ```
134
+
135
+ ### Log in
136
+
137
+ ```ruby
138
+ user = Parse::User.log_in 'YOUR USERNAME', 'YOUR PASSWORD'
139
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,161 @@
1
+ # coding:utf-8
2
+ module Parse
3
+ class Client
4
+ API_SERVER = 'api.parse.com'
5
+ API_VERSION = 1
6
+ @@default_client = nil
7
+
8
+ attr_accessor :http_client, :session_token
9
+
10
+ def self.default_client
11
+ @@default_client ||= new
12
+ end
13
+
14
+ def initialize application_id=nil, api_key=nil, http_client=nil
15
+ @application_id = application_id || Parse.application_id
16
+ @api_key = api_key || Parse.api_key
17
+ if @application_id.nil? || @api_key.nil?
18
+ raise ArgumentError.new <<-EOS
19
+ Both Application ID and API Key must be set.
20
+ ex. Parse.credentials application_id: APPLICATION_ID, api_key: API_KEY
21
+ EOS
22
+ end
23
+ @http_client = http_client || Parse::HttpClient.new(API_SERVER)
24
+ end
25
+
26
+ def call_api method, endpoint, body=nil, opt_headers={}, &block
27
+ endpoint = "/#{API_VERSION}/#{endpoint}" unless endpoint[0] == '/'
28
+ headers = {
29
+ 'X-Parse-Application-Id' => @application_id,
30
+ 'X-Parse-REST-API-Key' => @api_key,
31
+ 'Content-Type' => 'application/json'
32
+ }
33
+ headers.update('X-Parse-Session-Token' => @session_token) if @session_token
34
+ headers.update opt_headers
35
+ if body.is_a?(Hash)
36
+ body = Hash[*(body.to_a.map{|k, v| [k, URI.encode(v)]}.flatten)].to_json
37
+ end
38
+ @http_client.request method, endpoint, headers, body, &block
39
+ end
40
+
41
+ def sign_up username, password, opts={}, &block
42
+ call_api :post, 'users', {'username' => username, 'password' => password}.update(opts || {}), &block
43
+ end
44
+
45
+ def log_in username, password, &block
46
+ call_api :get, "login?username=#{URI.encode username}&password=#{
47
+ URI.encode password}", nil, &block
48
+ end
49
+
50
+ def find parse_class, object_id_or_conditions, opts={}
51
+ if object_id_or_conditions.is_a? String
52
+ find_by_id parse_class, object_id_or_conditions, opts
53
+ elsif object_id_or_conditions.is_a? Hash
54
+ find_by_query parse_class, object_id_or_conditions
55
+ end
56
+ end
57
+
58
+ def find_by_id parse_class, object_id, opts={}
59
+ call_api :get, "classes/#{parse_class.parse_class_name}/#{object_id}" do |resp_body|
60
+ convert parse_class, resp_body
61
+
62
+ if opts.has_key? :include
63
+ included_keys = opts[:include]
64
+ included_keys = [included_keys] unless included_keys.is_a? Enumerable
65
+ included_keys.each do |included_key|
66
+ pointer = resp_body[included_key]
67
+ pointer.load
68
+ end
69
+ end
70
+
71
+ parse_class.new resp_body
72
+ end
73
+ end
74
+
75
+ def find_by_query parse_class, conditions
76
+ query = Query.new parse_class
77
+ query.limit conditions[:limit] if conditions.has_key? :limit
78
+ query.skip conditions[:skip] if conditions.has_key? :skip
79
+ query.count conditions[:count] if conditions.has_key? :count
80
+ if conditions.has_key? :order
81
+ order = conditions[:order]
82
+ order = [order] unless order.is_a? Array
83
+ query.order order
84
+ end
85
+ if conditions.has_key? :keys
86
+ keys = conditions[:keys]
87
+ keys = [keys] unless keys.is_a? Array
88
+ query.keys keys
89
+ end
90
+ if conditions.has_key? :include
91
+ include = conditions[:include]
92
+ include = [include] unless include.is_a? Array
93
+ query.include include
94
+ end
95
+ if conditions.has_key? :where
96
+ case condition = conditions[:where]
97
+ when Hash
98
+ query.where condition
99
+ when Proc
100
+ query.where condition
101
+ else
102
+ raise 'wrong condition'
103
+ end
104
+ end
105
+ query.invoke
106
+ end
107
+
108
+ def create parse_object, values
109
+ call_api :post, "classes/#{parse_object.parse_class_name}", values.to_json do |resp_body|
110
+ resp_body
111
+ end
112
+ end
113
+
114
+ def update parse_object, values
115
+ call_api :put, "classes/#{parse_object.parse_class_name}/#{parse_object.object_id}", values.to_json do |resp_body|
116
+ resp_body
117
+ end
118
+ end
119
+
120
+ def delete parse_object
121
+ call_api :delete, "classes/#{parse_object.parse_class_name}/#{parse_object.object_id}" do |resp_body|
122
+ resp_body
123
+ end
124
+ end
125
+
126
+ def call_function name, param
127
+ func_name = Parse.auto_snake_case ? name.to_s.gsub(/_([a-z])/) {$1.upcase} : name
128
+ call_api :post, "functions/#{func_name}", param.to_json do |resp_body|
129
+ if resp_body.has_key? 'result'
130
+ resp_body['result']
131
+ else
132
+ raise StandartError.new 'unknown error'
133
+ end
134
+ end
135
+ end
136
+
137
+ def method_missing name, *args, &block
138
+ call_function name, args.first
139
+ end
140
+
141
+ private
142
+
143
+ def convert parse_class, resp_body
144
+ resp_body.each do |k, v|
145
+ if v.is_a?(Hash) && v.has_key?('__type')
146
+ resp_body[k] = case v['__type']
147
+ when 'Date'
148
+ Date.parse v['iso']
149
+ when 'File'
150
+ Parse::File.new v
151
+ when 'Pointer'
152
+ # TODO: too many arguments
153
+ Parse::Pointer.new self, parse_class, resp_body, k, v
154
+ else
155
+ v
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,2 @@
1
+ module Parse
2
+ end
@@ -0,0 +1,11 @@
1
+ unless String.methods.include? :camelize
2
+ class String
3
+ def camelize uppercase_first_letter=true
4
+ ret = self.gsub(/_(\w)/) do $1.upcase end
5
+ unless uppercase_first_letter == :lower
6
+ ret = ret.capitalize
7
+ end
8
+ ret
9
+ end
10
+ end
11
+ end
data/lib/parse/file.rb ADDED
@@ -0,0 +1,36 @@
1
+ # encoding:utf-8
2
+ module Parse
3
+ class File
4
+ attr_accessor :name, :url
5
+
6
+ def initialize hash
7
+ @raw_hash = hash
8
+ @name = hash['name']
9
+ @url = hash['url']
10
+ end
11
+
12
+ def load &block
13
+ open @url do |data| @data = data.read end unless @data
14
+ block.call @data
15
+ end
16
+
17
+ def store filepath=nil
18
+ filepath ||= @name
19
+ raise 'filepath is mandatory' unless filepath
20
+
21
+ FileUtils.mkdir_p ::File.dirname(filepath)
22
+ load do |data|
23
+ open filepath, 'wb' do |file|
24
+ file.write data
25
+ end
26
+ end
27
+ end
28
+
29
+ def inspect
30
+ data, @data = @data, '..snip..'
31
+ ret = super
32
+ @data = data
33
+ ret
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ # coding:utf-8
2
+ module Parse
3
+ class HttpClient
4
+ def initialize host
5
+ @host = host
6
+ end
7
+
8
+ def request method, endpoint, headers={}, body=nil, &block
9
+ req = eval("Net::HTTP::#{method.to_s.capitalize}").new endpoint, headers
10
+ req.body = body if body
11
+ client = Net::HTTP.new @host, 443
12
+ #client.set_debug_output $stderr
13
+ client.use_ssl = true
14
+ client.start do
15
+ resp = client.request req
16
+ resp_body = JSON.parse resp.body
17
+ raise StandardError.new "error calling #{endpoint}: #{
18
+ resp_body['error']}" if resp_body.has_key? 'error'
19
+ block.call resp_body if block
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,176 @@
1
+ # coding:utf-8
2
+ module Parse
3
+ class Object
4
+ class << self
5
+ attr_accessor :parse_class_name, :parse_client, :auto_camel_case
6
+
7
+ def create parse_class_name, mod=::Object
8
+ raise 'already defined' if mod.const_defined? parse_class_name
9
+
10
+ klass = Class.new(Parse::Object)
11
+ klass.parse_class_name = parse_class_name.to_sym
12
+ klass.auto_camel_case = true
13
+ mod.const_set parse_class_name, klass
14
+ end
15
+
16
+ def parse_class_name
17
+ @parse_class_name || name.split('::').last
18
+ end
19
+
20
+ def parse_client
21
+ @parse_client ||= Parse::Client.default_client
22
+ end
23
+
24
+ def find object_id_or_conditions, opts={}
25
+ parse_client.find self, object_id_or_conditions, opts
26
+ end
27
+ end
28
+
29
+ attr_accessor :obj_id, :created_at, :updated_at, :acl
30
+
31
+ def initialize hash={}
32
+ hash = string_keyed_hash hash
33
+ if hash.has_key? 'objectId'
34
+ @obj_id = hash['objectId']
35
+ @raw_hash = hash
36
+ @updated_hash = {}
37
+ else
38
+ @raw_hash = {}
39
+ @updated_hash = hash
40
+ end
41
+ @deleted = false
42
+ end
43
+
44
+ def new?
45
+ !@deleted && @raw_hash.empty?
46
+ end
47
+
48
+ def updated?
49
+ !@deleted && !@updated_hash.empty?
50
+ end
51
+
52
+ def deleted?
53
+ @deleted
54
+ end
55
+
56
+ def parse_client
57
+ self.class.parse_client
58
+ end
59
+
60
+ def parse_class_name
61
+ self.class.parse_class_name
62
+ end
63
+
64
+ def save hash=@updated_hash
65
+ check_deleted!
66
+ hash = string_keyed_hash hash
67
+ if new?
68
+ create hash
69
+ else
70
+ update hash
71
+ end
72
+ end
73
+
74
+ def create hash
75
+ check_deleted!
76
+ hash = string_keyed_hash hash
77
+ @updated_hash.update hash
78
+ parse_client.create(self, @updated_hash).tap do |response|
79
+ @obj_id = response['objectId']
80
+ @created_at = Date.parse response['createdAt']
81
+ @updated_at = @created_at
82
+ @raw_hash.update @updated_hash
83
+ @raw_hash.update response
84
+ @updated_hash.clear
85
+ end
86
+ end
87
+
88
+ def update hash
89
+ check_deleted!
90
+ hash = string_keyed_hash hash
91
+ parse_client.update(self, hash).tap do |response|
92
+ @updated_at = Date.parse response['updatedAt']
93
+ @raw_hash.update @updated_hash
94
+ @updated_hash.clear
95
+ end
96
+ end
97
+
98
+ def delete
99
+ raise 'You cannot delete new object' if new?
100
+ check_deleted!
101
+ parse_client.delete(self).tap do |response|
102
+ @deleted = true
103
+ end
104
+ end
105
+
106
+ def obj_id
107
+ @obj_id || @raw_hash['objectId']
108
+ end
109
+
110
+ def get_column name
111
+ name = name.to_s
112
+ ret = @updated_hash[name]
113
+ if ret.nil? && self.class.auto_camel_case
114
+ ret = @updated_hash[name.camelize :lower]
115
+ end
116
+ if ret.nil?
117
+ ret = @raw_hash[name]
118
+ if ret.nil? && self.class.auto_camel_case
119
+ ret = @raw_hash[name.camelize :lower]
120
+ end
121
+ end
122
+ ret
123
+ end
124
+
125
+ def set_column name, value
126
+ check_deleted!
127
+ @updated_hash[name] = value
128
+ end
129
+
130
+ def to_s
131
+ "<#{parse_class_name}: #{{}.update(@raw_hash).update(@updated_hash).to_s}>"
132
+ end
133
+
134
+ def method_missing name, *args, &block
135
+ if name =~ /^\w+$/ && args.empty? && block.nil?
136
+ get_column name
137
+ elsif name[-1] == '=' && args.size == 1 && block.nil?
138
+ set_column name[0..-2], args.first
139
+ else
140
+ super
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def string_keyed_hash hash
147
+ new_hash = {}
148
+ hash.each do |k, v|
149
+ new_hash[k.to_s] = v
150
+ end
151
+ new_hash
152
+ end
153
+
154
+ def check_deleted!
155
+ raise 'This object has already been deleted.' if deleted?
156
+ end
157
+ end
158
+
159
+ #
160
+ # create or get ParseObject class in the given module
161
+ #
162
+ # == Parameters:
163
+ # parse_class_name::
164
+ # Parse class name
165
+ # mod::
166
+ # module where ParseObject is populated
167
+ #
168
+ # == Returns:
169
+ # subclass of ParseObject for the given parse_class_name
170
+ #
171
+ def self.Object parse_class_name, mod=::Object
172
+ Parse::Object.create parse_class_name, mod \
173
+ unless mod.const_defined? parse_class_name
174
+ mod.const_get parse_class_name
175
+ end
176
+ end
@@ -0,0 +1,25 @@
1
+ # coding:utf-8
2
+ module Parse
3
+ class Pointer
4
+ def initialize parse_client, parent_parse_class, parent_hash, parent_key, hash
5
+ @parse_client = parse_client
6
+ @parent_parse_class = parent_parse_class
7
+ @parent_hash = parent_hash
8
+ @parent_key = parent_key
9
+ @raw_hash = hash
10
+ end
11
+
12
+ def load &block
13
+ included_parse_class_name = @raw_hash['className']
14
+ mod = @parent_parse_class.name.include?('::') ? \
15
+ eval(@perent_parse_class.name.split('::')[0..-2]) : ::Object
16
+ included_parse_class = Parse::Object included_parse_class_name, mod
17
+ @parent_hash[@parent_key] = @parse_client.find(
18
+ included_parse_class, @raw_hash['objectId']).tap do |real_value|
19
+ if block
20
+ block.call real_value
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end