Edited at

Railsで開発環境はpublicに書き込むS3にアクセスするクラスを書いた。

More than 3 years have passed since last update.


はじめに

S3へのアクセスは開発環境ではローカルに書き込む事が多い。なので、ステージングや本番環境ではS3へアクセスして開発環境ではローカルに書き込むクラスを書いた。

aws-sdkを使っているので、バージョンによって異なる動作をするので注意。


Gemfile

gem 'aws-sdk'


下記のバージョンで動作確認してます。


Gemfile.lock

    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_accessor.rb

# 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などでファイルを表示する場合は以下のようにします。


test.html.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は以下のようなコードで対応してます。


app_config.rb

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_config.yml

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/useS3の使用可否を切り替えれます。

幾つか動かして無いプログラムがあるので、バグがあるかもしれません・・・。(苦笑)