Rails - 构建会影响创建的子记录数量吗?

Rails - Does Build affect how many child records are created?

一篇文章has_many 图片。创建新文章时,用户最多可以添加2张图片

在我的控制器中,我只对图像 运行 "build" 两次,但是当我提交具有 3 个图像字段的表单时,它成功了。有必要 运行 "build" 吗?在这种情况下似乎毫无意义,是否有另一种方法可以更好地确保只接受 2 张图像?

articles_controller.rb

def new
  @article = Article.new

  2.times { @article.images.build }
end

注意这里的“2.times”。

def create
  @article = Article.new(place_params)
  @article.user = current_user

  respond_to do |format|
    if @review.save

      params[:images][:image_file].each do |image_params|
        @image = @article.images.create(image_file: image_params, user: current_user)
      end

    end
  end
end

_form.html.erb

<%= form_with(model: article, url: create_article_path(@article), local: true) do |form| %>

<div class="field">
  <%= form.label :title %> 
  <%= form.text_area :title %>
</div>

<%= form.fields_for :images, @image do |image| %>

  <div class="field">
    <%= image.label :image_file_1, "Image 1" %>
    <%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_1 %>
  </div>

  <div class="field">
    <%= image.label :image_file_2, "Image 2" %>
    <%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_2 %>
  </div>

  <div class="field">
    <%= image.label :image_file_3, "Image 3" %>
    <%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_3 %>
  </div>

<% end %>

<div class="actions">
  <%= form.submit %>
</div>

<% end %>

成功(但为什么呢?)

简而言之——您的构建语句正在准备视图以具有 2 child objects。但是您是手动创建它们,所以您将构建语句渲染为无用。您不必这样做,您可以在模型中声明嵌套属性,然后在控制器中将其列入白名单,然后在视图中 auto-add 它们。 (参见下面的代码示例)

Build 本身确实会更改 object 实例化的数量,但您要覆盖它。

您还手动保存了图像,您不必这样做。有一点 rails 魔法可以为你节省所有 children,如果你已经正确构建它们的话。

CodeView

1 模特

app/models/article.rb

class Article < ApplicationRecord
  has_many :images
  validates :images, length: {maximum: 2}

  accepts_nested_attributes_for :images
end

这里有 2 位注释。首先,在你的验证中,只允许 2 object,如果你试图保存第三个,它将失败。其次,接受模型中的属性允许您在控制器中创建安全参数,从而减轻您手动创建的需要。 (当然,除非你真的想要)

2 风景

<%= form_with(model: article, url: article_path(@article), local: true) do |form| %>

<div class="field">
  <%= form.label :title %> 
  <%= form.text_area :title %>
</div>

<%= form.fields_for :images do |image_form| %>
  <div class="field">
    <%= image_form.label "image_file_#{image_form.index + 1}" %>
    <%= image_form.file_field :image_file %>
    <%= image_form.hidden_field :user_id, value: current_user.id %>
  </div>
<% end %>

<div class="actions">
  <%= form.submit %>
</div>

<% end %>

这里的变化是 a) 我直接将用户添加到表单 b.) 因为您在模型中接受属性,我们将在控制器中将该属性列入白名单,您不需要传递 object 到 field_for -- :images 就可以了。因为你会说在你的控制器中构建它两次,所以你将在表单中有 2 个图像 objects。此外,因为您想要图像 1 和图像 2 的标签,使用 fields_for 您可以通过调用 [=17 自动访问 object 的索引(就像您使用任何数组一样) =].

3 控制器 - 新动作

app/models/article.rb

你的动作效果很好,保持原样。

def new
  @article = Article.new

  2.times { @article.images.build }
 end

4 控制器 - 强参数

def article_params
  params.require(:article).permit(:title, :body, images_attributes: [:id, :article_id,:user_id, :image_file])
end

将您的参数全部列入白名单将节省时间,并且比在每个控制器中允许它们更容易阅读,但如果需要,您可以这样做,例如,如果参数在某些操作中被允许,但在其他操作中则不允许。

5 控制器 - 创建操作

def create
  @article = Article.new(article_params)
  respond_to do |format|
    if @article.save
      format.html { redirect_to @article, notice: 'Article was successfully created.' }
      format.json { render :show, status: :created, location: @article }
    else
      format.html { render :new }
      format.json { render json: @article.errors, status: :unprocessable_entity }
    end
  end
end

这可能看起来与脚手架中的默认创建操作相似(如果不完全相同),这就是您所需要的。除非可以创建 parent,否则不会创建 child 图像 object,因此您不必担心在 if 保存中添加它们。