web-dev-qa-db-ja.com

Rails AmazonS3への直接アップロード

Railsアプリにファイルを直接AmazonS3にアップロードする機能を追加しようとしています。私の調査によると、一般的なコンセンサスは s3-swf-upload- plugin 。そのgemを使用してサンプルアプリをセットアップしましたが、単一のファイルの選択のみを許可すると、Niceを再生できません。また、アップロード後のレコードを作成し、Paperclipを使用して私がほとんどガイダンスを見つけることができないサムネイルを作成します。

だから私の質問は:

(1)私はその宝石を使用して正しい軌道に乗っていますか、それとも別のアプローチを取るべきですか?

(2)参照用に使用できるサンプルはありますか?

どんな援助も大歓迎です。

クリス

29
Chris Hilton

CarrierWaveDirect という新しいGemを試してください。これにより、htmlフォームを使用してファイルをS3に直接アップロードし、画像処理をバックグラウンドプロセスに簡単に移動できます。

23
dwilkie

一度に1つのファイルしかアップロードしないように簡単に変更できるかどうかはわかりませんが、このgemは私にとって非常にうまく機能します。 Ryan Bates'Railscast のいずれかに基づいています:

https://github.com/waynehoover/s3_direct_upload

6
migu

Carrierwaveを調べてみてください https://github.com/jnicklas/carrierwave (s3をサポート)carrierwaveを使用した複数ファイルのアップロードとuploadify http://blog.assimov.net/post/4306595758/multi-file-upload-with-uploadify-and-carrierwave-on

3
adc

Rails 3を使用している場合は、サンプルプロジェクトを確認してください。

Rails 3、FlashおよびMooToolsベースのFancyUploaderを使用してS3に直接アップロードするサンプルプロジェクト: https://github.com/iwasrobbed/Rails3-S3-Uploader-FancyUploader ==

Rails 3、Flash/Silverlight/GoogleGears/BrowserPlusおよびjQueryベースのPluploadを使用してS3に直接アップロードするサンプルプロジェクト: https://github.com/iwasrobbed/Rails3-S3 -Uploader-Plupload

ちなみに、このブログ投稿で説明されているようなものを使用して、ペーパークリップで後処理を行うことができます。

http://www.railstoolkit.com/posts/fancyupload-Amazon-s3-uploader-with-Paperclip

1
iwasrobbed

HerokuのS3アップロードソリューションへの直接 in Rails( jQuery-File-Uploadaws- sdk gem )なので、S3へのアップロードはajaxを使用してリモートで行うことができます。これがお役に立てば幸いです。

posts_controller.rb

_before_action :set_s3_direct_post, only: [:index, :create]
before_action :delete_picture_from_s3, only: [:destroy]

class PostsController < ApplicationController

  def index
    .
    .
  end

  def create
    @post = @user.posts.build(post_params)
    if @post.save
      format.html
      format.js
    end
  end

  def destroy
    Post.find(params[:id]).destroy
  end

  private

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

    def delete_picture_from_s3
      key = params[:picture_url].split('amazonaws.com/')[1]
      S3_BUCKET.object(key).delete
      return true
      rescue => e
        # If anyone knows a good way to deal with a defunct file sitting in the bucket, please speak up.
        return true
    end

    def post_params
      params.require(:post).permit(:content, :picture_url)
    end

end
_

posts.html.erb

_<div class="info"      data-url="<%= @s3_direct_post.url %>"
                  data-formdata="<%= (@s3_direct_post.fields.to_json) %>"
                      data-Host="<%= URI.parse(@s3_direct_post.url).Host %>">
</div>
_

フォーム

_<%= form_for(:post, url: :posts, method: :post,
              html: { class: "post_form", id: "post_form-#{post.id}" }
            ) do |f| %>
  <%= f.text_area :content, id: "postfield-#{post.id}", class: "postText" %>
  <%= f.button( :submit, name: "Post", title: "Post" ) do %>
    <span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
  <% end %>
  <span class="postuploadbutton" id="postUp-<%= post.id %>" title="Add file" >
    <span class="glyphicon glyphicon-upload" aria-hidden="true"></span>
  </span>
  <span title="Cancel file" class="noticecancelupload" id="postCancel-<%= post.id %>" >
    <span class="glyphicon glyphicon-remove-circle" aria-hidden="true"></span>
  </span>
  <%= f.file_field :picture_url, accept: 'image/jpeg,image/gif,image/png', 
               class: "notice_file_field", id: "postFile-#{post.id}" %>
<% end %>
_

_ post.html.erb

_<%= button_to post_path(
                      params: {
                        id: post.id,
                        picture_url: post.picture_url
                      }
                    ),
                    class: 'btn btn-default btn-xs blurme',
                    data: { confirm: "Delete post: are you sure?" },
                    method: :delete do %>
        <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
<% end %>
_

各_post.html.erbのJavascript

_$(document).off('click',"#postUp-<%= post.id %>");
$(document).on('click', '#postUp-<%= post.id %>', function(e) {
  prepareUpload("#post_form-<%= post.id %>");
  $('#postFile-<%= post.id %>').trigger("click");
});

$(document).off('click',"#postCancel-<%= post.id %>");
$(document).on('click', '#postCancel-<%= post.id %>', function(e) {
  $(".appendedInput").remove(); //  $('#postFile-<% post.id %>').val(""); doesn't work for me
  $('.progBar').css('background','white').text("");
});

