parse-ruby-client 0.1.15 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ module Parse
2
+ class HttpClient
3
+ class TimeoutError < StandardError; end
4
+
5
+ attr_accessor :base_url, :headers
6
+
7
+ def initialize(base_url=nil, headers = {})
8
+ @base_url = base_url
9
+ @headers = headers
10
+ end
11
+
12
+ def build_query(hash)
13
+ hash.to_a.map{|k, v| "#{k}=#{v}"}.join('&')
14
+ end
15
+
16
+ def request(method, uri, headers, options)
17
+ NotImplementedError.new("Subclass responsibility")
18
+ end
19
+ end
20
+
21
+ class NetHttpClient < HttpClient
22
+ class NetHttpResponseWrapper
23
+ def initialize(response) @response = response end
24
+ def status() @response.code.to_i end
25
+ def body() @response.read_body end
26
+ end
27
+
28
+ def request(method, uri, headers, options)
29
+ request_class = eval("Net::HTTP::#{method.to_s.capitalize}")
30
+ uri = "#{uri}?#{options[:query]}" if options[:query]
31
+ request = request_class.new(uri, @headers.dup.update(headers))
32
+ request.body = options[:data] if options.has_key?(:data)
33
+ NetHttpResponseWrapper.new(
34
+ @client.start do
35
+ @client.request(request)
36
+ end
37
+ )
38
+ end
39
+
40
+ def base_url=(url)
41
+ @base_url = url
42
+ @client = Net::HTTP.new(@base_url.sub('https://', ''), 443)
43
+ @client.use_ssl = true
44
+ end
45
+ end
46
+
47
+ class PatronHttpClient < HttpClient
48
+ def initialize(base_url=nil, headers = {})
49
+ super
50
+ @session = Patron::Session.new
51
+ @session.timeout = 30
52
+ @session.connect_timeout = 30
53
+ @session.headers.update(@headers)
54
+ end
55
+
56
+ def build_query(hash)
57
+ Patron::Util.build_query_pairs_from_hash(hash).join('&')
58
+ end
59
+
60
+ def request(method, uri, headers, options)
61
+ @session.request(method, uri, headers, options)
62
+ rescue Patron::TimeoutError => e
63
+ raise HttpClient::TimeoutError.new(e)
64
+ end
65
+
66
+ def base_url
67
+ @session.base_url
68
+ end
69
+
70
+ def base_url=(url)
71
+ @session.base_url = url
72
+ end
73
+
74
+ def headers
75
+ @session.headers
76
+ end
77
+
78
+ def headers=(hash)
79
+ @session.headers = hash
80
+ end
81
+ end
82
+
83
+ DEFAULT_HTTP_CLIENT = defined?(JRUBY_VERSION) ? NetHttpClient : PatronHttpClient
84
+ end
data/lib/parse/object.rb CHANGED
@@ -35,7 +35,7 @@ module Parse
35
35
  end
36
36
 
37
37
  def pointer
38
- Parse::Pointer.new(self.merge(Parse::Protocol::KEY_CLASS_NAME => class_name)) unless new?
38
+ Parse::Pointer.new(rest_api_hash) unless new?
39
39
  end
40
40
 
41
41
  # make it easier to deal with the ambiguity of whether you're passed a pointer or object
@@ -43,68 +43,10 @@ module Parse
43
43
  self
44
44
  end
45
45
 
46
- # Merge a hash parsed from the JSON representation into
47
- # this instance. This will extract the reserved fields,
48
- # merge the hash keys, and then ensure that the reserved
49
- # fields do not occur in the underlying hash storage.
50
- def parse(data)
51
- if !data
52
- return
53
- end
54
-
55
- @parse_object_id ||= data[Protocol::KEY_OBJECT_ID]
56
-
57
- if data.has_key? Protocol::KEY_CREATED_AT
58
- @created_at = DateTime.parse data[Protocol::KEY_CREATED_AT]
59
- end
60
-
61
- if data.has_key? Protocol::KEY_UPDATED_AT
62
- @updated_at = DateTime.parse data[Protocol::KEY_UPDATED_AT]
63
- end
64
-
65
- data.each do |k,v|
66
- if k.is_a? Symbol
67
- k = k.to_s
68
- end
69
-
70
- if k != Parse::Protocol::KEY_TYPE
71
- self[k] = v
72
- end
73
- end
74
-
75
- self
76
- end
77
-
78
46
  def new?
