Rails通过Shrine和嵌套附件进行多次上传

时间:2017-09-22 16:46:59

标签: amazon-web-services file-upload ruby-on-rails-5 shrine

我遇到了将多个文件上传到AWS的问题。我有两种型号Lessons&&附件。课程has_many :attachments我正在设置一个包含课程字段的表单,我想将多个附件上传到该课程。所有内容都正确上传,除非我上传多个文件,它会创建一个新的课程和附件。我正在努力:

https://gorails.com/episodes/multiple-file-uploads-with-shrine?autoplay=1

https://u.osu.edu/hasnan.1/2014/03/30/rails-4-multiple-file-upload-with-carrierwave-nested-form-and-jquery-file-upload/

模型

课程模型

class Lesson < ApplicationRecord
  belongs_to :user
  has_many :attachments, dependent: :destroy
  accepts_nested_attributes_for :attachments
end

附件模型

class Attachment < ApplicationRecord
  belongs_to :lesson, optional: true
  include AttachmentUploader::Attachment.new(:media)
end

控制器

课程控制器

Class LessonsController < ApplicationController
  # truncated for brevity.

  def new
    @lesson = current_user.lessons.build
  end


  def create
    @lesson = current_user.lessons.build(lesson_params)

    respond_to do |format|
      if @lesson.save

        if params[:media]
          params[:media].each { |media|
            @lesson.attachments.create(media_data: media)
          }
        end

        format.html { redirect_to @lesson, notice: 'Lesson was successfully created.' }
        format.json { render :show, status: :created, location: @lesson }
      else
        puts "\n\n\n#{@lesson.errors.full_messages.to_sentence}\n\n\n"
        format.html { render :new, notice: @lesson.errors }
      end
    end

  end



  private

    def set_lesson
      @lesson = Lesson.find(params[:id])
    end

    def lesson_params
      params.require(:lesson).permit(
        :title, 
        :content, 
        :document, 
        :category_id,
        :pinned, 
        :bootsy_image_gallery_id, 
        attachments_attributes: {media: []},
      )
    end

end

附件控制器

class AttachmentsController < ApplicationController
  before_action :set_attachment, only: [:edit, :update, :destroy]

  def create
    @attachment = Attachment.new(attachment_params)
    @attachment.save
  end


  private

    def set_attachment
      @attachment = Attachment.find(params[:id])
    end

    def attachment_params
      params.fetch(:attachment, {})
    end
end

ERB

_form.html.erb

<%= @lesson.errors.full_messages.first if @lesson.errors.any? %>
<%= form_for @lesson do |f| %>
<div class="control-group">
  <%= f.label :title %>
  <div class="controls">
    <%= f.text_field :title, required: true %>
  </div>

  <div>
    <%= f.label :content %>
    <%= f.bootsy_area :content, editor_options: { html: false }, rows: "20", cols: "100" %>
  </div>

  <div>
    <%= f.label 'File under at least one class' %>
    <%= f.collection_select :category_id, Subject.all, :id, :name, { promt: "Choose a Class" } %>
  </div>

  <div>
    <%= f.label :pinned %>
    <%= f.label :pinned, "Yes", value: "Yes"  %>
    <%= f.radio_button :pinned, true%>
    <%= f.label :pinned, "No", value: "No" %>
    <%= f.radio_button :pinned, false, checked: true %>
  </div>
  <hr>


    <div>
      <%= f.label 'Or Upoad a file' %>

      <%
        ######################
        # This is where I have the attachment file field. 
        ######################      
      %>

      <%= file_field_tag "media[]", type: :file, multiple: true %>

    </div>

  <br>
    <%= f.submit nil %>
    <%= link_to 'Cancel', lessons_path%>

  <div class="form-actions btn-a">
    <%= link_to 'Cancel', lessons_path, class: "btn btn-default" %>
  </div>

路线

Rails.application.routes.draw do
  mount AttachmentUploader::UploadEndpoint => "/attachments/upload"


  resources :lessons do
    member do
      put "like",     to: "lessons#upvote"
      put "dislike",  to: "lessons#downvote"
    end
    resources :comments
    resources :attachments
  end

  root 'static#index'

end

JS

$(document).on("turbolinks:load", function () {
  $("[type=file]").fileupload({
    add: function (e, data) {
      data.progressBar = $('<div class="progress" style="width: 300px"><div class="progress-bar"></div></dov>').insertAfter("#file-upload");
      var options = {
        extension: data.files[0].name.match(/(\.\w+)?$/)[0],
        _: Date.now() // revent caching
      }

      $.getJSON("/attachments/upload/cache/presign", options, function (result) {
        data.formData = result['fields'];
        data.url = result['url'];
        data.paramName = "file";
        data.submit();
      });
    },
    progress: function (e, data) {
      var progress = parseInt(data.loaded / data.total * 100, 10);
      var percentage = progress.toString() + '%';
      data.progressBar.find(".progress-bar").css("width", percentage).html(percentage);
    },
    done: function (e, data) {
      console.log("done", data);
      data.progressBar.remove();

      var document = {
        id: data.formData.key.match(/cache\/(.+)/)[1],
        storage: 'cache',
        metadata: {
          size: data.files[0].size,
          filename: data.files[0].name.match(/[^\/\\]+$/)[0],
          mime_type: data.files[0].type
        }
      }

      form = $(this).closest("form");
      form_data = new FormData(form[0]);
      form_data.append($(this).attr("name"), JSON.stringify(document))

      $.ajax(form.attr("action"), {
        contentType: false,
        processData: false,
        data: form_data,
        method: form.attr("method"),
        dataType: "json",
        success: function(response) {
          var $img = $("<img/>", { src: response.image_url, width: 400 });
          var $div = $("<div/>").append($img);
          $("#photos").append($div);
        }
      });
    }
  });
});

1 个答案:

答案 0 :(得分:1)

发生这种情况的原因是因为Javascript查找文件字段的父表单的URL。您有创建新课程的表单,这意味着每次上传文件时,它都会创建一个新课程。

要考虑的一些选项是:

  1. 将上传表单放在仅在创建课程后才可用的位置。这使您可以创建form_for [@lesson, Attachment.new],它将指向正确的URL并让您快速上传文件,但必须首先创建课程。

  2. 您可以调整JS不要立即提交AJAX请求,而是使用图像数据附加隐藏字段。

  3. 我的截屏视频介绍了这种方法,因为您必须先创建相册才能在相册的显示页面上传。我认为这最终会带来更好的用户体验,因此他们不会担心文件上传失败并导致其他表单数据可能丢失或未保存。