Rails – 为什么我的嵌套模型表不需要’accepts_nested_attributes_for’

我有两个模型itemuser_itemItem有许多user_itemsuser_items属于item 。 我有一个表单,用户可以在其中创建新项目。 在表单中,用户应该包括图片。 名称,描述和标签将保存到新的项目对象中。 图片应作为属性保存在同时创建的user_item对象上。

我一直在阅读嵌套模型表单和强参数,并使用accepts_nested_attributes_for 。 我的表单似乎有效,但我不明白为什么我不需要accepts_nested_attributes_for 。 虽然它似乎有效,但是我的方式有问题吗?

形成

  

Items_controller

 def create Item.transaction do @item = Item.create(name: item_params[:name], description: item_params[:description], tag_list: item_params[:tag_list], created_by: current_user.id, status: Item::STATUS[:pending]) if item_params[:item] == nil @item.errors.add(:picture, "is required") end if @item.errors.empty? @user_item = @item.user_items.build(user_id: current_user.id, picture: item_params[:item][:picture]) if @user_item.save flash[:notice] = "Thank you for your item submission." redirect_to items_path else render :new raise ActiveRecord::Rollback, "Useritem create failed" end else render :new raise ActiveRecord::Rollback, "no picture" end end end private def item_params params.require(:item).permit(:name, :description, :tag_list, item: :picture) end 

编辑:

我在下面的理查德的答案中做了改动,但我现在遇到了几个问题。 这是我在items_controller中的当前创建操作

  def create @item= Item.new item_params @item.status = Item::STATUS[:pending] @item.created_by = current_user.id @item.user_items.first.user_id = current_user.id if @item.save redirect_to items_path, notice: "Thank you for your item request! else render :new end end 
  1. 在validation失败时,我收到了一个丢失的模板错误,所以我添加了一个elserender :new

  2. 现在validation失败时,将呈现新模板,但不显示图片的fields_for输入。

  3. 我在user_item.rb中有validates_presence_of :picture ,我在item.rb中有validates_associated :user_items ,但我仍然能够提交没有图片的表单。

  4. 正如您在有效提交中看到的那样,我需要将item的status属性设置为pending,并将created_by属性设置为当前用户ID。 我还需要将user_item的user_id属性设置为当前用户ID。 我在控制器中设置了它。 我想知道如果设置这个就像我在创建动作中是正确的方法。

你不需要它,因为你的模式是错误的:

 @user_item = @item.user_items.build(user_id: current_user.id, picture: item_params[:item][:picture]) 

使用上面的代码,您将从每个@item创建单独的user_items 。 这不需要accepts_nested_attributes_for ,但是很麻烦,违反常规和非常hacky。


这是应该如何做的:

楷模

 #app/models/item.rb class Item < ActiveRecord::Base has_many :user_items has_many :users, through: :user_items accepts_nested_attributes_for :user_items end #app/models/user_item.rb class UserItem < ActiveRecord::Base belongs_to :user belongs_to :item end 

控制器

 #app/controllers/items_controller.rb class ItemsController < ApplicationController def new @item = Item.new @item.user_items.build end def create @item = Item.new item_params redirect_to @item, notice: "Thank you for your item submission." if @item.save end private def item_params params.require(:item).permit(:name, :description, :tag_list, user_items_attributes: [:picture]) end end 

查看

 #app/views/items/new.html.erb <%= simple_form_for @item do |item_builder| %> <%= item_builder.input :name, required: false, error: false, label: "Item name" %> <%= item_builder.input :description, as: :text, required: false, error: false, label: "Describe item" %> <%= item_builder.input :tag_list, required: false, label: "Tags" %> <%= item_builder.simple_fields_for :user_items do |user_item_builder| %> <%= user_item_builder.input :picture, as: :file, required: false, label: "Picture of you with this item" %> <% end %> <%= item_builder.submit 'Submit new item', class: "btn btn-primary pull-right inherit-width" %> <% end %> 

我正在做的方式有问题吗?

从技术上讲, 没有

但是,如果你渴望成为一名专业人士,或者在类似的水平上,那么你所写的代码就不会太过分了。

使用像Rails这样的框架可以访问前所未有的预烘焙function。 最好的代码不是您编写的代码,而是经过多年生产使用而编译和测试的库代码。