79
47
  self["objectId"].nil?
80
48
  end
81
49
 
82
- def safe_hash
83
- without_reserved = self.dup
84
- Protocol::RESERVED_KEYS.each { |k| without_reserved.delete(k) }
85
-
86
- without_relations = without_reserved
87
- without_relations.each do |k,v|
88
- if v.is_a? Hash
89
- if v[Protocol::KEY_TYPE] == Protocol::TYPE_RELATION
90
- without_relations.delete(k)
91
- end
92
- end
93
- end
94
-
95
- without_relations.each do |k, v|
96
- without_relations[k] = Parse.pointerize_value(v)
97
- end
98
-
99
- without_relations
100
- end
101
-
102
- def safe_json
103
- safe_hash.to_json
104
- end
105
-
106
- private :parse
107
-
108
50
  # Write the current state of the local object to the API.
109
51
  # If the object has never been saved before, this will create
110
52
  # a new object, otherwise it will update the existing stored object.
@@ -116,7 +58,7 @@ module Parse
116
58
  method = :post
117
59
  end
118
60
 
119
- body = safe_json
61
+ body = safe_hash.to_json
120
62
  data = Parse.client.request(self.uri, method, body)
121
63
 
122
64
  if data
@@ -133,20 +75,36 @@ module Parse
133
75
  self
134
76
  end
135
77
 
136
- def as_json(*a)
78
+ # representation of object to send on saves
79
+ def safe_hash
137
80
  Hash[self.map do |key, value|
138
- value = if !value.nil?
139
- value.respond_to?(:as_json) ? value.as_json : value
81
+ if Protocol::RESERVED_KEYS.include?(key)
82
+ nil
83
+ elsif value.is_a?(Hash) && value[Protocol::KEY_TYPE] == Protocol::TYPE_RELATION
84
+ nil
85
+ elsif value.nil?
86
+ [key, Protocol::DELETE_OP]
140
87
  else
141
- Protocol::DELETE_OP
88
+ [key, Parse.pointerize_value(value)]
142
89
  end
90
+ end.compact]
91
+ end
143
92
 
144
- [key, value]
93
+ # full REST api representation of object
94
+ def rest_api_hash
95
+ self.merge(Parse::Protocol::KEY_CLASS_NAME => class_name)
96
+ end
97
+
98
+ def to_h(*a)
99
+ Hash[rest_api_hash.map do |key, value|
100
+ [key, value.respond_to?(:to_h) ? value.to_h : value]
145
101
  end]
146
102
  end
103
+ alias :as_json :to_h
104
+ alias :to_hash :to_h
147
105
 
148
106
  def to_json(*a)
149
- as_json.to_json(*a)
107
+ to_h.to_json(*a)
150
108
  end
151
109
 
152
110
  def to_s
@@ -220,6 +178,38 @@ module Parse
220
178
 
221
179
  private
222
180
 
181
+ # Merge a hash parsed from the JSON representation into
182
+ # this instance. This will extract the reserved fields,
183
+ # merge the hash keys, and then ensure that the reserved
184
+ # fields do not occur in the underlying hash storage.
185
+ def parse(data)
186
+ if !data
187
+ return
188
+ end
189
+
190
+ @parse_object_id ||= data[Protocol::KEY_OBJECT_ID]
191
+
192
+ if data.has_key? Protocol::KEY_CREATED_AT
193
+ @created_at = DateTime.parse data[Protocol::KEY_CREATED_AT]
194
+ end
195
+
196
+ if data.has_key? Protocol::KEY_UPDATED_AT
197
+ @updated_at = DateTime.parse data[Protocol::KEY_UPDATED_AT]
198
+ end
199
+
200
+ data.each do |k,v|
201
+ if k.is_a? Symbol
202
+ k = k.to_s
203
+ end
204
+
205
+ if k != Parse::Protocol::KEY_TYPE
206
+ self[k] = v
207
+ end
208
+ end
209
+
210
+ self
211
+ end
212
+
223
213
  def array_op(field, operation, value)
