Rails - 验证复杂的嵌套表单

时间:2016-02-10 16:01:12

标签: ruby-on-rails forms validation

我有一个包含四个输入字段的表单,用户可以在其中创建item。在表单中,四个输入字段之一用于图片,用于在表单提交的同时创建user_item。我遇到了几个问题。

  1. item验证失败时,我有控制器render :new但是当发生这种情况时,图片输入字段不可见。

  2. 对图片存在的验证无效。

  3. 我需要一种方法在创建user_id时将current_user属性设置为user_item

  4. item.rb的

      validates :name, presence: true, uniqueness: true
      validates :description, presence: true
      has_many :tags
      has_many :user_items
      has_many :users, -> { uniq }, through: :user_items
      belongs_to :user
    
      accepts_nested_attributes_for :user_items
      validates_associated :user_items
    

    user_item.rb

      belongs_to :user
      belongs_to :item
      mount_uploader :picture, PictureUploader
      validates_presence_of :picture
      validate :picture_size
    

    items_controller.rb

      def new
        @item = Item.new
        @item.user_items.build
      end
    
      def create
        @item = item.new item_params
    
        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
    

    new.html.erb

    <%= simple_form_for @item, html: { class: "create-item-form" } 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: "Description of item" %>
      <%= item_builder.input :tag_list, required: false, label: "Tags (these will help users find your item)" %>
      <%= 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 %>
      </div>
      <div class="clearfix">
        <%= item_builder.submit 'Submit new item request', class: "btn btn-primary pull-right inherit-width" %>
      </div>
    <% end %>
    

1 个答案:

答案 0 :(得分:1)

  

我需要一种方法来设置user_id

user_id添加到user_item的最简单方法是在fields_for中添加hidden_field。不是最安全的,但应该有效:

#app/views/items/new.html.erb
...
<%= 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" %>
    <%= user_item_builder.input :user_id, as: :hidden, input_html: { value: current_user.id } %>
<% end %>

#app/controllers/items_controller.rb
...
def item_params
   params.require(:item).permit(:name, :description, :tag_list, user_items_attributes: [:picture, :user_id]).merge(created_by: current_user.id)
end
  

图片输入字段不可见

根据这个答案:Nested Input Disappears When Form Reloads,您需要重建picture个对象:

def create
   if @item.save
     ...
   else
     @item.user_items.build
     render :new
   end
end

file_field输入特别有趣。由于您的操作系统无法保证您的文件与它们完全相同,因此未填充file_field

  

对图片存在的验证无效。

您应该使用inverse_of来确保两个对象可以相互通信:

#app/models/user.rb
class User < ActiveRecord::Base
  has_many :user_items, inverse_of: :user
end

#app/models/user_item.rb
class UserItem < ActiveRecord::Base
  belongs_to :user, inverse_of: :user_items
  validates :picture, presence: true
end

<强>更新

如果您想通过后端传递user_id,而不是隐藏字段,那么您可以执行以下操作:

#app/controllers/items_controller.rb
class ItemsController < ApplicationController
  def new
    @user = current_user
    @user.user_items.build.build_item
  end

  def create
    @user = current_user.update user_params
  end 

  private

  def user_params
     params.require(:user).permit(user_items_attributes: [:picture, item_attributes: [:name, :description, :tag_list])
  end
end

必须同时对items#new视图进行以下更改:

<%= simple_form_for @user, url: items_path, html: { class: "create-item-form" } do |f| %>
  <%= f.fields_for :user_items do |user_item_builder|
    <%= user_item_builder.input :picture, as: :file, required: false, label: "Picture of you with this item" %>
    <%= user_item_builder.fields_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: "Description of item" %>
        <%= item_builder.input :tag_list, required: false, label: "Tags (these will help users find your item)" %>
    <% end %>
  <% end %>
  <%= f.submit 'Submit new item request', class: "btn btn-primary pull-right inherit-width" %>
<% end %>

您还需要通过相应的模型传递属性:

#app/models/user.rb
class User < ActiveRecord::Base
  has_many :user_items
  has_many :items, 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

  accepts_nested_attributes_for :item
end