Reputation: 1920
I got an issue with uploading multiple files to AWS. I have two models Lessons && Attachments. Lessons has_many :attachments
and I'm setting up a form that has lesson fields and I want to upload multiple attachments to that lesson. Everything uploads correctly except when I upload multiple files, it creates a new lesson and attachment. I'm working off:
https://gorails.com/episodes/multiple-file-uploads-with-shrine?autoplay=1
And
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
<%= @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
$(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);
}
});
}
});
});
Upvotes: 1
Views: 1262
Reputation: 1667
The reason this is happening is because the Javascript looks for the URL of the file field's parent form. You have the form for creating a new Lesson, which means every time you upload a file, it will create a new lesson.
Some options to consider are:
Place the upload form in a place that's only available after the Lesson has been created. This lets you create a form_for [@lesson, Attachment.new]
which will point to the right URL and let you upload files really quickly, but the Lesson must be created first.
You could adjust the JS not to submit an AJAX request right away and instead attach hidden fields with the image data.
My screencast covered this method because you had to create the Album first before uploading on the Album's show page. I think this ends up being a better user experience so they aren't worrying about file uploads failing and causing their other form data to possibly get lost or not saved.
Upvotes: 2