diff --git a/README.md b/README.md index 4fb7015..c55f51f 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ game_score["score"] = Parse::Increment.new(1) game_score.save ``` -You can also use `Parse::Decrement.new(amount)`. +You can also use a negative amount to decrement. #### Arrays @@ -247,7 +247,7 @@ You can delete a single field from an object by using the `Parse::Object#delete_ To reduce the amount of time spent on network round trips, you can create, update, or delete several objects in one call, using the batch endpoint. -parse-ruby-client provides a "manual" way to construct Batch Operations, as well as some convenience methods. The commands are run in the order they are given. For example, to create a couple of GameScore objects using the "manual" style, use `Parse::Batch#add_request`. `#add_request` takes a `Hash` with `"method"`, `"path"`, and `"body"` keys that specify the HTTP command that would normally be used for that command. +parse-ruby-client provides a "manual" way to construct Batch Operations, as well as some convenience methods. The commands are run in the order they are given. For example, to create a couple of GameScore objects using the "manual" style, use `Parse::Batch#add_request`. `#add_request` takes a `Hash` with `"method"`, `"path"`, and `"body"` keys that specify the HTTP command that would normally be used for that command. ```ruby batch = Parse::Batch.new @@ -458,7 +458,7 @@ Other constraint methods include: `Parse::Query#select` TODO: `$select` not yet implemented. This matches a value for a key in the result of a different query - + For example, to retrieve scores between 1000 and 3000, including the endpoints, we could issue: @@ -704,7 +704,7 @@ To sign up a new user, create a new `Parse::User` object and then call `#save` o ```ruby user = Parse::User.new({ - :username => "cooldude6", + :username => "cooldude6", :password => "p_n7!-e8", :phone => "415-392-0202" }) @@ -828,7 +828,7 @@ All of the options for queries that work for regular objects also work for user ### Deleting Users -TODO: Implement this! +TODO: Implement this! Proposed api: @@ -1014,8 +1014,8 @@ To upload a file to Parse, use `Parse::File`. You must include the `"Content-Typ ```ruby file = Parse::File.new({ - :body => "Hello World!", - :local_filename => "hello.txt", + :body => "Hello World!", + :local_filename => "hello.txt", :content_type => "text/plain" }) file.save @@ -1033,8 +1033,8 @@ To upload an image, the syntax is a little bit different. Here's an example that ```ruby photo = Parse::File.new({ - :body => IO.read("test/parsers.jpg"), - :local_filename => "parsers.jpg", + :body => IO.read("test/parsers.jpg"), + :local_filename => "parsers.jpg", :content_type => "image/jpeg" }) photo.save @@ -1046,8 +1046,8 @@ After files are uploaded, you can associate them with Parse objects: ```ruby photo = Parse::File.new({ - :body => IO.read("test/parsers.jpg"), - :local_filename => "parsers.jpg", + :body => IO.read("test/parsers.jpg"), + :local_filename => "parsers.jpg", :content_type => "image/jpeg" }) photo.save diff --git a/features.md b/features.md index cdb32e3..652a9b0 100644 --- a/features.md +++ b/features.md @@ -179,7 +179,7 @@ game_score["score"] = Parse::Increment.new(1) game_score.save ``` -You can also use `Parse::Decrement.new(amount)`. +Use a negative amount to decrement. #### Arrays @@ -243,7 +243,7 @@ You can delete a single field from an object by using the `Parse::Object#delete_ To reduce the amount of time spent on network round trips, you can create, update, or delete several objects in one call, using the batch endpoint. -parse-ruby-client provides a "manual" way to construct Batch Operations, as well as some convenience methods. The commands are run in the order they are given. For example, to create a couple of GameScore objects using the "manual" style, use `Parse::Batch#add_request`. `#add_request` takes a `Hash` with `"method"`, `"path"`, and `"body"` keys that specify the HTTP command that would normally be used for that command. +parse-ruby-client provides a "manual" way to construct Batch Operations, as well as some convenience methods. The commands are run in the order they are given. For example, to create a couple of GameScore objects using the "manual" style, use `Parse::Batch#add_request`. `#add_request` takes a `Hash` with `"method"`, `"path"`, and `"body"` keys that specify the HTTP command that would normally be used for that command. ```ruby batch = Parse::Batch.new @@ -454,7 +454,7 @@ Other constraint methods include: `Parse::Query#select` TODO: `$select` not yet implemented. This matches a value for a key in the result of a different query - + For example, to retrieve scores between 1000 and 3000, including the endpoints, we could issue: @@ -700,7 +700,7 @@ To sign up a new user, create a new `Parse::User` object and then call `#save` o ```ruby user = Parse::User.new({ - :username => "cooldude6", + :username => "cooldude6", :password => "p_n7!-e8", :phone => "415-392-0202" }) @@ -824,7 +824,7 @@ All of the options for queries that work for regular objects also work for user ### Deleting Users -TODO: Implement this! +TODO: Implement this! Proposed api: @@ -1010,8 +1010,8 @@ To upload a file to Parse, use `Parse::File`. You must include the `"Content-Typ ```ruby file = Parse::File.new({ - :body => "Hello World!", - :local_filename => "hello.txt", + :body => "Hello World!", + :local_filename => "hello.txt", :content_type => "text/plain" }) file.save @@ -1029,8 +1029,8 @@ To upload an image, the syntax is a little bit different. Here's an example that ```ruby photo = Parse::File.new({ - :body => IO.read("test/parsers.jpg"), - :local_filename => "parsers.jpg", + :body => IO.read("test/parsers.jpg"), + :local_filename => "parsers.jpg", :content_type => "image/jpeg" }) photo.save @@ -1042,8 +1042,8 @@ After files are uploaded, you can associate them with Parse objects: ```ruby photo = Parse::File.new({ - :body => IO.read("test/parsers.jpg"), - :local_filename => "parsers.jpg", + :body => IO.read("test/parsers.jpg"), + :local_filename => "parsers.jpg", :content_type => "image/jpeg" }) photo.save diff --git a/fixtures/vcr_cassettes/test_array_add_unique.yml b/fixtures/vcr_cassettes/test_array_add_unique.yml new file mode 100644 index 0000000..2d2e4ff --- /dev/null +++ b/fixtures/vcr_cassettes/test_array_add_unique.yml @@ -0,0 +1,180 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.parse.com/1/classes/Post + body: + encoding: UTF-8 + string: '{}' + headers: + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - Parse for Ruby, 0.0 + X-Parse-Master-Key: + - '' + X-Parse-Rest-Api-Key: + - + X-Parse-Application-Id: + - + X-Parse-Session-Token: + - '' + Expect: + - '' + response: + status: + code: 201 + message: Created + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Request-Method: + - '*' + Cache-Control: + - no-cache + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 21 May 2013 17:44:02 GMT + Location: + - https://api.parse.com/1/classes/Post/SNcYuo3t5K + Server: + - nginx/1.2.2 + Set-Cookie: + - + Status: + - 201 Created + X-Runtime: + - '0.289539' + X-Ua-Compatible: + - IE=Edge,chrome=1 + Content-Length: + - '64' + Connection: + - keep-alive + body: + encoding: ASCII-8BIT + string: '{"createdAt":"2013-05-21T17:44:02.504Z","objectId":"SNcYuo3t5K"}' + http_version: + recorded_at: Tue, 21 May 2013 17:44:02 GMT +- request: + method: post + uri: https://api.parse.com/1/classes/Comment + body: + encoding: UTF-8 + string: '{"text":"great post!"}' + headers: + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - Parse for Ruby, 0.0 + X-Parse-Master-Key: + - '' + X-Parse-Rest-Api-Key: + - + X-Parse-Application-Id: + - + X-Parse-Session-Token: + - '' + Expect: + - '' + response: + status: + code: 201 + message: Created + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Request-Method: + - '*' + Cache-Control: + - no-cache + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 21 May 2013 17:44:02 GMT + Location: + - https://api.parse.com/1/classes/Comment/lJjuzuDd6H + Server: + - nginx/1.2.2 + Set-Cookie: + - + Status: + - 201 Created + X-Runtime: + - '0.107052' + X-Ua-Compatible: + - IE=Edge,chrome=1 + Content-Length: + - '64' + Connection: + - keep-alive + body: + encoding: ASCII-8BIT + string: '{"createdAt":"2013-05-21T17:44:02.601Z","objectId":"lJjuzuDd6H"}' + http_version: + recorded_at: Tue, 21 May 2013 17:44:02 GMT +- request: + method: put + uri: https://api.parse.com/1/classes/Post/SNcYuo3t5K + body: + encoding: UTF-8 + string: '{"comments":{"__op":"AddUnique","objects":[{"__type":"Pointer","className":"Comment","objectId":"lJjuzuDd6H"}]}}' + headers: + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - Parse for Ruby, 0.0 + X-Parse-Master-Key: + - '' + X-Parse-Rest-Api-Key: + - + X-Parse-Application-Id: + - + X-Parse-Session-Token: + - '' + Expect: + - '' + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Request-Method: + - '*' + Cache-Control: + - max-age=0, private, must-revalidate + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 21 May 2013 17:44:02 GMT + Etag: + - '"08c4a7d37dd1d79503cdfba1d649a71b"' + Server: + - nginx/1.2.2 + Set-Cookie: + - + Status: + - 200 OK + X-Runtime: + - '0.109572' + X-Ua-Compatible: + - IE=Edge,chrome=1 + Content-Length: + - '120' + Connection: + - keep-alive + body: + encoding: ASCII-8BIT + string: '{"comments":[{"__type":"Pointer","className":"Comment","objectId":"lJjuzuDd6H"}],"updatedAt":"2013-05-21T17:44:02.779Z"}' + http_version: + recorded_at: Tue, 21 May 2013 17:44:02 GMT +recorded_with: VCR 2.4.0 diff --git a/fixtures/vcr_cassettes/test_decrement.yml b/fixtures/vcr_cassettes/test_decrement.yml new file mode 100644 index 0000000..679ebcf --- /dev/null +++ b/fixtures/vcr_cassettes/test_decrement.yml @@ -0,0 +1,121 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.parse.com/1/classes/Post + body: + encoding: UTF-8 + string: '{"count":1}' + headers: + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - Parse for Ruby, 0.0 + X-Parse-Master-Key: + - '' + X-Parse-Rest-Api-Key: + - + X-Parse-Application-Id: + - + X-Parse-Session-Token: + - '' + Expect: + - '' + response: + status: + code: 201 + message: Created + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Request-Method: + - '*' + Cache-Control: + - no-cache + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 21 May 2013 17:44:02 GMT + Location: + - https://api.parse.com/1/classes/Post/mbi9hgIA8z + Server: + - nginx/1.2.2 + Set-Cookie: + - + Status: + - 201 Created + X-Runtime: + - '0.036852' + X-Ua-Compatible: + - IE=Edge,chrome=1 + Content-Length: + - '64' + Connection: + - keep-alive + body: + encoding: ASCII-8BIT + string: '{"createdAt":"2013-05-21T17:44:02.950Z","objectId":"mbi9hgIA8z"}' + http_version: + recorded_at: Tue, 21 May 2013 17:44:02 GMT +- request: + method: put + uri: https://api.parse.com/1/classes/Post/mbi9hgIA8z + body: + encoding: UTF-8 + string: '{"count":{"__op":"Increment","amount":-1}}' + headers: + Content-Type: + - application/json + Accept: + - application/json + User-Agent: + - Parse for Ruby, 0.0 + X-Parse-Master-Key: + - '' + X-Parse-Rest-Api-Key: + - + X-Parse-Application-Id: + - + X-Parse-Session-Token: + - '' + Expect: + - '' + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Request-Method: + - '*' + Cache-Control: + - max-age=0, private, must-revalidate + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 21 May 2013 17:44:03 GMT + Etag: + - '"801ca6262d69a0dcff8d69e7f078aaca"' + Server: + - nginx/1.2.2 + Set-Cookie: + - + Status: + - 200 OK + X-Runtime: + - '0.104988' + X-Ua-Compatible: + - IE=Edge,chrome=1 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + body: + encoding: ASCII-8BIT + string: '{"count":0,"updatedAt":"2013-05-21T17:44:03.131Z"}' + http_version: + recorded_at: Tue, 21 May 2013 17:44:03 GMT +recorded_with: VCR 2.4.0 diff --git a/lib/parse/datatypes.rb b/lib/parse/datatypes.rb index 7a89853..d31de0a 100644 --- a/lib/parse/datatypes.rb +++ b/lib/parse/datatypes.rb @@ -202,37 +202,6 @@ def to_json(*a) end end - class Decrement - # '{"score": {"__op": "Decrement", "amount": 1 } }' - attr_accessor :amount - - def initialize(amount) - @amount = amount - end - - def eql?(other) - self.class.equal?(other.class) && - amount == other.amount - end - - alias == eql? - - def hash - amount.hash - end - - def as_json(*a) - { - Protocol::KEY_OP => Protocol::KEY_DECREMENT, - Protocol::KEY_AMOUNT => @amount - } - end - - def to_json(*a) - as_json.to_json(*a) - end - end - class ArrayOp # '{"myArray": {"__op": "Add", "objects": ["something", "something else"] } }' attr_accessor :operation diff --git a/lib/parse/object.rb b/lib/parse/object.rb index d1f753f..a981615 100644 --- a/lib/parse/object.rb +++ b/lib/parse/object.rb @@ -116,17 +116,14 @@ def save method = :post end - object_store = Parse.store_objects_by_pointer(self) - body = safe_json data = Parse.client.request(self.uri, method, body) if data - parse data + # array operations can return mutated view of array which needs to be parsed + parse Parse.parse_json(class_name, data) end - Parse.restore_objects!(self, object_store) - if @class_name == Parse::Protocol::CLASS_USER self.delete("password") self.delete(:username) @@ -209,13 +206,6 @@ def increment(field, amount = 1) # return nil #end - #if amount != 0 - # op = amount > 0 ? Protocol::OP_INCREMENT : Protocol::OP_DECREMENT - # body = "{\"#{field}\": {\"#{Protocol::KEY_OP}\": \"#{op}\", \"#{Protocol::KEY_AMOUNT}\" : #{amount.abs}}}" - # data = Parse.client.request( self.uri, :put, body) - # parse data - #end - #self body = {field => Parse::Increment.new(amount)}.to_json data = Parse.client.request(self.uri, :put, body) parse data @@ -225,11 +215,7 @@ def increment(field, amount = 1) # Decrement the given field by an amount, which defaults to 1. Saves immediately to reflect decremented # A synonym for increment(field, -amount). def decrement(field, amount = 1) - #increment field, -amount - body = {field => Parse::Decrement.new(amount)}.to_json - data = Parse.client.request(self.uri, :put, body) - parse data - self + increment(field, -amount) end private diff --git a/lib/parse/protocol.rb b/lib/parse/protocol.rb index 9c62221..83d9340 100644 --- a/lib/parse/protocol.rb +++ b/lib/parse/protocol.rb @@ -59,7 +59,6 @@ module Protocol KEY_OP = "__op" KEY_INCREMENT = "Increment" - KEY_DECREMENT = "Decrement" KEY_DELETE = "Delete" # array ops @@ -86,9 +85,6 @@ module Protocol # Operation name for incrementing an objects field value remotely OP_INCREMENT = "Increment" - # Operation name for decrementing an objects field value remotely - OP_DECREMENT = "Decrement" - # The data type name for special JSON objects representing a full object TYPE_OBJECT = "Object" diff --git a/lib/parse/util.rb b/lib/parse/util.rb index 831065a..0eba084 100644 --- a/lib/parse/util.rb +++ b/lib/parse/util.rb @@ -84,50 +84,4 @@ def Parse.object_pointer_hash(v) v.class_name.hash ^ v.id.hash end end - - def Parse.store_objects_by_pointer(obj, store={}) - if obj.is_a?(Parse::Object) && !obj.new? - if store[obj.pointer] # don't recurse if you have circular objects - return store - end - - store[obj.pointer] = obj - end - - if obj.is_a?(Array) - obj.each do |v| - Parse.store_objects_by_pointer(v, store) - end - elsif obj.is_a?(Hash) - obj.each do |k, v| - Parse.store_objects_by_pointer(v, store) - end - end - - store - end - - def Parse.restore_objects!(obj, store, already_restored={}) - if already_restored[obj] - return obj - end - - already_restored[obj] = true - - if obj.is_a?(Hash) # Parse::Object or Hash, we'll actually modify the object - obj.each do |k, v| - obj[k] = Parse.restore_objects!(v, store, already_restored) - end - - obj - elsif obj.is_a?(Parse::Pointer) && store[obj] - store[obj] - elsif obj.is_a?(Array) - obj.map do |v| - Parse.restore_objects!(v, store, already_restored) - end - else - obj - end - end end diff --git a/test/test_datatypes.rb b/test/test_datatypes.rb index 5f5d671..d917946 100644 --- a/test/test_datatypes.rb +++ b/test/test_datatypes.rb @@ -55,13 +55,6 @@ def test_increment assert_equal increment.to_json, "{\"__op\":\"Increment\",\"amount\":#{amount}}" end - def test_decrement - amount = 5 - increment = Parse::Decrement.new amount - - assert_equal increment.to_json, "{\"__op\":\"Decrement\",\"amount\":#{amount}}" - end - def test_geopoint # '{"location": {"__type":"GeoPoint", "latitude":40.0, "longitude":-30.0}}' data = { diff --git a/test/test_object.rb b/test/test_object.rb index 8355ebc..eb3099e 100644 --- a/test/test_object.rb +++ b/test/test_object.rb @@ -198,6 +198,32 @@ def test_array_add_pointerizing end end + def test_array_add_unique + VCR.use_cassette('test_array_add_unique', :record => :new_episodes) do + post = Parse::Object.new "Post" + post.save + + comment = Parse::Object.new("Comment", "text" => "great post!") + comment.save + + post.array_add_unique("comments", comment) + assert_equal "great post!", post['comments'][0]['text'] + post.save + assert_equal comment, post['comments'][0] + assert post['comments'][0].instance_of?(Parse::Pointer) # save returns array pointerized + end + end + + def test_decrement + VCR.use_cassette('test_decrement', :record => :new_episodes) do + post = Parse::Object.new "Post", 'count' => 1 + post.save + + post.decrement('count') + assert_equal 0, post['count'] + end + end + def test_array_add_relation omit("broken test, saving Post results in ParseProtocolError: 111: can't add a relation to an non-relation field")