Rails: 先保存子元素再保存父元素
Rails: Save child elements before saving parent
我在 Rails 应用程序中使用 Paperclip 来验证文件附件并将它们加载到 AWS S3 中。期望的行为是用户在提交记录表单时能够上传多个 "RecordAttachments"(文件附件)。
要注意的是,我想在用户使用文件选择器选择要上传的文件后立即开始上传 RecordAttachment,而不管用户是否已提交记录表单。我还想确保每个 RecordAttachment 都映射到它所属的 Record。这一定是可能的,但我还不确定如何构建逻辑。
目前,我可以允许用户在填写并提交 Record 表单时创建多个 RecordAttachment,但我想立即开始上传文件,以便在表单上向用户显示每次上传的进度页。下面是允许用户提交具有多个 RecordAttachments 的记录的代码:
#app/models/record.rb
class Record < ActiveRecord::Base
has_many :record_attachments
accepts_nested_attributes_for :record_attachments
# Ensure user has provided the required fields
validates :title, presence: true
validates :description, presence: true
validates :metadata, presence: true
validates :hashtag, presence: true
validates :release_checked, presence: true
end
和子元素:
#app/models/record_attachment.rb
class RecordAttachment < ActiveRecord::Base
belongs_to :record
validates :file_upload, presence: true
# Before saving the record to the database, manually add a new
# field to the record that exposes the url where the file is uploaded
after_save :set_record_upload_url
# Use the has_attached_file method to add a file_upload property to the Record
# class.
has_attached_file :file_upload
# Validate that we accept the type of file the user is uploading
# by explicitly listing the mimetypes we are willing to accept
validates_attachment_content_type :file_upload,
:content_type => [
"video/mp4",
"video/quicktime"
]
end
记录控制器:
class RecordsController < ApplicationController
# GET /records/new
def new
@record = Record.new
end
def create
# retrieve the current cas user name from the session hash
@form_params = record_params()
@form_params[:cas_user_name] = session[:cas_user]
@record = Record.create( @form_params )
respond_to do |format|
if @record.save
if params[:record_attachments]
params[:record_attachments].each do |file_upload|
@record.record_attachments.create(file_upload: file_upload)
end
end
flash[:success] = "<strong>CONFIRMATION</strong>".html_safe +
": Thank you for your contribution to the archive."
format.html { redirect_to @record }
format.json { render action: 'show',
status: :created, location: @record }
else
format.html { render action: 'new' }
format.json { render json: @record.errors,
status: :unprocessable_entity }
end
end
end
end
我的表单如下所示:
<%= form_for @record, html: { multipart: true } do |f| %>
<div class="field">
<%= f.label :title, "Title of Material<sup>*</sup>".html_safe %>
<%= f.text_field :title %>
</div>
<!-- bunch of required fields followed by multiple file uploader: -->
<div id="file-upload-container" class="field">
<%= f.label :file_upload, "Upload File<sup>*</sup>".html_safe %>
<div id="placeholder-box">File name...</div>
<%= f.file_field :file_upload,
multiple: true,
type: :file,
name: 'record_attachments[]',
id: "custom-file-upload",
style: "display:none"
%>
<span class="btn btn-small btn-default btn-inverse" id="file-upload-button" onclick="$(this).parent().find('input[type=file]').click();">Browse</span>
</div>
<!-- bunch of fields followed by submit button -->
<%= button_tag(type: 'submit', class: "btn btn-primary blue-button submit-button") do %>
<i class="icon-ok icon-white"></i> SUBMIT
<% end %>
<% end %>
鉴于此设置,其他人是否知道任何方法可以让我在用户选择 RecordAttachments 时立即开始上传它们,而不是在他们提交 Record 表单时开始上传?
在与下面的@ShamSUP 讨论之后,这就是我的想法:在页面加载时,检查用户是否有任何 record_id 为零的 RecordAttachments。如果是这样,请删除它们。然后用户选择一个或多个文件。对于每个,在 RecordAttachment table 中保存一行,并将 record_id 设置为 nil。然后,if/when 用户成功提交了他们的 Record 表单,找到用户的所有 record_id == nil 的 RecordAttachments,并将每个的 record_id 设置为当前 Record 的 ID。
这看起来是一个合理的解决方案吗?有better/easier方法吗?如果其他人可以就此问题提供任何帮助,我将不胜感激!
Javascript 无法实际读取文件输入字段中的文件内容,因此不幸的是,当用户尝试填写其余部分时,无法通过 ajax 提交文件表格。不过,这并不意味着您不能执行某些迂回方法。
您可以尝试在表单页面加载时生成记录 ID,然后将 ID 包含在主表单的隐藏输入中,然后将文件输入放在单独的表单中,或者将它们移动到单独的表单中页面加载后带有 javascript 的表单。在与文件输入相同的形式中,还包括生成的记录 ID。
<form id="main-form" enctype="multipart/form-data">
<input type="hidden" name="recordid" value="123">
<input type="text" name="title">
.... etc fields
</form>
<form id="file-form" enctype="multipart/form-data" target="file_frame">
<div id="placeholder-box">Select a file</div>
<input type="file" multiple name="record_attachments">
<input type="hidden" name="recordid" value="123">
</form>
拥有两个表单后,您可以使用javascript在值更改后将文件表单提交到iframe,防止提交时重新加载页面。
<iframe name="file_frame" id="file_frame" src="path_to_upload_script" />
一些例子javascript:More in-depth script here
$("input.[name=record_attachments]").change(function () {
$("#file_form').submit();
});
因为您包含隐藏 recordid
字段,所以您可以在用户完成其他部分后使用它来关联两个表单。
我最终使用了 ng-file-upload, an awesome file upload library for Angular.js, for this task. The full source 以防其他人最终需要在保存父记录之前保存子项,但总而言之,您需要将子项写入数据库 Javascript,然后一旦父项被保存,更新每个子项的 parent_id 元素。
我在 Rails 应用程序中使用 Paperclip 来验证文件附件并将它们加载到 AWS S3 中。期望的行为是用户在提交记录表单时能够上传多个 "RecordAttachments"(文件附件)。
要注意的是,我想在用户使用文件选择器选择要上传的文件后立即开始上传 RecordAttachment,而不管用户是否已提交记录表单。我还想确保每个 RecordAttachment 都映射到它所属的 Record。这一定是可能的,但我还不确定如何构建逻辑。
目前,我可以允许用户在填写并提交 Record 表单时创建多个 RecordAttachment,但我想立即开始上传文件,以便在表单上向用户显示每次上传的进度页。下面是允许用户提交具有多个 RecordAttachments 的记录的代码:
#app/models/record.rb
class Record < ActiveRecord::Base
has_many :record_attachments
accepts_nested_attributes_for :record_attachments
# Ensure user has provided the required fields
validates :title, presence: true
validates :description, presence: true
validates :metadata, presence: true
validates :hashtag, presence: true
validates :release_checked, presence: true
end
和子元素:
#app/models/record_attachment.rb
class RecordAttachment < ActiveRecord::Base
belongs_to :record
validates :file_upload, presence: true
# Before saving the record to the database, manually add a new
# field to the record that exposes the url where the file is uploaded
after_save :set_record_upload_url
# Use the has_attached_file method to add a file_upload property to the Record
# class.
has_attached_file :file_upload
# Validate that we accept the type of file the user is uploading
# by explicitly listing the mimetypes we are willing to accept
validates_attachment_content_type :file_upload,
:content_type => [
"video/mp4",
"video/quicktime"
]
end
记录控制器:
class RecordsController < ApplicationController
# GET /records/new
def new
@record = Record.new
end
def create
# retrieve the current cas user name from the session hash
@form_params = record_params()
@form_params[:cas_user_name] = session[:cas_user]
@record = Record.create( @form_params )
respond_to do |format|
if @record.save
if params[:record_attachments]
params[:record_attachments].each do |file_upload|
@record.record_attachments.create(file_upload: file_upload)
end
end
flash[:success] = "<strong>CONFIRMATION</strong>".html_safe +
": Thank you for your contribution to the archive."
format.html { redirect_to @record }
format.json { render action: 'show',
status: :created, location: @record }
else
format.html { render action: 'new' }
format.json { render json: @record.errors,
status: :unprocessable_entity }
end
end
end
end
我的表单如下所示:
<%= form_for @record, html: { multipart: true } do |f| %>
<div class="field">
<%= f.label :title, "Title of Material<sup>*</sup>".html_safe %>
<%= f.text_field :title %>
</div>
<!-- bunch of required fields followed by multiple file uploader: -->
<div id="file-upload-container" class="field">
<%= f.label :file_upload, "Upload File<sup>*</sup>".html_safe %>
<div id="placeholder-box">File name...</div>
<%= f.file_field :file_upload,
multiple: true,
type: :file,
name: 'record_attachments[]',
id: "custom-file-upload",
style: "display:none"
%>
<span class="btn btn-small btn-default btn-inverse" id="file-upload-button" onclick="$(this).parent().find('input[type=file]').click();">Browse</span>
</div>
<!-- bunch of fields followed by submit button -->
<%= button_tag(type: 'submit', class: "btn btn-primary blue-button submit-button") do %>
<i class="icon-ok icon-white"></i> SUBMIT
<% end %>
<% end %>
鉴于此设置,其他人是否知道任何方法可以让我在用户选择 RecordAttachments 时立即开始上传它们,而不是在他们提交 Record 表单时开始上传?
在与下面的@ShamSUP 讨论之后,这就是我的想法:在页面加载时,检查用户是否有任何 record_id 为零的 RecordAttachments。如果是这样,请删除它们。然后用户选择一个或多个文件。对于每个,在 RecordAttachment table 中保存一行,并将 record_id 设置为 nil。然后,if/when 用户成功提交了他们的 Record 表单,找到用户的所有 record_id == nil 的 RecordAttachments,并将每个的 record_id 设置为当前 Record 的 ID。
这看起来是一个合理的解决方案吗?有better/easier方法吗?如果其他人可以就此问题提供任何帮助,我将不胜感激!
Javascript 无法实际读取文件输入字段中的文件内容,因此不幸的是,当用户尝试填写其余部分时,无法通过 ajax 提交文件表格。不过,这并不意味着您不能执行某些迂回方法。
您可以尝试在表单页面加载时生成记录 ID,然后将 ID 包含在主表单的隐藏输入中,然后将文件输入放在单独的表单中,或者将它们移动到单独的表单中页面加载后带有 javascript 的表单。在与文件输入相同的形式中,还包括生成的记录 ID。
<form id="main-form" enctype="multipart/form-data">
<input type="hidden" name="recordid" value="123">
<input type="text" name="title">
.... etc fields
</form>
<form id="file-form" enctype="multipart/form-data" target="file_frame">
<div id="placeholder-box">Select a file</div>
<input type="file" multiple name="record_attachments">
<input type="hidden" name="recordid" value="123">
</form>
拥有两个表单后,您可以使用javascript在值更改后将文件表单提交到iframe,防止提交时重新加载页面。
<iframe name="file_frame" id="file_frame" src="path_to_upload_script" />
一些例子javascript:More in-depth script here
$("input.[name=record_attachments]").change(function () {
$("#file_form').submit();
});
因为您包含隐藏 recordid
字段,所以您可以在用户完成其他部分后使用它来关联两个表单。
我最终使用了 ng-file-upload, an awesome file upload library for Angular.js, for this task. The full source 以防其他人最终需要在保存父记录之前保存子项,但总而言之,您需要将子项写入数据库 Javascript,然后一旦父项被保存,更新每个子项的 parent_id 元素。