【问题标题】:Rails - Validating complex nested formRails - 验证复杂的嵌套表单
【发布时间】:2016-05-21 02:05:47
【问题描述】:

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

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

  2. 验证图片是否存在无效。

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

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 %>

【问题讨论】:

    标签: ruby-on-rails forms validation


    【解决方案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
    

    【讨论】:

    • 感谢您的回答。只是几个问题。如果使用隐藏字段“不是最安全的”,为什么它仍然比控制器中的显式属性声明更好?事实证明,一旦我添加了隐藏字段,我不再需要在 if 语句中重建图片对象。最后,很明显,现在我最初的问题是一团糟,除了在这里编写代码和提问之外,您还有什么建议可以帮助我改进我的 ruby​​ on rails 编码(资源、书籍等)吗?
    • 在控制器中声明值是很棘手的,因为它是嵌套的。我想你可以试试merge,但需要调整。它不是“安全的”,因为如果它是 HTML,人们可以更改它。
    • 资源方面,除了练习,还需要找一个非常非常非常非常好的人;和他们一起工作。这就像如果你去健身房,如果你和一个敬业的人一起锻炼,你最终会接受他们的习惯,比单独锻炼要好得多。软件有些相似;非常非常非常优秀的开发人员会不断突破界限并激发您的想法。我个人会远离无聊的教程,专注于完成工作。免费工作,制作宝石,谈论很酷的想法。制作游戏。
    • 在我对this 问题的编辑中,我在控制器中有@item.user_items.first.user_id = current_user.id 行。你认为隐藏字段是更好的方法吗?
    • 如果您想要多个user_item 会怎样? (考虑到你打电话给user_items.first
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-02
    相关资源
    最近更新 更多