Deep-dive on the Next Gen Platform. Join the Webinar!

Skip Navigation
Show nav
Dev Center
  • Get Started
  • ドキュメント
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
    • .NET
  • ドキュメント
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up
Hide categories

Categories

  • Heroku のアーキテクチャ
    • Dyno (アプリコンテナ)
      • Dyno Management
      • Dyno Concepts
      • Dyno Behavior
      • Dyno Reference
      • Dyno Troubleshooting
    • スタック (オペレーティングシステムイメージ)
    • ネットワーキングと DNS
    • プラットフォームポリシー
    • プラットフォームの原則
  • Developer Tools
    • コマンドライン
    • Heroku VS Code Extension
  • デプロイ
    • Git を使用したデプロイ
    • Docker によるデプロイ
    • デプロイ統合
  • 継続的デリバリーとインテグレーション
    • 継続的統合
  • 言語サポート
    • Node.js
      • Working with Node.js
      • Troubleshooting Node.js Apps
      • Node.js Behavior in Heroku
    • Ruby
      • Rails のサポート
      • Bundler の使用
      • Working with Ruby
      • Ruby Behavior in Heroku
      • Troubleshooting Ruby Apps
    • Python
      • Working with Python
      • Python でのバックグランドジョブ
      • Python Behavior in Heroku
      • Django の使用
    • Java
      • Java Behavior in Heroku
      • Working with Java
      • Maven の使用
      • Spring Boot の使用
      • Troubleshooting Java Apps
    • PHP
      • PHP Behavior in Heroku
      • Working with PHP
    • Go
      • Go の依存関係管理
    • Scala
    • Clojure
    • .NET
      • Working with .NET
  • データベースとデータ管理
    • Heroku Postgres
      • Postgres の基礎
      • Postgres スターターガイド
      • Postgres のパフォーマンス
      • Postgres のデータ転送と保持
      • Postgres の可用性
      • Postgres の特別なトピック
      • Migrating to Heroku Postgres
    • Heroku Data For Redis
    • Apache Kafka on Heroku
    • その他のデータストア
  • AI
    • Working with AI
  • モニタリングとメトリクス
    • ログ記録
  • アプリのパフォーマンス
  • アドオン
    • すべてのアドオン
  • 共同作業
  • セキュリティ
    • アプリのセキュリティ
    • ID と認証
      • シングルサインオン (SSO)
    • Private Space
      • インフラストラクチャネットワーキング
    • コンプライアンス
  • Heroku Enterprise
    • Enterprise Accounts
    • Enterprise Team
    • Heroku Connect (Salesforce 同期)
      • Heroku Connect の管理
      • Heroku Connect のリファレンス
      • Heroku Connect のトラブルシューティング
  • パターンとベストプラクティス
  • Heroku の拡張
    • Platform API
    • アプリの Webhook
    • Heroku Labs
    • アドオンのビルド
      • アドオン開発のタスク
      • アドオン API
      • アドオンのガイドラインと要件
    • CLI プラグインのビルド
    • 開発ビルドパック
    • Dev Center
  • アカウントと請求
  • トラブルシューティングとサポート
  • Salesforce とのインテグレーション
  • 言語サポート
  • Ruby
  • Rails のサポート
  • Rails での S3 への画像の直接アップロード

Rails での S3 への画像の直接アップロード

日本語 — Switch to English

最終更新日 2020年06月23日(火)

Table of Contents

  • 原理
  • アプリの例
  • S3
  • S3 SDK
  • オリジン間サポート
  • 事前署名済み POST
  • クライアント側のコード
  • ビューを準備する
  • クライアント側でのファイルフィールドの検出
  • 完成した jquery-file-upload コード
  • オプション: dataType
  • オプション: replaceFileInput
  • jQuery-File-Upload のコールバック
  • 画像の送信と描画
  • デバッグ
  • 拡張

