diff --git a/lib/aws/s3/object.rb b/lib/aws/s3/object.rb index bcdf9e1..771267e 100644 --- a/lib/aws/s3/object.rb +++ b/lib/aws/s3/object.rb @@ -75,9 +75,6 @@ module S3 # song.content_type = 'application/pdf' # song.store # - # (Keep in mind that due to limitiations in S3's exposed API, the only way to change things like the content_type - # is to PUT the object onto S3 again. In the case of large files, this will result in fully re-uploading the file.) - # # A bevie of information about an object can be had using the about method: # # pp song.about @@ -184,11 +181,22 @@ def copy(key, copy_key, bucket = nil, options = {}) source_key = path!(bucket, key) default_options = {'x-amz-copy-source' => source_key} target_key = path!(bucket, copy_key) - returning put(target_key, default_options) do + returning put(target_key, default_options.merge(options)) do acl(copy_key, bucket, acl(key, bucket)) if options[:copy_acl] end end - + + # Updates the object with key by copying it in-place, preserving the ACL of the existing object. + # Useful for updating an object's metadata without having to re-PUT the data. + def update(key, bucket = nil, options = {}) + bucket = bucket_name(bucket) + source_key = path!(bucket, key) + default_options = {'x-amz-copy-source' => source_key, 'x-amz-metadata-directive' => 'REPLACE'} + returning put(source_key, default_options.merge(options)) do + acl(key, bucket, acl(key, bucket)) + end + end + # Rename the object with key from to have key in to. def rename(from, to, bucket = nil, options = {}) copy(from, to, bucket, options) @@ -538,7 +546,12 @@ def store(options = {}) end alias_method :create, :store alias_method :save, :store - + + # Updates the the current object by copying it in place. + def update + self.class.update(key, bucket.name, about.to_headers) + end + # Deletes the current object. Trying to save an object after it has been deleted with # raise a DeletedObject exception. def delete @@ -547,8 +560,7 @@ def delete self.class.delete(key, bucket.name) end - # Copies the current object, given it the name copy_name. Keep in mind that due to limitations in - # S3's API, this operation requires retransmitting the entire object to S3. + # Copies the current object, giving it the name copy_name. def copy(copy_name, options = {}) self.class.copy(key, copy_name, bucket.name, options) end diff --git a/test/remote/object_test.rb b/test/remote/object_test.rb index c63076c..8d83a47 100644 --- a/test/remote/object_test.rb +++ b/test/remote/object_test.rb @@ -157,7 +157,7 @@ def test_object response = fetch_object_at(object.url) assert (200..299).include?(response.code.to_i) - + # Copy the object assert_nothing_raised do @@ -363,6 +363,23 @@ def test_handling_a_path_that_is_not_valid_utf8 end end + def test_updating_an_object_should_replace_its_metadata + key = 'updated-object' + + S3Object.store(key, 'value does not matter', TEST_BUCKET) + object = S3Object.find(key, TEST_BUCKET) + + object.content_type = 'foo/bar' + object.metadata[:foo] = 'bar' + object.update + + reloaded_object = S3Object.find(key, TEST_BUCKET) + assert_equal 'foo/bar', reloaded_object.content_type + assert_equal 'bar', reloaded_object.metadata[:foo] + ensure + S3Object.delete(key, TEST_BUCKET) + end + private def fetch_object_at(url) Net::HTTP.get_response(URI.parse(url))