虽然您的代码有效,但它不是高效的,也不是可扩展的 。

-

你所写的是“正确”的最终关键在于,未来你是否会为再次看待它而自豪。 如果没有,你可能是最好的重构。


更新

如果要为Item提供status ,您需要查看ActiveRecord模型的enum模块:

 #app/models/item.rb class Item < ActiveRecord::Base enum status: [:active, :pending, :declined] end 

这是一个非常有趣的方法,因为它提供了一系列类方法(范围)和实例方法:

 @item = Item.find x @item.active? #-> true @item.pending? #-> false @item.declined? #-> false Item.active #-> collection of "active" items Item.pending #-> collection of "pending" items Item.declined #-> collection of "declined" items 

要保存项目的status ,可以使用collection_select

 <%= form_for @item do |f| %> <%= f.collection_select :status, Item.statuses, :first, :first %> <%= f.submit %> <% end %> 

更新

您的代码可以大量改进:

 def create @item= Item.new item_params #-> this line should do ALL the heavy lifting. if @item.save redirect_to items_path, notice: "Thank you for your item request!" else render :new end end private def item_params params.require(:item).permit(:name, :description, :tag_list, user_items_attributes: [:picture]).merge(created_by: current_user.id) end 

如果您有validation问题,则不会再次填充file字段; 这是一个操作系统问题,而不是Rails(操作系统如何知道您的文件与您第一次提交时的位置相同?)。

您需要使您的create代码尽可能简洁; 属性的显式声明通常是个坏主意。 您应该尽可能多地将其放入Item模型( enum等)中。

虽然它似乎有效,但是我的方式有问题吗?

它的function很棒 – 但它确实有代码味道 。 您的控制器可以使用属于模型层的大量业务逻辑,并且您将被迫在更新操作中使用参数检查重复整个混乱。

那么让我们看一下Rails方式

 class Item enum status: [:pending, :approved, :awesome] # or whatever statuses you have has_many :user_items belongs_to :creator, class_name: 'User' # or author or whatever validates_associated :user_items accepts_nested_attributes_for :user_items, reject_if: :all_blank? end class UserItem belongs_to :item validates :picture, presence: true end class User has_many :items, source: :creator, inverse_of: :creator end 

这里我们将项目设置为accepts_nested_attributes_for :user_items并且我们为UserItem设置validation, validates_associated将导致Itemvalidation失败,除非所有关联的UserItem都有效。

ActiveRecord :: Enum是一种不那么愚蠢的方式来做我认为你在使用item.status做的item.status

我们的模型经过处理,我们可以继续使用控制器:

 class ItemsController before_action :set_item, only: [:show, :edit, :update, :destroy] def new @item = Item.new @item.user_items.new # seeds the form end def edit @item.user_items.new unless @item.user_items.any? # seeds the form end def create @item = Item.new(item_params) do |item| item.status = :pending item.creator = current_user end if @item.save redirect_to items_path, success: 'Oh yes' else render :new, error: 'Oh noes' end end def update if @item.update(item_params) redirect_to @item, success: 'Item updated' else render :edit, error: 'Oh noes' end end private def set_item @item = Item.includes(:user_items).find(params[:id]) end def item_params params.require(:item) .permit(:name, :description, :tag_list, user_items_attributes: [:picture]) end end 

这是MVC 的Rails方式 – 模型负责validation和关联。 我们使用强参数以可重用的方式将表单输入映射到模型。

最后让我们清理表格。 我们将首先将表单提取为部分,以便可以重复使用。

 <% # views/items/_form.html.erb %> <%= simple_form_for @item do |item_builder| %> 
<%= item_builder.input :name, require: false, label: "Item name" %> <%= item_builder.input :description, require: false, as: :text, label: "Describe item" %> <%= item_builder.input :tag_list, label: "Tags", require: false %> <%= item_builder.simple_fields_for :user_items do |user_item_builder| %> <%= user_item_builder.input :picture, as: :file, label: "Picture of you with this item" %> <% end %>
<%= item_builder.submit %>
<% end %>

然后我们可以将此表单用于newedit

 

Create a new item

<%= render partial: '_form' %>

 

Edit <%= @item.name %>

<%= render partial: '_form' %>