この記事の内容はライブラリの最新の変更を反映していないため、現在は通用しない可能性があります。

この記事では、S3 への直接アップロードを Rails アプリに追加する方法を示します。 Ruby および Rails 向けの S3 画像アップロードソリューションとして一般的なものは Paperclip​ や CarrierWave​ など多数ありますが、これらのソリューションではサーバーを一時キャッシュとして使用します。

これらのソリューションでは通常、ファイルを Heroku にアップロードしてから S3 にストリーミングします。ファイルのサイズが小さければこの方法で問題ありませんが、Heroku の一時的なファイルシステム​の性質上、S3 にアップロードする前に大きなファイルが dyno から削除されてしまう場合があります。

より確実性のある別の方法は、クライアント側から S3 に画像を直接アップロードし、画像が完全にアップロードされたら参照 URL をデータベースに保存するというものです。この方法では、画像のアップロード中に dyno が再起動されても問題なく、画像アップロードの追加負荷を dyno が処理する必要がなく、ファイルが 2 回 (dyno と S3 に 1 回ずつ) アップロードされるのを待つ必要もありません。

唯一の短所は、すべてのロジックをクライアント側で実行する必要があることです。 この記事では、これを行う方法を示します。

原理

この記事では、jQuery-File-Upload​ プラグインと AWS gem を使用します。carrier wave direct​ など、S3 に画像を直接アップロードするために使用できるライブラリは他にもありますが、そのようなライブラリの実装は、クライアント側のすべての考慮事項に関する低レベルの知識がなければ難しい場合があります。

jQuery-File-Upload プラグインを使用して、読みやすくて短い JavaScript コードを作成します。このコードは、画像アップロード入力を使用するフォームであればどのフォームでも再利用できます。UI の動作は細かくカスタマイズ可能であり、ユーザーの視点からも動作は非常にシンプルです。Rails 側で AWS presigned-post を作成し、画像 URL をデータベースに保存します。

アプリの例

この例の目的のために、User​ モデルがあり、各ユーザーのアバターを S3 に保存すると仮定します。UsersController​ があることも前提です。まだプロジェクトがない場合でも、最初から手順を進めることができます。

$ rails new direct-s3-example
$ cd direct-s3-example
$ rails generate scaffold user name avatar_url
      invoke  active_record
      create    db/migrate/20140519195131_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      # ...

まず、データベースを移行できます。

$ rake db:migrate

次に、任意のエディタでプロジェクトを開きます。ここで、アプリケーションが S3 と通信できるようにする必要があります。

S3

ファイルを S3 に送信する前に、S3 アカウント​を取得し、バケットを正しく設定しておく必要があります。

指示に従って、次の手順に進む前に S3 アカウントを取得​しておいてください。

Rails アプリと S3 アカウントの用意ができたら、クライアント側ではファイルを操作する必要があり、Ruby 側では S3 と対話するためのライブラリが必要になります。

S3 SDK

S3 との対話には Amazon Ruby SDK を使用します。アプリケーションの Gemfile に次の内容を追加します。

gem 'aws-sdk', '~> 2'

次に、bundle install​ を実行します。ローカル開発のために、.env​ ファイルおよび heroku local​ を使用していると仮定します。.env​ ファイルを開き、S3_BUCKET​、AWS_ACCESS_KEY_ID​、および AWS_SECRET_ACCESS_KEY​ が、S3 バケットを作成​したときの値に設定されていることを確認します。

$ cat .env
S3_BUCKET=my-s3-development
AWS_ACCESS_KEY_ID=EXAMPLEKVFOOOWWPYA
AWS_SECRET_ACCESS_KEY=exampleBARZHS3sRew8xw5hiGLfroD/b21p2l

値が環境に書き込まれていることを確認します。そのためには、このコマンドを実行して、それが .env​ 内の値と一致していることを確認します。

$ heroku local:run rails runner "puts ENV['S3_BUCKET']"
my-s3-development