224
214
  raise "field #{field} not an array" if self[field] && !self[field].is_a?(Array)
225
215
 
data/lib/parse/query.rb CHANGED
@@ -125,7 +125,17 @@ module Parse
125
125
  [:count, :limit, :skip, :include].each {|a| merge_attribute(a, query)}
126
126
  Parse.client.logger.info{"Parse query for #{uri} #{CGI.unescape(query.inspect)}"}
127
127
  response = Parse.client.request uri, :get, nil, query
128
- Parse.parse_json class_name, response
128
+
129
+ if response.is_a?(Hash) && response.has_key?(Protocol::KEY_RESULTS) && response[Protocol::KEY_RESULTS].is_a?(Array)
130
+ parsed_results = response[Protocol::KEY_RESULTS].map{|o| Parse.parse_json(class_name, o)}
131
+ if response.keys.size == 1
132
+ parsed_results
133
+ else
134
+ response.dup.merge(Protocol::KEY_RESULTS => parsed_results)
135
+ end
136
+ else
137
+ raise ParseError.new("query response not a Hash with #{Protocol::KEY_RESULTS} key: #{response.class} #{response.inspect}")
138
+ end
129
139
  end
130
140
 
131
141
  private
data/lib/parse/util.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Parse
2
2
 
3
3
  # Parse a JSON representation into a fully instantiated
4
- # class. obj can be either a string or a Hash as parsed
4
+ # class. obj can be either a primitive or a Hash of primitives as parsed
5
5
  # by JSON.parse
6
6
  # @param class_name [Object]
7
7
  # @param obj [Object]
@@ -19,8 +19,6 @@ module Parse
19
19
  # If it's a datatype hash
20
20
  if obj.has_key?(Protocol::KEY_TYPE)
21
21
  parse_datatype obj
22
- elsif obj.size == 1 && obj.has_key?(Protocol::KEY_RESULTS) && obj[Protocol::KEY_RESULTS].is_a?(Array)
23
- obj[Protocol::KEY_RESULTS].collect { |o| parse_json(class_name, o) }
24
22
  elsif class_name # otherwise it must be a regular object, so deep parse it avoiding re-JSON.parsing raw Strings
25
23
  Parse::Object.new class_name, Hash[obj.map{|k,v| [k, parse_json(nil, v)]}]
26
24
  else # plain old hash
@@ -54,7 +52,9 @@ module Parse
54
52
 
55
53
  def Parse.pointerize_value(obj)
56
54
  if obj.kind_of?(Parse::Object)
57
- obj.pointer
55
+ p = obj.pointer
56
+ raise ArgumentError.new("new object used in context requiring pointer #{obj}") unless p
57
+ p
58
58
  elsif obj.is_a?(Array)
59
59
  obj.map do |v|
60
60
  Parse.pointerize_value(v)
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "parse-ruby-client"
8
- s.version = "0.1.15"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Alan deLevie", "Adam Alpern"]
12
- s.date = "2013-05-22"
12
+ s.date = "2013-10-08"
13
13
  s.description = "A simple Ruby client for the parse.com REST API"
14
14
  s.email = "adelevie@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -34,8 +34,8 @@ Gem::Specification.new do |s|
34
34
  "fixtures/vcr_cassettes/test_batch_create_object.yml",
35
35
  "fixtures/vcr_cassettes/test_batch_delete_object.yml",
36
36
  "fixtures/vcr_cassettes/test_batch_run.yml",
37
+ "fixtures/vcr_cassettes/test_batch_update_nils_delete_keys.yml",
37
38
  "fixtures/vcr_cassettes/test_batch_update_object.yml",
