はじめに
S3へのアクセスは開発環境ではローカルに書き込む事が多い。なので、ステージングや本番環境ではS3へアクセスして開発環境ではローカルに書き込むクラスを書いた。
aws-sdk
を使っているので、バージョンによって異なる動作をするので注意。
gem 'aws-sdk'
下記のバージョンで動作確認してます。
aws-sdk (2.1.24)
aws-sdk-resources (= 2.1.24)
aws-sdk-core (2.1.24)
jmespath (~> 1.0)
aws-sdk-resources (2.1.24)
aws-sdk-core (= 2.1.24)
S3Accessor
最低限の機能を兼ね揃えたクラスはこちら
# S3へのアクセッサー
# S3が全公開されている事が前提です。(HTTPアクセスを利用してます。)
class S3Accessor
require 'aws-sdk'
# S3のルートパスを返却します。
def self.root_url(root_url = "/")
if S3Accessor.s3_use?
# S3を使用している場合はS3のドメイン+バケット名を返却する。
return "https://s3-#{AppConfig.get(:s3, :region)}.amazonaws.com/#{S3Accessor.bucket}/"
else
# S3を使用しない場合はルートパスを返す。
return root_url
end
end
# S3に保存されているデータを取得します。
def self.get(path)
# 冒頭に"/"がある場合は削除する。
s3_path = path.index("/") == 0 ? path.sub("/", "") : path
# S3の使用可否で分岐
if S3Accessor.s3_use?
# S3を使用する場合(aws-sdkでアクセス出来るかもしれ無いが・・・)
# S3からデータを取得します。
file_data = nil
uri = URI.parse(S3Accessor.root_url + s3_path)
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') { |http|
# 送信
response = http.get(uri.request_uri)
# 取得したデータを保存します。
if response.code == "200"
# 返却用ファイルイメージを読み込みます。
file_data = response.body
else
raise "S3 Access Error! response code:#{response.code}"
end
}
return file_data
else
# 開発環境などでS3を使用しない場合
# ファイルを読み込みます。
file_data = nil
File::open("#{Rails.root}/public/#{s3_path}") {|f|
file_data = f.read
}
# 読み込んだファイルを返却する。
return file_data
end
end
# 指定のファイルをS3へアップします。
# 同名のキーがある場合は上書きします。
def self.put(path, filename, data)
# 冒頭に"/"がある場合は削除する。
s3_path = path.index("/") == 0 ? path.sub("/", "") : path
# 末尾に"/"がない場合は付与する。
s3_path << "/" unless s3_path.rindex("/") == s3_path.length
# S3の使用可否で分岐
if S3Accessor.s3_use?
# S3を使用する場合
# S3のインスタンスを取得
s3 = S3Accessor.s3_instance
# バケットを取得してファイルアップを行います。
obj = s3.bucket(S3Accessor.bucket).object("#{s3_path}#{filename}")
obj.put(body: data)
else
# 開発環境などでS3を使用しない場合
# ローカルに保存する場合はpublic配下に配置する。
local_path = "#{Rails.root}/public/#{s3_path}"
# ファイルパスがない場合は作成する。
FileUtils.mkdir_p(local_path) unless FileTest.exist?(local_path)
# ファイルを書き込みます。
File.open("#{local_path}#{filename}", 'wb') do|f|
f.write(data)
end
end
end
# S3のデータを削除します。
def self.delete(path)
# 冒頭に"/"がある場合は削除する。
s3_path = path.index("/") == 0 ? path.sub("/", "") : path
# S3の使用可否で分岐
if S3Accessor.s3_use?
# S3を使用する場合
# S3のインスタンスを取得
s3 = S3Accessor.s3_instance
# バケットを取得してファイルアップを行います。
obj = s3.bucket(S3Accessor.bucket).object(path)
obj.delete
else
# ローカルに保存する場合はpublic配下のファイルを削除する。
full_path = "#{Rails.root}/public/#{s3_path}"
# ファイルを削除する。
begin
File.delete full_path
rescue => e
Rails.logger.error("file delete error #{e}")
end
end
end
private
# S3のインスタンスを取得します。
def self.s3_instance
Aws::S3::Resource.new(access_key_id: AppConfig.get(:s3, :access_key_id),
secret_access_key: AppConfig.get(:s3, :secret_access_key),
region: AppConfig.get(:s3, :region))
end
# S3を使用するか?
def self.s3_use?
AppConfig.get(:env, Rails.env, :s3, :use)
end
# S3のバケット名を取得
def self.bucket
AppConfig.get(:env, Rails.env, :s3, :bucket)
end
end
AppConfig
に関しては見ればなんとなくわかると思いますが、後述します。
使用方法
S3を使う場合は、開発環境ではローカルにファイルを保存したい場合があります。
そのような要望にも対応してます。
ファイルアップロード
S3Accessor.put("file_path", "file_name", file_data)
file_dataにはバイナリを指定します。
params[:file]
でファイルアップロードを行った場合は以下のようにファイルをアップします。
S3Accessor.put("file_path", "file_name", params[:file].read)
ファイル参照
erb
などでファイルを表示する場合は以下のようにします。
<%= image_tag "#{S3Accessor.root_url(root_url)}file_path/file_name" %>
開発環境でファイルを表示するためにroot_url
を渡してます。
絶対パスが不要の場合は引数なしでOKです。
ファイルデータ取得
バイナリデータが必要な場合は以下のアクセスで取得します。
※HTTPリクエストで取りに行ってるので、ここはイケて無い・・・
S3Accessor.get("file_path/file_name")
ファイル削除
S3にはフォルダの概念が無いのですが、開発環境の場合はフォルダが残ります。
S3Accessor.delete("file_path/file_name")
おまけ
上記にあるAppConfig
は以下のようなコードで対応してます。
class AppConfig
# データを取得します。
def self.get(*keys)
yml = AppConfig.yml["app"]
keys.each { |key|
yml = yml[key.to_s]
}
return yml
end
private
# ymlデータを取得します。
def self.yml
# yamlファイルは毎回読む。
YAML.load_file("#{Rails.root}/config/app_config.yml")
end
end
読み込むyamlは以下のようになってます。
app:
# S3関連
s3:
# 接続情報
access_key_id: 'access_key_id'
secret_access_key: 'secret_access_key'
region: 'ap-northeast-1'
# 環境ごとに変化する設定値
env:
development:
s3:
use: false
bucket: "bucket-develop"
production:
s3:
use: true
bucket: "bucket-production"
development/s3/use
でS3
の使用可否を切り替えれます。
幾つか動かして無いプログラムがあるので、バグがあるかもしれません・・・。(苦笑)