ローカルで環境変数を設定したら、コントローラで使用する新しい S3 オブジェクトをインスタンス化する必要があります。config/initializers/aws.rb​ でイニシャライザを作成します。次に、AWS を設定し、グローバル S3 定数を作成します。

Aws.config.update({
  region: 'us-east-1',
  credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']),
})

S3_BUCKET = Aws::S3::Resource.new.bucket(ENV['S3_BUCKET'])

Heroku で実行している本番環境用に、新しい S3 バケットの作成プロセスを繰り返す必要があります。これらの値は heroku config:set​ を使用して設定する必要があります。たとえば、本番環境バケットの名前が my-s3-production​ の場合、次を実行することによって Heroku で適切な値を設定できます。

$ heroku config:set S3_BUCKET=my-s3-production

本番環境の資格情報を使用して、AWS_ACCESS_KEY_ID​ および AWS_SECRET_ACCESS_KEY​ に対してこのプロセスを繰り返します。

オリジン間サポート

ブラウザのデフォルト設定では、現在描画中のページの外部にある他のサービスを JavaScript で呼び出すことは許可されていません。この保護が存在しないと、ログインしている任意の Web ページから (Facebook や GitHub などの) 他のサービスにリクエストを送信でき、その時点でログインしていれば、それらのサービスがプライベートデータを受信する可能性があります。幸いにも、このセキュリティメカニズムはデフォルトで組み込まれていますが、このメカニズムにより、現在参照している URL 以外の URL にファイルを送信することも禁止されます。そのため、デフォルトでは、JavaScript を使用して Web サイトから S3 にファイルを送信することはできません。この機能を有効にするには、CORS を使用する必要があります。

CORS はオリジン間リソース共有​の略で、基本的には、HTTP リクエストの送信元を指定するための機能です。したがって、S3 バケットへの指示で、JavaScript 経由でサーバーからファイルを受信することを許可する必要があります。

本番環境用と開発用に、2 つの異なるバケットが必要です。開発用バケットで、CORS 設定を変更する必要があります。次に、localhost:3000​ で実行されているローカルマシンから AWS へのファイル送信を許可する設定の例を示します。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>http://localhost:3000</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

AllowedOrigin​ のための適切なオリジンセットが本番環境バケットにあることを確認する必要があります。バケットでの CORS サポートの設定についての詳細は、「How Do I Enable CORS On My Bucket?​」(バケットで CORS を有効にする方法) を参照してください。

事前署名済み POST

これから使用するのは、AWS ruby gem から生成された事前署名済み POST です。事前署名済み URL は、特定のオブジェクトをバケットにアップロードすることをユーザーや顧客に許可するが、AWS のセキュリティ資格情報または権限を持っていることを要求しない場合に役立ちます。詳細は、ドキュメントの事前署名済み POST に関する記述​を参照してください。事前署名済み URL は、基本的には、S3 オブジェクト (この場合は画像) が置かれる場所とそれに対する制約に関するすべての設定を行うための簡単な方法です。

app/controllers/users_controller.rb​ を開きます。次のような内容になっています。

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

...

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end

end

ユーザーを作成および​編集するときに @s3_direct_post​ 変数を使用できるよう、before_action​ メソッドでこれを設定します。

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]
  before_action :set_s3_direct_post, only: [:new, :edit, :create, :update]

...

private
  def set_s3_direct_post
    @s3_direct_post = S3_BUCKET.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: '201', acl: 'public-read')
  end

ここでは多くのことを説明する必要がありますが、まずは presigned_post​ メソッドのオプションについて説明します。

事前署名済み POST のオプション: key

