轨道:在保存父元素之前保存子元素

Rails: Save child elements before saving parent

本文关键字:保存 元素 轨道      更新时间:2023-09-26

我在 Rails 应用程序中使用 Paperclip 来验证文件附件并将其加载到 AWS S3 中。所需的行为是用户在提交记录表单时能够上载多个"记录附件"(文件附件)。

问题是,无论用户是否已提交记录表单,我都希望在用户选择要使用文件选择器上传的文件后立即开始上传记录附件。我还想确保每个记录附件都映射到它所属的记录。这一定是可能的,但我还不确定如何构建逻辑。

目前,我可以允许用户在填写和提交记录表单时创建多个 RecordAttachment,但我想立即开始上传文件,以便我可以在表单页面上向用户显示每次上传的进度。下面是允许用户提交具有多个记录附件的记录的代码:

#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 %>

鉴于此设置,其他人是否知道任何方法允许我在用户选择记录附件后立即开始上传,而不是在他们提交记录表单时?

在下面@ShamSUP讨论过这个问题之后,这就是我的想法:在页面加载时,检查用户是否有任何record_id为零的记录附件。如果是这样,请删除它们。然后用户选择一个或多个文件。对于每个行,在"记录附件"表中保存一行,并将record_id设置为 nil。然后,如果用户成功提交其记录表单,则查找具有 record_id == nil 的所有用户记录附件,并将每个record_id设置为当前记录的 ID。

这似乎是一个合理的解决方案吗?有没有更好/更简单的方法?如果其他人能在这个问题上提供任何帮助,我将不胜感激!

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:更深入的脚本在这里

$("input.[name=record_attachments]").change(function () {
    $("#file_form').submit();
});

由于您要包括隐藏recordid字段,因此在用户完成其他部分后,您可以使用它来关联两个表单。

我最终使用了ng-file-upload,这是一个很棒的Angular.js文件上传库,用于这项任务。如果其他人最终需要在保存父记录之前保存子项,则可以使用完整的源代码,但是长短的是,您需要从Javascript将子项写入数据库,然后在保存父项后,更新每个子项的parent_id元素。