LoginSignup
6
11

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-10-26

はじめに

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の使用可否を切り替えれます。
幾つか動かして無いプログラムがあるので、バグがあるかもしれません・・・。(苦笑)

6
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
11