キーは、S3 内でオブジェクトが置かれる場所です。S3 ではキーの重複は認められていないため、各キーは一意である必要があります。AWS ではカスタムの ${filename}​ ディレクティブがサポートされています。構文は ("#{}​ を使用して) Ruby コードを文字列に挿入する方法に似ていますが、これは AWS で特別な意味に解釈される文字列です。この ${filename}​ ディレクティブは、ユーザーが pic.png​ という名前のファイルをアップロードした場合、S3 は最終的なファイルを pic.png​ と同じ名前で保存する必要があることを S3 に指示します。同じ名前のファイルを使用している 2 人のユーザーがどちらもアバターを保存できるようにするために、SecureRandom.uuid​ を使用してランダム文字列を追加します。最後に、すべての画像はベースレベルの uploads​ に委任されるため、他のアイテムをバケットに保存する場合は、ユーザーがアップロードしたファイルを個別に保管する方が簡単です。

事前署名済み POST のオプション: success_action_​status

これは、画像が正常に保存されたときに AWS が返す HTTP ステータスコード​です。${filename}​ ディレクティブを使用しているため、ファイルの保存に使用されるキーの名前について AWS から通知を受ける必要があります。そのためには、success_action_status​ を 201 に設定する必要があります。新しいオブジェクトの作成に関する AWS のドキュメント​を参照してください。

値が 201 に設定されている場合、Amazon S3 は 201 ステータスコードを含む XML ドキュメントを返します。

キーを解析できる XML 応答を受信できることが必要です。201 ステータスコードを省略すると、それが不可能になります。この値を 201 に設定することは非常に重要です。

事前署名済み POST のオプション: acl

アクセス制御リスト​ (ACL) は、誰がファイルを閲覧、編集、削除できるかを AWS で制御する方法です。アバター URL をすべてのユーザーに公開するために、この値を :public_read​ に設定します。このように設定すると、ユーザーが S3 バケットにアップロードしたファイルは誰でも閲覧できますが、ファイルを編集できるのは S3 バケットの管理者だけです。acl​ を設定しない場合、ユーザーのアバターを誰も閲覧できなくなります。

クライアント側のコード

事前署名済み POST の用意ができたら、クライアント側でオブジェクト内の情報を使用して画像を S3 に送信できます。

サーバーを一時キャッシュとして利用できないため、クライアント側のコード (JavaScript) を使用してファイルを S3 に配信する必要があります。HTML 5 でファイル API が導入されましたが、バージョン 10 までの IE ではサポート対象外​でした。これに対する解決策として、jQuery File Upload​ プラグインを使用します。ただし、このライブラリを使用する前に、まず JQuery UI が必要です。

$ curl -O \
https://raw.githubusercontent.com/jquery/jquery-ui/master/ui/widget.js \
>> app/assets/javascripts/jquery.ui.widget.js

次に、fileupload JavaScript を Rails プロジェクトに含めます。

$ curl -O \
https://raw.githubusercontent.com/blueimp/jQuery-File-Upload/master/js/jquery.fileupload.js \
>> app/assets/javascripts/z.jquery.fileupload.js

名前の先頭に z​ を付けて、このファイルが他の jQuery ファイルよりも後に読み込まれるようにしています。

‘//= require_tree’ ディレクティブを使用して application.js​ 内のすべての JavaScript ファイルを読み込んでいる場合、 この JavaScript は自動的に使用可能になります。そうでない場合、次のようにして明示的に要求する必要があります。

//= require jquery.ui.widget
//= require z.jquery.fileupload

ローカルサーバーを起動します。

$ heroku local

ブラウザで localhost:3000/users/new​ を読み込み、fileupload JavaScript ファイルの存在を確認します。Chrome では、CMD+Option+J​ キーを押して JavaScript コンソールを開くことができます。コンソールで、jQuery が正しく読み込まれていることを確認します。

> console.log($)
function $(selector, [startNode]) { [Command Line API] }

ここで、fileupload​ 関数が使用できることを確認します。

> console.log($().fileupload)
function ( options ) {
          var isMethodCall = typeof options === "string",
               args = slice.call( arguments, 1 ),
               returnValue = this;
//...

結果が返されない場合は、JavaScript コンソールでエラーが報告されていないこと、ソースを表示したときにすべての必要なファイルがリストに含まれていること、それらのファイルが空でなく順序が正しいことを確認してください。

ビューを準備する

S3 アップロードプロセスを完了するには、まずファイルを S3 に送信してから、URL をデータベースに保存する必要があります。このタスクの場合、Users テーブルに avatar_url​ 文字列フィールドがあります。

JavaScript では、S3 に直接アップロードする画像が含まれるフィールドを特定する何らかの方法が必要です。まず、ファイルアップロードフィールド app/views/users/_form.html.erb​ があることがわかっているフォームを開きます。フォームは次のようになります。

<%= form_for(@user) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
  <!-- ... -->

JavaScript でこのフォームをターゲットにする方法が必要なため、directUpload​ という名前のカスタムクラスを追加します。

<%= form_for(@user, html: { class: "directUpload" }) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
  <!-- ... -->

POST の際に、特定の S3 データも一緒に送信する必要があります。これを data 属性としてフォームに配置し、後から JavaScript で参照します。

<%= form_for(@user, html: { class: 'directUpload', data: { 'form-data' => (@s3_direct_post.fields), 'url' => @s3_direct_post.url, 'host' => URI.parse(@s3_direct_post.url).host } }) do |f| %>

ユーザーが写真をアップロードするためのファイルフィールドも必要です。avatar_url​ フィールドを探します。これは text_field​ である必要があります。

<div class="field">
  <%= f.label :avatar_url %><br>
  <%= f.text_field :avatar_url %>
</div>

これを変更して file_field​ にします。

<div class="field">
  <%= f.label :avatar_url %><br>
  <%= f.file_field :avatar_url %>
</div>

クライアント側でのファイルフィールドの検出

この時点で、画像をアップロードするための JavaScript ライブラリ、S3 バケット、有効な事前署名済み POST オブジェクト、avatar_url​ 文字列フィールドを持つ User モデル、ファイル入力フィールドを持つビューの準備ができています。S3 に送信するユーザーの画像を取得し、URL を avatar_url​ に保存する必要があります。これは手動のプロセスであり、主に JavaScript を使用して行います。このエクスペリエンスはカスタマイズが可能であり、そのために使用できるオプションについては後で説明します。

デバッグ用の一連の <script></script>​ タグ、または application.js​ ファイルのいずれかで、カスタムクラスを含むフォームを探してから、すべてのファイルフィールドを反復処理します。

$(function() {
  $('.directUpload').find("input:file").each(function(i, elem) {
    var fileInput    = $(elem);
    console.log(fileInput);
  });
});

ここでページを読み込むと、ファイルフィールドごとにコンソール出力が表示されます (avatar_url​ に対応する出力だけであることを確認します)。ここから、役に立つ他の要素を引き出すことができます。ファイル入力を保持するフォームと、フォームの送信ボタンが必要であり、ファイル要素ごとの進捗状況バーを作成する必要もあります。

$(function() {
  $('.directUpload').find("input:file").each(function(i, elem) {
    var fileInput    = $(elem);
    var form         = $(fileInput.parents('form:first'));
    var submitButton = form.find('input[type="submit"]');
    var progressBar  = $("<div class='bar'></div>");
    var barContainer = $("<div class='progress'></div>").append(progressBar);
    fileInput.after(barContainer);
  });
});

ここで、進捗状況バーにスタイルを適用してみましょう。app/assets/stylesheets/screen.css​ で、次の内容を追加します。

.progress {
  max-width: 600px;
  margin:    0.2em 0 0.2em 0;
}

.progress .bar {
  height:  1.2em;
  padding-left: 0.2em;
  color:   white;
  display: none;
}

完成した jquery-file-upload コード

すべての必要な要素が揃ったので、それぞれのファイル入力要素で fileInput.fileupload({})​ を呼び出してオプションを渡すことができます (コールバックは後で追加します)。

$(function() {
  $('.directUpload').find("input:file").each(function(i, elem) {
    var fileInput    = $(elem);
    var form         = $(fileInput.parents('form:first'));
    var submitButton = form.find('input[type="submit"]');
    var progressBar  = $("<div class='bar'></div>");
    var barContainer = $("<div class='progress'></div>").append(progressBar);
    fileInput.after(barContainer);
    fileInput.fileupload({
      fileInput:       fileInput,
      url:             form.data('url'),
      type:            'POST',
      autoUpload:       true,
      formData:         form.data('form-data'),
      paramName:        'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
      dataType:         'XML',  // S3 returns XML if success_action_status is set to 201
      replaceFileInput: false
    });
  });
});

ファイルを S3 に送信するために必要なものはこれで全部ですが、画像の URL を取得したり、現在の状況をユーザーに知らせるために UI を更新したりすることはまだできません。これらの機能はすべて、後の「コールバック」のセクションで追加します。

まずは、fileupload​ に渡すオプションについて説明します。詳細は、jquery-file-upload のオプションに関するドキュメント​を参照してください。

オプション: fileInput

このオプションは jQuery-File-Upload がファイルを探すことができる場所の参照であり、今回の例ではフォームのファイル入力フィールドからファイルを取得します。

オプション: url

画像の送信先 URL です。以前に AWS 事前署名済み POST を設定するときに生成した URL に送信します。この情報は、以前にフォームフィールドに設定した data 属性から読み取ります。

url: form.data('url')

オプション: type

使用する HTTP リクエストの種類です。今回の例では S3 上にオブジェクトを “作成” するので、POST​ HTTP リクエストを送信する必要があります。

オプション: autoUpload

ユーザーがファイルを選択したら、自動的にアップロードを開始します。これにより、他にも多くの情報を入力する必要がある場合でも、フォームの残り項目の入力中に画像をアップロードできます。画像のアップロードの状況を知らせるためには、手動で進捗状況バーを描画する必要があります。

この値を false​ に設定した場合、JavaScript によってアップロードアクションを手動でトリガーする必要があります。

オプション: formData

URL の生成に加えて、事前署名済み POST は、画像を送信するために必要なすべてのデータ (AWS アクセスキーなど) をセットアップします。このオブジェクトにすでに含まれている情報を利用するために、以前に設定した data 属性からこれを読み取ります。

formData: form.data('form-data')

オプション: paramName

送信中のフィールドの “名前” です。デフォルトでは、Rails は name="user[avatar_url]"​ のような HTML を生成します。S3 ではこのようなネストされた名前フィールドは望ましくないため、他の名前を付けます。今回の例では “file” を使用します。

paramName: 'file'

オプション: dataType

画像のアップロード後に返されるデータの想定型を指定します。success_action_status​ が 201 に設定されている場合、AWS から XML​ が返されるため、ここでは XML が返されることを想定するよう jQuery-File-Upload に指示します。

オプション: replaceFileInput

デフォルトでは、jQuery-File-Upload は画像入力を複製し、ページ上の画像入力を複製で置き換えます。これによって予期しない視覚的動作が発生し、後から必要な場合に fileInput​ オブジェクトを操作することが難しくなります。これを避けるために、この機能を無効にします。

jQuery-File-Upload のコールバック

progressall​、start​、done​、fail​ の各オプションで設定された多数のコールバックを使用します。これらは、アップロードの進捗状況をユーザーに表示したり、UI を制御したり、フォーム送信時に avatar_url​ の適切な値を設定したりするために必要です。コールバックの完全なコードは次の場所にあります。


$(function() {
  $('.directUpload').find("input:file").each(function(i, elem) {
    var fileInput    = $(elem);
    var form         = $(fileInput.parents('form:first'));
    var submitButton = form.find('input[type="submit"]');
    var progressBar  = $("<div class='bar'></div>");
    var barContainer = $("<div class='progress'></div>").append(progressBar);
    fileInput.after(barContainer);
    fileInput.fileupload({
      fileInput:       fileInput,
      url:             form.data('url'),
      type:            'POST',
      autoUpload:       true,
      formData:         form.data('form-data'),
      paramName:        'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
      dataType:         'XML',  // S3 returns XML if success_action_status is set to 201
      replaceFileInput: false,
      progressall: function (e, data) {
        var progress = parseInt(data.loaded / data.total * 100, 10);
        progressBar.css('width', progress + '%')
      },
      start: function (e) {
        submitButton.prop('disabled', true);

        progressBar.
          css('background', 'green').
          css('display', 'block').
          css('width', '0%').
          text("Loading...");
      },
      done: function(e, data) {
        submitButton.prop('disabled', false);
        progressBar.text("Uploading done");

        // extract key and generate URL from response
        var key   = $(data.jqXHR.responseXML).find("Key").text();
        var url   = '//' + form.data('host') + '/' + key;

        // create hidden field
        var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })
        form.append(input);
      },
      fail: function(e, data) {
        submitButton.prop('disabled', false);

        progressBar.
          css("background", "red").
          text("Failed");
      }
    });
  });
});

