Rails - 为什么我的嵌套模型表单不需要&accept; accepted_nested_attributes_for'

时间:2016-02-09 03:57:27

标签: ruby-on-rails ruby-on-rails-4

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

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

表格

<%= simple_form_for @item, url: items_path, method: :post do |item_builder| %>
  <div class="well">
  <%= 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 @item, @item.user_items.build do |user_item_builder| %>
    <%= user_item_builder.input :picture, as: :file, required: false, label: "Picture of you with this item" %>
  <% end %>
  </div>
  <div class="clearfix">
    <%= item_builder.submit 'Submit new item', class: "btn btn-primary pull-right inherit-width" %>
  </div>
<% end %>

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. 验证失败后,我收到了模板错误,因此我添加了elserender :new

  2. 现在验证失败时,会渲染新模板,但不显示图片的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。我在控制器中设置了它。我想知道如果像创建动作那样设置它是正确的方法。

2 个答案:

答案 0 :(得分:4)

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

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

使用上面的代码,您可以从每个user_items创建单独的@item。这不需要accepts_nested_attributes_for,但是很麻烦,反对convention而且非常苛刻。

以下是如何做到的:

<强>模型

#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这样的框架,您可以访问前所未有的预烘焙功能。最好的代码不是你编写的代码,而是编译过的库代码。经过多年的生产使用测试。

虽然您的代码有效,但效率不高,extensible

-

您所写的内容的最终关键是&#34;对&#34;未来你是否会为再次看待它而自豪。如果没有,你可能最好重构。

<强>更新

如果您想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

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

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

答案 1 :(得分:1)

  

虽然它似乎有效,但我的方式是否存在问题   这样做?

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

让我们看一下 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的验证,validates_associated将导致项验证失败,除非所有关联的UserItem都有效。

ActiveRecord::Enum使用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方式 - 模型负责验证和关联。我们使用强参数以可重用的方式将表单输入映射到模型。

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

<% # views/items/_form.html.erb %>
<%= simple_form_for @item do |item_builder| %>
  <div class="well">
    <%= 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 %>
  </div>
  <div class="clearfix">
    <%= item_builder.submit %>
  </div>
<% end %>

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

<h1>Create a new item</h1>
<%= render partial: '_form' %>
<h1>Edit <%= @item.name %></h1>
<%= render partial: '_form' %>