$(document).off('submit',"#post_form-<%= post.id %>"); // without this the form submitted multiple times in production
$(document).on('submit', '#post_form-<%= post.id %>', function(e) { // don't use $('#post_form-<%= post.id %>').submit(function() { so it doesn't bind to the #post_form (so it still works after ajax loading)
  e.preventDefault(); // prevent normal form submission
  if ( validatePostForm('<%= post.id %>') ) {
    $.ajax({
      type: 'POST',
      url:  $(this).attr('action'),
      data: $(this).serialize(),
      dataType: 'script'
    });
    $('#postCancel-<%= post.id %>').trigger("click");
  }
});

function validatePostForm(postid) {
  if ( jQuery.isBlank($('#postfield-' + postid).val()) && jQuery.isBlank($('#postFile-' + postid).val()) ) {
    alert("Write something fascinating or add a picture.");
    return false;
  } else {
    return true;
  }
}
_

application.jsのJavascript

_function prepareUpload(feckid) {
  $(feckid).find("input:file").each(function(i, elem) {
    var fileInput    = $(elem);
    var progressBar  = $("<div class='progBar'></div>");
    var barContainer = $("<div class='progress'></div>").append(progressBar);
    fileInput.after(barContainer);
    var maxFS = 10 * 1024 * 1024;

    var info             = $(".info");
    var urlnumbnuts      = info.attr("data-url");
    var formdatanumbnuts = jQuery.parseJSON(info.attr("data-formdata"));
    var hostnumbnuts     = info.attr("data-Host");

    var form             = $(fileInput.parents('form:first'));

    fileInput.fileupload({
      fileInput:        fileInput,
      maxFileSize:      maxFS,
      url:              urlnumbnuts,
      type:             'POST',
      autoUpload:       true,
      formData:         formdatanumbnuts,
      paramName:        'file',
      dataType:         'XML',
      replaceFileInput: false,
      add: function (e, data) {
        $.each(data.files, function (index, file) {
          if (file.size > maxFS) {
            alert('Alas, the file exceeds the maximum file size of 10MB.');
            form[0].reset();
            return false;
          } else {
            data.submit();
            return true;
          }
        });
      },
      progressall: function (e, data) {
        var progress = parseInt(data.loaded / data.total * 100, 10);
        progressBar.css('width', progress + '%')
      },
      start: function (e) {
        progressBar.
          css('background', 'orange').
          css('display', 'block').
          css('width', '0%').
          text("Preparing...");
      },
      done: function(e, data) {
        var key   = $(data.jqXHR.responseXML).find("Key").text();
        var url   = '//' + hostnumbnuts + '/' + key;
        var input = $('<input />', { type:'hidden', class:'appendedInput', 
                     name: fileInput.attr('name'), value: url });
        form.append(input);
        progressBar.
          css('background', 'green').
          text("Ready");
      },
      fail: function(e, data) {
        progressBar.
          css("background", "red").
          css("color", "black").
          text("Failed");
      }
    });
  });
} // function prepareUpload()
_

create.js.erb

_$(".info").attr("data-formdata",  '<%=raw @s3_direct_post.fields.to_json   %>'); // don't use .data() to set attributes 
$(".info").attr("data-url",       "<%= @s3_direct_post.url                 %>");
$(".info").attr("data-Host",      "<%= URI.parse(@s3_direct_post.url).Host %>");

$('.post_form')[0].reset();
$('.postText').val('');
_

application.js

_//= require jquery-fileupload/basic
_

config/initializers/aws.rb

_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'])
_

注:

このソリューションは、index.html.erbページの複数の投稿フォーム用に設計されています。これが、_@s3_direct_post_情報が、各投稿フォームではなく、index.html.erb内のクラスinfoのdiv内に配置される理由です。これは、ページ上のフォームの数に関係なく、一度に1つの_@s3_direct_post_のみがページに表示されることを意味します。 _@s3_direct_post_内のデータは、ファイルアップロードボタンをクリックしたときにのみ取得されます(prepareUpload()を呼び出す場合)。送信すると、新しい_@s3_direct_post_がpostsコントローラーで生成され、_.info_内の情報がcreate.js.erbによって更新されます。フォーム内に_@s3_direct_post_データを保存すると、_@s3_direct_post_のさまざまなインスタンスが同時に存在する可能性があり、ファイル名の生成でエラーが発生します。

投稿コントローラーのインデックスアクション(最初のアップロードの準備ができている)と作成アクション(2番目以降のアップロードの準備ができている)の両方で_:set_s3_direct_post_する必要があります。

通常のフォーム送信はe.preventDefault();によって防止されるため、_$.ajax({_を使用して「手動で」実行できます。フォームで_remote: true_を使用しないのはなぜですか? Railsでは、ファイルのアップロードはHTMLリクエストで行われ、リモートで実行しようとしてもページが更新されるためです。

Info.dataは更新されないため、info.attr()ではなくinfo.data()を使用して_@s3_direct_post_属性を設定および取得します(たとえば、 this の質問を参照) )。これは、jQuery.parseJSON()を使用して属性をオブジェクトに手動で解析する必要があることも意味します(これは実際には.data()が自動的に行います)。

Application.jsで_//= require jquery-fileupload_を使用しないでください。このバグは、特定するのに本当に苦労しました( ここ を参照)。 元のHerokuソリューション これを変更するまで機能しませんでした。

0
Bazley