フォーム送信時に適切な avatar_url​ をデータベースに設定したり、S3 への画像アップロード中に読み込みの進捗状況バーを表示したり、画像アップロードの進行中にユーザーが誤ってフォームを送信するのを防いだりするために必要なものはこれで全部です。

デバッグ用の console.log("done");​ コードは、コールバックに追加しておくと、コールバックが適切に呼び出されていることの確認に役立ちますが、本番環境にデプロイする前に削除することを忘れないでください。

次のセクションでは、各コールバックとそれに含まれるコードについて説明します。

コールバック: progressall

progressall​ コールバックは、イベントオブジェクトとデータオブジェクトを使用して呼び出されます。データオブジェクトには、アップロードされるファイルの合計サイズ data.total​ と、現在までにアップロード済みのファイルサイズ data.loaded​ が含まれます。これら 2 つを使用して進捗率を計算し、進捗状況バーを使用して表示できます。

progressall: function (e, data) {
  var progress = parseInt(data.loaded / data.total * 100, 10);
  progressBar.css('width', progress + '%')
}

すべての画像入力に対して進捗状況バーを作成し、画像ごとに個別の fileupload​ を初期化するため、この progressall​ コールバックには 1 つの画像に関する情報しか含まれないことに注意してください。

コールバック: start