38
- "fixtures/vcr_cassettes/test_circular_save.yml",
39
39
  "fixtures/vcr_cassettes/test_created_at.yml",
40
40
  "fixtures/vcr_cassettes/test_decrement.yml",
41
41
  "fixtures/vcr_cassettes/test_deep_parse.yml",
@@ -52,18 +52,21 @@ Gem::Specification.new do |s|
52
52
  "fixtures/vcr_cassettes/test_pointer.yml",
53
53
  "fixtures/vcr_cassettes/test_save_with_sub_objects.yml",
54
54
  "fixtures/vcr_cassettes/test_saving_boolean_values.yml",
55
+ "fixtures/vcr_cassettes/test_saving_nested_objects.yml",
55
56
  "fixtures/vcr_cassettes/test_server_update.yml",
56
57
  "fixtures/vcr_cassettes/test_simple_save.yml",
57
58
  "fixtures/vcr_cassettes/test_text_file_save.yml",
58
59
  "fixtures/vcr_cassettes/test_update.yml",
59
60
  "fixtures/vcr_cassettes/test_updated_at.yml",
60
61
  "fixtures/vcr_cassettes/test_user_save.yml",
62
+ "fixtures/vcr_cassettes/test_xget.yml",
61
63
  "lib/parse-ruby-client.rb",
62
64
  "lib/parse/batch.rb",
63
65
  "lib/parse/client.rb",
64
66
  "lib/parse/cloud.rb",
65
67
  "lib/parse/datatypes.rb",
66
68
  "lib/parse/error.rb",
69
+ "lib/parse/http_client.rb",
67
70
  "lib/parse/model.rb",
68
71
  "lib/parse/object.rb",
69
72
  "lib/parse/protocol.rb",
@@ -106,7 +109,7 @@ Gem::Specification.new do |s|
106
109
  s.add_development_dependency(%q<shoulda>, [">= 0"])
107
110
  s.add_development_dependency(%q<test-unit>, ["= 2.5.0"])
108
111
  s.add_development_dependency(%q<mocha>, ["= 0.12.0"])
109
- s.add_development_dependency(%q<jeweler>, [">= 0"])
112
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.5"])
110
113
  s.add_development_dependency(%q<simplecov>, [">= 0"])
111
114
  s.add_development_dependency(%q<webmock>, [">= 0"])
112
115
  s.add_development_dependency(%q<vcr>, [">= 0"])
@@ -117,7 +120,7 @@ Gem::Specification.new do |s|
117
120
  s.add_dependency(%q<shoulda>, [">= 0"])
118
121
  s.add_dependency(%q<test-unit>, ["= 2.5.0"])
119
122
  s.add_dependency(%q<mocha>, ["= 0.12.0"])
120
- s.add_dependency(%q<jeweler>, [">= 0"])
123
+ s.add_dependency(%q<jeweler>, ["~> 1.8.5"])
121
124
  s.add_dependency(%q<simplecov>, [">= 0"])
122
125
  s.add_dependency(%q<webmock>, [">= 0"])
123
126
  s.add_dependency(%q<vcr>, [">= 0"])
@@ -129,7 +132,7 @@ Gem::Specification.new do |s|
129
132
  s.add_dependency(%q<shoulda>, [">= 0"])
130
133
  s.add_dependency(%q<test-unit>, ["= 2.5.0"])
131
134
  s.add_dependency(%q<mocha>, ["= 0.12.0"])
132
- s.add_dependency(%q<jeweler>, [">= 0"])
135
+ s.add_dependency(%q<jeweler>, ["~> 1.8.5"])
133
136
  s.add_dependency(%q<simplecov>, [">= 0"])
134
137
  s.add_dependency(%q<webmock>, [">= 0"])
135
138
  s.add_dependency(%q<vcr>, [">= 0"])
data/test/test_batch.rb CHANGED
@@ -5,6 +5,11 @@ class TestBatch < ParseTestCase
5
5
  def test_initialize
6
6
  batch = Parse::Batch.new
