如何在不使用嵌套参数的情况下在 Rails 中同时保存父对象和子对象?

How can I save parent and child objects at the same time in Rails without using nested parameters?

我在 Rails 6 应用程序中有具有父子关系的对象:

class Sequence < ApplicationRecord
  has_many :frames, dependent: :destroy, autosave: true
  belongs_to :sequence_list
end

class Frame < ApplicationRecord
  belongs_to :sequence
end

在sequences_controller.rb中,我想在同一个控制器动作中创建一个序列和一堆子帧。

这是 sequences_controller 创建操作:

  # POST /sequences
  # POST /sequences.json
  def create
    #@sequence = Sequence.new(sequence_params)
    @sequence = Sequence.new
    @sequence.name = params[:sequence][:name]

    filename = params[:sequence][:upload][:file].original_filename
    tmp_filename = params[:sequence][:upload][:file].tempfile

    if (File.extname(filename)==".txt")
      Frame.transaction do
        File.readlines(tmp_filename).each do |line|
          i = Frame.new
          i.url = line.strip
          i.sequence = @sequence
          i.save
        end
      end
    end
    tmp_filename.close
    tmp_filename.unlink

    respond_to do |format|
      if @sequence.save
        format.html { redirect_to @sequence, notice: 'Sequence was successfully created.' }
        format.json { render :show, status: :created, location: @sequence }
      else
        format.html { render :new }
        format.json { render json: @sequence.errors, status: :unprocessable_entity }
      end
    end
  end

当我POST执行此操作时,出现以下错误:

SQLite3::ConstraintException: NOT NULL constraint failed: frames.sequence_id

错误来自 Frame.transaction 块中的行 i.save

我知道发生此错误是因为 i.sequence 未分配,因为 @sequence 尚未保存。

我相信在这种情况下的常见方法可能是使用嵌套参数一次性创建子对象,但在这种情况下,子对象的参数实际上在 params[] 哈希中不可用因为我没有通过 JSON 发送它们;我正在发送一个文本文件,该文件由该控制器操作解析以生成子对象列表。

我发送文本文件(而不是使用嵌套表单)的原因是因为子对象太多,通过浏览器手动创建它们会很乏味。每个父级有成百上千个子对象,因此解析上传似乎是显而易见的方法。

我想解决这个问题的一种方法是使用 Javascript 在前端解析文本文件并构建一个嵌套表单,但是是否可以从控制器解决这个问题?

我一直在阅读一些有关 :inverse_of:autosave 的文章,但我不清楚这种情况下的默认方法是什么。我怎样才能同时保存父项和子项,同时允许像普通控制器创建方法一样在失败时回滚,而无需在前端解析文本文件?

您的问题是 Sequence 未保存,您将其作为数据库中的关系。

现在为了让您的代码正常工作,您 sequence 实例应该先于其他实例创建。

幸运的是,ActiveRecord 可以解决这个问题。这是你应该做的:

# POST /sequences
# POST /sequences.json
def create
  #@sequence = Sequence.new(sequence_params)
  @sequence = Sequence.new
  @sequence.name = params[:sequence][:name]

  filename = params[:sequence][:upload][:file].original_filename
  tmp_filename = params[:sequence][:upload][:file].tempfile

  if (File.extname(filename)==".txt")
    Frame.transaction do
      File.readlines(tmp_filename).each do |line|
        @sequence.frames.new(url: line.strip)
      end
    end
  end
  tmp_filename.close
  tmp_filename.unlink

  respond_to do |format|
    if @sequence.save
      format.html { redirect_to @sequence, notice: 'Sequence was successfully created.' }
      format.json { render :show, status: :created, location: @sequence }
    else
      format.html { render :new }
      format.json { render json: @sequence.errors, status: :unprocessable_entity }
    end
  end
end

我所做的是初始化序列的帧,ActiveRecord 将在您保存 @sequence 模型时处理它们的创建。

注意:您的 Sequence 模型当然应该有 has_many :frames

你可以使用这个:

class Frame < ApplicationRecord
  belongs_to :sequence, optional: true
end