このコールバックはファイルアップロードの開始時に呼び出されます。これを使用して進捗状況バーを表示します。また、画像が途中までしかアップロードされていなのにユーザーが誤ってフォームを送信しないよう、送信ボタンを無効にします。

start: function (e) {
  submitButton.prop('disabled', true);

  progressBar.
    css('background', 'green').
    css('display', 'block').
    css('width', '0%').
    text("Loading...");
}

コールバック: done

このコールバックは、画像が正常に S3 に送信されると呼び出されます。

コールバック全体は次のようになります。

done: function(e, data) {
  submitButton.prop('disabled', false);
  progressBar.text("Uploading done");

  // extract key and generate URL from response
  var key   = $(data.jqXHR.responseXML).find("Key").text();
  var url   = '//' + $(elem).data('host') + '/' + key;

  // create hidden field
  var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })
  form.append(input);
}

短く、機能は複雑ですが、処理内容は次のとおりです。

まず、ファイルのアップロードが完了したので、送信機能を再度有効にします。

submitButton.prop('disabled', false);

進捗状況バーのテキストも更新します。

progressBar.text("Uploading done");

最も重要なのは、S3 からの XML 応答を解析してキーを取得することです。

var key = $(data.jqXHR.responseXML).find("Key").text();

キーを取得したら、image: からのプロトコル相対 URL​ を構築します。

