parsecom 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/.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