7
7
  assert_equal batch.class, Parse::Batch
8
+ assert_equal Parse.client, batch.client
9
+
10
+ batch = Parse::Batch.new(Parse::Client.new)
11
+ assert_equal batch.class, Parse::Batch
12
+ assert_not_equal Parse.client, batch.client
8
13
  end
9
14
 
10
15
  def test_add_request
@@ -91,6 +96,21 @@ class TestBatch < ParseTestCase
91
96
  end
92
97
  end
93
98
 
99
+ def test_update_nils_delete_keys
100
+ VCR.use_cassette('test_batch_update_nils_delete_keys', :record => :new_episodes) do
101
+ post = Parse::Object.new("BatchTestObject")
102
+ post["foo"] = "1"
103
+ post.save
104
+
105
+ post["foo"] = nil
106
+ batch = Parse::Batch.new
107
+ batch.update_object(post)
108
+ batch.run!
109
+
110
+ assert_false post.refresh.keys.include?("foo")
111
+ end
112
+ end
113
+
94
114
  def test_delete_object
95
115
  VCR.use_cassette('test_batch_delete_object', :record => :new_episodes) do
96
116
  objects = [1, 2, 3, 4, 5].map do |i|
@@ -18,14 +18,13 @@ class TestDatatypes < Test::Unit::TestCase
18
18
  end
19
19
 
20
20
  def test_date
21
- date_time = DateTime.now
22
- data = date_time
23
- parse_date = Parse::Date.new data
21
+ date_time = Time.at(0).to_datetime
22
+ parse_date = Parse::Date.new(date_time)
24
23
 
25
- assert_equal parse_date.value, date_time
26
- assert_equal JSON.parse(parse_date.to_json)["iso"], date_time.iso8601(3)
24
+ assert_equal date_time, parse_date.value
25
+ assert_equal "1970-01-01T00:00:00.000Z", JSON.parse(parse_date.to_json)["iso"]
27
26
  assert_equal 0, parse_date <=> parse_date
28
- assert_equal 0, Parse::Date.new(data) <=> Parse::Date.new(data)
27
+ assert_equal 0, Parse::Date.new(date_time) <=> Parse::Date.new(date_time)
29
28
 
30
29
  post = Parse::Object.new("Post")
31
30
  post["time"] = parse_date
data/test/test_object.rb CHANGED
@@ -143,11 +143,19 @@ class TestObject < ParseTestCase
143
143
  end
144
144
  end
145
145
 
146
+ def test_saving_nested_objects
147
+ VCR.use_cassette('test_saving_nested_objects', :record => :new_episodes) do
148
+ post = Parse::Object.new "Post"
149
+ post["comment"] = Parse::Object.new("Comment", "text" => "testing")
150
+ assert_raise{post.save}
151
+ end
152
+ end
153
+
146
154
  def test_boolean_values_as_json
147
155
  post = Parse::Object.new "Post"
148
156
  post["read"] = false
149
157
  post["published"] = true
150
- safe_json_hash = JSON.parse post.safe_json
158
+ safe_json_hash = JSON.parse post.safe_hash.to_json
151
159
  assert_equal false, safe_json_hash["read"]
152
160
  assert_equal true, safe_json_hash["published"]
153
161
  end
@@ -269,17 +277,4 @@ class TestObject < ParseTestCase
269
277
  assert_equal 'baz', bar['baz']
270
278
  end
271
279
  end
272
-
273
- def test_circular_save
274
- VCR.use_cassette('test_circular_save', :record => :new_episodes) do
275
- bar = Parse::Object.new("CircularBar", "text" => "bar")
276
- bar_2 = Parse::Object.new("CircularBar", "bar" => bar, "text" => "bar_2")
277
- bar_2.save
278
- bar['bar'] = bar_2
279
- assert bar.save
280
-
281
- assert_equal "bar_2", bar["bar"]["text"]
282
- assert_equal "bar", bar["bar"]["bar"]["text"]
283
- end
284
- end
285
280
  end