var url   = '//' + form.data('host') + '/' + key;

このデータを使用して、URL 文字列の値と元の要素の名前を持つ非表示フィールドを作成します。fileInput.attr('name')​ を使用して要素から名前を取り出すことができます。

var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })

そのため、新しい入力要素は同じ名前になります (この場合は name="user[avatar_url]"​)。したがって、ユーザーがフォームを送信すると、URL が UserController の create​ アクションに返送され、保存されます。

form.append(input);

user[avatar_url]​ の名前はすでにファイルフィールドで使用されていますが、非表示要素の方が (フォームで後ろの方に出現するので) 優先されます。この時点で、ユーザーがフォームを送信すると、URL がパラメータに保存されます。

コールバック: fail

問題が発生して画像がアップロードされない場合、アップロードの失敗をユーザーに警告し、(画像が機能しなくても送信する必要がある場合に備えて) 送信ボタンを再び有効にする必要があります。

今回の例では、進捗状況バーの色を赤に変えて、失敗テキストを出力することによってこれを行います。

fail: function(e, data) {
  submitButton.prop('disabled', false);

  progressBar.
    css("background", "red").
    text("Failed");
}

画像の送信と描画

画像が S3 に正常にアップロードされ、非表示のフォーム要素がフォームにアタッチされたら、ユーザーはフォームを送信できます。これが正しく機能すると、ログのパラメータで user["avatar_url"]​ を確認できます。

