Ruby on rails Rails直接上传到AmazonS3

Ruby on rails Rails直接上传到AmazonS3,ruby-on-rails,upload,amazon-s3,Ruby On Rails,Upload,Amazon S3,我希望在我的Rails应用程序中添加功能,将文件直接上传到AmazonS3。从我的研究来看,普遍的共识似乎是使用。我已经使用这个gem设置了一个示例应用程序,但是我不能让它只允许选择单个文件。我还想创建一个记录后上传和使用回形针创建一个缩略图,我可以找到一些指导 因此,我的问题是: (1) 我是在正确的轨道上使用宝石还是我应该采取另一种方法 (2) 有什么样的样品可供我参考吗 任何帮助都将不胜感激 Chris您可以使用回形针上载到S3(请参阅)并创建缩略图,尽管它会先上载到临时文件夹,然后可以在

我希望在我的Rails应用程序中添加功能,将文件直接上传到AmazonS3。从我的研究来看,普遍的共识似乎是使用。我已经使用这个gem设置了一个示例应用程序,但是我不能让它只允许选择单个文件。我还想创建一个记录后上传和使用回形针创建一个缩略图,我可以找到一些指导

因此,我的问题是:

(1) 我是在正确的轨道上使用宝石还是我应该采取另一种方法

(2) 有什么样的样品可供我参考吗

任何帮助都将不胜感激


Chris

您可以使用回形针上载到S3(请参阅)并创建缩略图,尽管它会先上载到临时文件夹,然后可以在将文件上载到S3之前应用图像处理


至于这种配置的例子,在整个博客圈和StackOverflow上都有很多,例如..

尝试查看carrierwave(支持s3)
使用carrierwave和uploadify进行多文件上载

如果您使用的是Rails 3,请查看我的示例项目:

使用Rails 3、Flash和基于MooTools的FancyUploader直接上传到S3的示例项目:

使用Rails 3、Flash/Silverlight/GoogleGears/BrowserPlus和基于jQuery的Plupload直接上传到S3的示例项目:

顺便说一句,你可以用回形针做后处理,就像这篇博文描述的:


尝试一个名为it的新Gem,它允许您使用html表单直接将文件上载到S3,并轻松地将图像处理移到后台处理过程中

不确定您是否可以轻松地修改它,一次只上载一个文件,但这个Gem对我来说效果非常好。它基于以下之一:

我在Rails中进行了调整(使用和),因此可以使用ajax远程上传到S3。我希望这是有用的:

posts\u 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
<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>
<%= 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 %>
$(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;
  }
}
$(".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('');
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'])
config/initializers/aws.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
<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>
<%= 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 %>
$(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;
  }
}
$(".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('');
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页面上的多个post表单。这就是为什么
@s3\u direct\u post
信息被放置在index.html.erb中的类
info
的div中,而不是每个post表单中。这意味着无论页面上的表单数量如何,在任何时候页面上都只有一个
@s3\u direct\u post
。只有在单击文件上载按钮时才能抓取
@s3\u direct\u post
中的数据(通过调用
prepareUpload()
)。提交后,posts控制器中会生成一个新的
@s3\u direct\u post
,create.js.erb会更新
.info
中的信息。将
@s3\u direct\u post
数据存储在表单中意味着
@s3\u direct\u post
的许多不同实例可能同时存在,从而导致文件名生成错误

您需要
:在posts控制器索引操作(准备第一次上载)和创建操作(准备第二次和后续上载)中设置\u s3\u direct\u post

e.preventDefault()阻止正常表单提交
这样就可以使用
$.ajax({
)手动完成。为什么不在表单中使用
remote:true
?因为在Rails中,文件上传是通过HTML请求完成的,即使您尝试远程执行,页面刷新也是如此

使用
info.attr()
而不是
info.data()
来设置和检索
@s3\u direct\u post
属性,因为info.data不会更新 (例如,请参见问题)。这意味着您还必须使用
jQuery.parseJSON()
(实际上,.data()会自动将属性解析为对象)


不要在application.js中使用
/=require jquery fileupload
。这个bug是一个很难识别的问题(请参阅)。直到我改变了这一点,这个才起作用。

我不认为曲别针会直接上传到S3。曲别针和Carrierwave一样,会上传到服务器的tmp目录进行处理,然后再上传到S3。这意味着,服务器将占用整个http请求处理程序,直到上传完成。在某些情况下,这可能会“冻结”应用程序t在上载到S3之前,先上载到tmp文件夹以允许图像处理(例如,调整大小、创建适当的缩略图等)。由于topicstarter需要图像后处理,此解决方案似乎比“将原始文件直接上载到S3->下载->创建缩略图->将缩略图上载到S3”更好。应用程序“冻结”的问题可以通过将上传到S3转移到后台进程来解决。感谢大家的评论。另一个关于曲别针上传到tmp文件夹的方法的问题是在Heroku这样的网站上托管时出现的,因为它们限制了上传文件的大小。曲别针不允许你直接上传到S3,你应该编辑你的文件r回答编辑了一个答案。把它留在这里,以防有人想办法间接上传到S3(通过临时文件夹),但不知何故偶然发现了这个页面。请记住,如果你在Heroku上托管并打算上载大文件,它们有30秒的请求超时时间,这将阻止任何大的上载。我在下面发布了一个答案,让一些示例项目直接上载到S3,绕过这个问题。+1 great gem,一直在等待类似这样的东西,那将我集成到carrierwave中。感谢steer dwilkie-一定会看看这个gem这很好,但是有没有关于如何在Ruby之外构建帖子的例子,比如:iOS?是的!这个gem是最好的。非常感谢Ryan Bates和这个gem的维护者。