これで大抵賄える！AWS SDK for Ruby V3でのS3操作まとめ！！

S3の操作するコードを書くタスクは日常的にやるわけではなくて、なぜかちょうど忘れた頃の絶妙なタイミングでやってきます。そのたびにAPIリファレンスを見ながら頑張って組み立ててるような気がしたので、しゃらくせぇ！と思い、よく使うやつをまとめました。

`Aws::S3::Client`と`Aws::S3::Resource`の2系統を用意しました。お好みに応じて参照してください。
参考までに、この2つの違いがよく分からなくて途方に暮れている人はこちらをご覧ください。
[AWS SDK for Ruby V3のAws::S3::ClientとAws::S3::Resourceの違いに正面から向き合う](https://qiita.com/Kta-M/items/c1245ae6bf12f686e827)

クレデンシャル情報やリージョンは、環境変数やIAMロール等で設定されているものとします。
個別に設定する場合は、`Aws::S3::Client.new`や`Aws::S3::Resource.new`の引数に渡してあげてください。

## Aws::S3::Client
```ruby
# 共通
client = Aws::S3::Client.new
```

### バケット一覧
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#list_buckets-instance_method

```ruby
# バケット情報一覧
client.list_buckets
=> #<struct Aws::S3::Types::ListBucketsOutput
 buckets=
  [#<struct Aws::S3::Types::Bucket name="bucket-01", creation_date=2017-09-16 14:07:03 UTC>,
   #<struct Aws::S3::Types::Bucket name="bucket-02", creation_date=2018-08-19 04:22:41 UTC>,],
 owner=#<struct Aws::S3::Types::Owner display_name="XXXXXX", id="xxxxxxxxxxxxxxx">>
 
# 各バケットを操作
client.list_buckets.buckets.each do |bucket|
  # なにかする
end
```

### バケット作成
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#create_bucket-instance_method

```ruby
client.create_bucket(bucket: "bucket-03")
=> #<struct Aws::S3::Types::CreateBucketOutput location="http://bucket-03.s3.amazonaws.com/">
```

### バケット削除
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#delete_bucket-instance_method

```ruby
client.delete_bucket(bucket: "bucket-03")
=> #<struct Aws::EmptyStructure>
```

### オブジェクト一覧
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#list_objects_v2-instance_method

```ruby
# オブジェクト情報一覧
# `list_objects`は使わない
client.list_objects_v2(bucket: "bucket-01")
=> #<struct Aws::S3::Types::ListObjectsV2Output
 is_truncated=true,
 contents=
  [#<struct Aws::S3::Types::Object key="example_01.txt", last_modified=2019-09-24 21:42:10 UTC, etag="\"xxxxxxxxxxxxxx\"", size=7, storage_class="STANDARD", owner=nil>,
   #<struct Aws::S3::Types::Object key="example_02.txt", last_modified=2019-09-24 21:42:10 UTC, etag="\"xxxxxxxxxxxxxx\"", size=7, storage_class="STANDARD", owner=nil>],
 name="bucket-01",
 prefix="",
 delimiter=nil,
 max_keys=1000,
 common_prefixes=[],
 encoding_type=nil,
 key_count=1000,
 continuation_token=nil,
 next_continuation_token="1I+AdtAyjlXhJ1jEKpdpR3fqz2bDnfJFEfZ+wgn9vmpXjHNGcLzL8bg==",
 start_after=nil>
 
# 各オブジェクトを操作(1000個以下の場合)
client.list_objects_v2(bucket: "bucket-01").contents.each do |object|
  # なにかする
end

# 各オブジェクトを操作(1000個より多いの場合)
options = {bucket: "bucket-01"}
loop do
  object_list = client.list_objects_v2(options)
  object_list.contents.each do |object|
    # なにかする
  end
  options[:continuation_token] = object_list.next_continuation_token
  break unless object_list.next_continuation_token
end

# キー名のプレフィクスによる絞り込み
client.list_objects_v2(bucket: "bucket-01", prefix: "hoge/").contents.each do |object|
  # なにかする
end
```

### オブジェクトアップロード
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_object-instance_method

```ruby
client.put_object(bucket: "bucket-01", key: "example_01.txt", body: "example")
=> #<struct Aws::S3::Types::PutObjectOutput
 expiration=nil,
 etag="\"xxxxxxxxxxxxxx\"",
 server_side_encryption=nil,
 version_id="JlDwEydDxlVNoyEaChzromzkjPo5FwVl",
 sse_customer_algorithm=nil,
 sse_customer_key_md5=nil,
 ssekms_key_id=nil,
 ssekms_encryption_context=nil,
 request_charged=nil>
```

### オブジェクト読み込み
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#get_object-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Types/GetObjectOutput.html

```ruby
client.get_object(bucket: "bucket-01", :key => 'example_01.txt').body.read
=> "example"
```

### オブジェクト削除
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#delete_object-instance_method

```ruby
client.delete_object(bucket: "bucket-01", key: "example_01.txt")
=> #<struct Aws::S3::Types::DeleteObjectOutput delete_marker=true, version_id="skCZWAXC0.vhxjK6zKTF0dBkj4H0gEe8", request_charged=nil>
```

### オブジェクトのバージョン操作
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#list_object_versions-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#delete_object-instance_method

```ruby
# バージョン情報
client.list_object_versions(bucket: "bucket-01", prefix: "example_01.txt")
=> #<struct Aws::S3::Types::ListObjectVersionsOutput
 is_truncated=false,
 key_marker="",
 version_id_marker="",
 next_key_marker=nil,
 next_version_id_marker=nil,
 versions=
  [#<struct Aws::S3::Types::ObjectVersion
  etag="\"xxxxxxxxxx\"",
  size=7,
  storage_class="STANDARD",
  key="example_01.txt",
  version_id="BcczH4ZRVunnKyBuzk3cBOQgrh1gtGpR",
  is_latest=true,
  last_modified=2019-09-24 21:47:54 UTC,
  owner=#<struct Aws::S3::Types::Owner display_name="xxxxxx", id="xxxxxxxxxxxxxxxxxx">>,
 #<struct Aws::S3::Types::ObjectVersion
  etag="\"xxxxxxxxxx\"",
  size=7,
  storage_class="STANDARD",
  key="example_01.txt",
  version_id="null",
  is_latest=false,
  last_modified=2019-09-24 21:42:10 UTC,
  owner=#<struct Aws::S3::Types::Owner display_name="xxxxxx", id="xxxxxxxxxxxxxxxxxx">>],
 name="bucket-01",
 prefix="example_01.txt",
 delimiter=nil,
 max_keys=1000,
 common_prefixes=[],
 encoding_type="url">
 
# 特定のバージョンを削除
client.delete_object(bucket: "bucket-01", prefix: "example_01.txt", version_id: "JlDwEydDxlVNoyEaChzromzkjPo5FwVl")
=> #<struct Aws::S3::Types::DeleteObjectOutput delete_marker=nil, version_id="JlDwEydDxlVNoyEaChzromzkjPo5FwVl", request_charged=nil>
```

### マルチパートアップロード
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#create_multipart_upload-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#upload_part-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#complete_multipart_upload-instance_method

```ruby
multipart = client.create_multipart_upload(bucket: "bucket-01", key: "multipart")
=> #<struct Aws::S3::Types::CreateMultipartUploadOutput
 abort_date=nil,
 abort_rule_id=nil,
 bucket="bucket-01",
 key="multipart",
 upload_id="xxxxxxxxxxxxxxxxxxxx",
 server_side_encryption=nil,
 sse_customer_algorithm=nil,
 sse_customer_key_md5=nil,
 ssekms_key_id=nil,
 ssekms_encryption_context=nil,
 request_charged=nil>

part_01 = client.upload_part(bucket: "bucket-01", key: "multipart", body: File.open("/path/to/part_01"), part_number: 1, upload_id: multipart.upload_id)
=> #<struct Aws::S3::Types::UploadPartOutput
 server_side_encryption=nil,
 etag="\"xxxxxxxxxxxxxxxxxxxxx\"",
 sse_customer_algorithm=nil,
 sse_customer_key_md5=nil,
 ssekms_key_id=nil,
 request_charged=nil>

part_02 = client.upload_part(bucket: "bucket-01", key: "multipart", body: File.open("/path/to/part_02"), part_number: 2, upload_id: multipart.upload_id)
=> #<struct Aws::S3::Types::UploadPartOutput
 server_side_encryption=nil,
 etag="\"yyyyyyyyyyyyyyyyyyyyy\"",
 sse_customer_algorithm=nil,
 sse_customer_key_md5=nil,
 ssekms_key_id=nil,
 request_charged=nil>

client.complete_multipart_upload(
  bucket: "bucket-name-0001",
  key: "multipart",
  multipart_upload: {
    parts: [
      {etag: part_01.etag, part_number: 1},
      {etag: part_02.etag, part_number: 2}
    ]
  },
  upload_id: multipart.upload_id
)
=> #<struct Aws::S3::Types::CompleteMultipartUploadOutput
 location="https://bucket-01.s3.ap-northeast-1.amazonaws.com/multipart",
 bucket="bucket-01",
 key="multipart",
 expiration=nil,
 etag="\"zzzzzzzzzzzzzzzzzzzzz\"",
 server_side_encryption=nil,
 version_id="CSbIGreIivX2CTbIxj9Qz1JbePVQb2Qo",
 ssekms_key_id=nil,
 request_charged=nil>
```

### 署名付きURL作成
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Presigner.html

```ruby
# clientではできない
signer = Aws::S3::Presigner.new
signer.presigned_url(:get_object, bucket: "bucket-01", key: "example_01.txt", expires_in: 3600)
=> "https://bucket-01.s3.ap-northeast-1.amazonaws.com/example_0001?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-...
```

## Aws::S3::Resource

```ruby
# 共通
resource = Aws::S3::Resource.new
```

### バケット一覧
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Resource.html#buckets-instance_method

```ruby
# バケット情報一覧
resource.buckets
=> #<Aws::S3::Bucket::Collection:0x00007fb1e3ccb6a8 @batches=#<Enumerator: ...>, @limit=nil, @size=nil>

# 各バケットを操作
resource.buckets.each do |bucket|
  # なにかする
end
```

### バケット作成
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Resource.html#create_bucket-instance_method

```ruby
resource.create_bucket(bucket: "bucket-03")
=> #<Aws::S3::Bucket:0x00007fb1e3d3f2d8 @client=#<Aws::S3::Client>, @data=nil, @name="bucket-03">

# こちらでもできるけど、Client系統で扱うbucket情報が返ってくる
resource.bucket("bucket-03").create

# 存在確認もできる
resource.bucket("bucket-03").exists?
=> true
```

### バケット削除
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#delete-instance_method

```ruby
resource.bucket("bucket-03").delete
=> #<struct Aws::EmptyStructure>
```

### オブジェクト一覧
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#objects-instance_method

```ruby
bucket = resource.bucket("bucket-01")

# オブジェクト情報
bucket.objects
=> #<Aws::S3::ObjectSummary::Collection:0x00007fb1e3e47a68 @batches=#<Enumerator: ...>, @limit=nil, @size=nil>

# 各オブジェクトを操作(1000個より多くてもOK！)
bucket.objects.each do |object|
  # なにかする
end

# キー名のプレフィクスによる絞り込み
bucket.objects(prefix: "hoge/").each do |object|
  # なにかする
end
```

### オブジェクトアップロード
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#put_object-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#put-instance_method

```ruby
bucket = resource.bucket("bucket-01")

# 文字列から
bucket.put_object(key: "example_01.txt", body: "example")
=> #<Aws::S3::Object:0x00007f859e9625c8 @bucket_name="bucket-01", @client=#<Aws::S3::Client>, @data=nil, @key="example_01.txt">

# ファイルから
bucket.put_object(key: "example_01.txt", body: File.open("example_01.txt"))
=> #<Aws::S3::Object:0x00007f859e9625c8 @bucket_name="bucket-01", @client=#<Aws::S3::Client>, @data=nil, @key="example_01.txt">

# こちらでもできるけど、Client系統で扱うbucket情報が返ってくる
bucket.object("example_01.txt").put(body: "example")
=> #<struct Aws::S3::Types::PutObjectOutput
 expiration=nil,
 etag="\"xxxxxxxxxxxxxxxxxxxxxxxxxxxx\"",
 server_side_encryption=nil,
 version_id=nil,
 sse_customer_algorithm=nil,
 sse_customer_key_md5=nil,
 ssekms_key_id=nil,
 ssekms_encryption_context=nil,
 request_charged=nil>
```

### オブジェクト読み込み
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#get-instance_method

```ruby
bucket = resource.bucket("bucket-01")

bucket.object("example_01.txt").get.body.read
=> "example"

# 存在確認もできる
bucket.object("example_01.txt").exists?
=> true
```

### オブジェクト削除
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#delete-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/ObjectSummary/Collection.html#batch_delete!-instance_method

```ruby
bucket = resource.bucket("bucket-01")

bucket.object("example_01.txt").delete
=> #<struct Aws::S3::Types::DeleteObjectOutput delete_marker=nil, version_id=nil, request_charged=nil>

# 一括削除
bucket.objects(prefix: "hoge/").batch_delete!
=> nil
```

### オブジェクトのバージョン操作
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#object_versions-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#delete-instance_method

```ruby
bucket = resource.bucket("bucket-01")

bucket.object_versions(prefix: "example_01.txt").map(&:data)
=> [#<struct Aws::S3::Types::ObjectVersion
  etag="\"xxxxxxxxxx\"",
  size=7,
  storage_class="STANDARD",
  key="example_01.txt",
  version_id="BcczH4ZRVunnKyBuzk3cBOQgrh1gtGpR",
  is_latest=true,
  last_modified=2019-09-24 21:47:54 UTC,
  owner=#<struct Aws::S3::Types::Owner display_name="xxxxxx", id="xxxxxxxxxxxxxxxxxx">>,
 #<struct Aws::S3::Types::ObjectVersion
  etag="\"xxxxxxxxxx\"",
  size=7,
  storage_class="STANDARD",
  key="example_01.txt",
  version_id="null",
  is_latest=false,
  last_modified=2019-09-24 21:42:10 UTC,
  owner=#<struct Aws::S3::Types::Owner display_name="xxxxxx", id="xxxxxxxxxxxxxxxxxx">>]
  
# 特定のバージョンを削除
bucket.object("example_01.txt").delete(version_id: "BcczH4ZRVunnKyBuzk3cBOQgrh1gtGpR")
=> #<struct Aws::S3::Types::DeleteObjectOutput delete_marker=nil, version_id="BcczH4ZRVunnKyBuzk3cBOQgrh1gtGpR", request_charged=nil>
```

### マルチパートアップロード
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#upload_file-instance_method

```ruby
bucket = resource.bucket("bucket-01")

# デフォルトだと15MBより大きければマルチパートアップロードになる
# しきい値を変えたい場合は、`multipart_threshold`オプションを与える
bucket.object("very_large_file.txt").upload_file("/path/to/very_large_file")
=> true
```

### 署名付きURL作成
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method

```ruby
bucket = resource.bucket("bucket-01")

# `:get`/`put`/`head`/`delete`が指定できる
# `expires_in`はデフォルト900秒
bucket.object("example_01.txt").presigned_url(:get, expires_in: 3600)
=> "https://bucket-01.s3.ap-northeast-1.amazonaws.com/example_0001?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-...
```