Started POST "/users" for 127.0.0.1 at 2014-05-19 17:47:01 -0500
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "user"=>{"avatar_url"=>"//my-development.s3.amazonaws.com/uploads/220f5378-1e0f-4823-9527-3d1170089a49/foo.gif", "name"=>"Schneems", "twitter"=>""}, "commit"=>"Create User"}

この時点で、URL の表示を希望し、@user​ オブジェクトがある場合、画像を描画できます。

<%= image_tag @user.avatar_url %>

デバッグ

S3 への直接アップロードを実装している間、JavaScript コンソールを開いたままにし、console.log​ ステートメントを追加して開発中のコールバックの実行可能性を高めると非常に役立ちます。それらは本番環境にデプロイする前に削除する必要があります。コンソールに例外がある場合、解決しない限りコードは機能しません。ファイルのアップロードは機能しているが URL が保存されない場合は、ログをチェックし、UsersController の create​ アクションのコードが avatar_url​ 列を適切に保存および許可していることを確認してください。

拡張

この例は、S3 への直接アップロードを実装する 1 つの方法を表しています。要件や需要はアプリケーションによって異なります。この例をテンプレートとして使用し、変更および拡張して、必要な動作を実現する必要があります。

たとえば、クライアント側での写真のトリミングや編集​を有効にしたり、さまざまな進捗状況バー、ドラッグアンドドロップインターフェイスなどを使用したりできます。コードを拡張する方法については、jQuery-File-Upload のドキュメント​を参照してください。

受け入れ可能なファイルの種類を制限する方法や、一定時間の経過後にユーザーの画像を期限切れにする方法については、AWS 事前署名済み POST に関するドキュメント​を参照してください。

関連カテゴリー

  • Rails のサポート
Unicorn を使用した Rails アプリケーションのデプロイ Rails を使用した Ruby での HTTP キャッシング

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure
  • .NET

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing
  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Github
  • LinkedIn
  • © 2025 Salesforce, Inc. All rights reserved. Various trademarks held by their respective owners. Salesforce Tower, 415 Mission Street, 3rd Floor, San Francisco, CA 94105, United States
  • heroku.com
  • Legal
  • Terms of Service
  • Privacy Information
  • Responsible Disclosure
  • Trust
  • Contact
  • Cookie Preferences
  • Your Privacy Choices