【问题标题】:Rails Activerecord, Nested ValidationsRails Activerecord,嵌套验证
【发布时间】:2016-09-17 19:13:56
【问题描述】:

我已经担心这个错误太多天了,我不确定我错过了什么。

我有一个名为“product”的父模型和一个名为“product_attachment”的子模型。 通过 /product_attachments#new

在创建时成功验证子模型以禁止空白图像字段

但是,当使用其父表单 /product#new(以下文件)时,我希望它能够在没有图像的情况下成功验证失败。但是,Activerecord 忽略了错误,如果验证通过然后抱怨我的 product_attachments 为空,我的控制器会尝试保存结果。

我目前的理解是,使用“validates_associated”将验证子模型作为父进程的一部分,但这并没有奏效。 我们没有在表单中传递一个快乐的失败验证以允许用户采取行动,而是离开表单,控制器尝试处理由于没有附件而失败的 create 方法。 由于我应该始终有附件,因此一直试图修复此验证无济于事。

感谢您的帮助,我之前已包含类似的代码示例以供您提供反馈。 我对 Rails 很陌生,所以我希望我误用了关键语法或上下文。

我也很好奇是什么原因以及最好的解决方法是什么,因为我仍在开发良好的调试实践。

产品.rb

class Product < ActiveRecord::Base

  has_many :product_attachments
  validates_presence_of :title, :message =>  "You must provide a name for this product."
  accepts_nested_attributes_for :product_attachments, allow_destroy: true#, 
  validates_associated :product_attachments
end

product_attachment.rb (这里使用carrierwave来处理上传,似乎工作正常)

class ProductAttachment < ActiveRecord::Base
  belongs_to :product 
  mount_uploader :image, ImageUploader
  validates_presence_of :image, :message =>  "You must upload an image to go with this item."
end

products_controller.rb

class ProductsController < ApplicationController
  before_action :set_product, only: [:show, :edit, :update, :destroy]

  def index
    @products = Product.all
  end

  def show
    @product_attachments = @product.product_attachments.all
  end

  def new
    @product = Product.new
    @product_attachment = @product.product_attachments.build
  end

  def edit
  end

  def create
    @product = Product.new(product_params)
    respond_to do |format|
      if @product.save
        params[:product_attachments]['image'].each do |a|
          @product_attachment = @product.product_attachments.create!(:image => a)
        end
        format.html { redirect_to @product, notice: 'Product was successfully created.' }
      else #- when would this fire?
        format.html { render :new }
      end
    end
  end

  def update
    respond_to do |format|
      if @product.update(product_params)
          params[:product_attachments]['image'].each do |a|
            @product_attachment = @product.product_attachments.create!(:image => a, :post_id => @post.id)
          end
        format.html { redirect_to @product, notice: 'Product was successfully updated.' }
      else #- when would this fire?
        format.html { render action: 'new' }
      end
    end
  end

  def destroy
    @product.destroy
    respond_to do |format|
      format.html { redirect_to @product, notice: 'Product was successfully destroyed.' }
    end
  end

  private
    def set_product
      @product = Product.find(params[:id])
    end
    # we pass the _destroy so the above model has the access to delete
    def product_params
      params.require(:product).permit(:id, :title, :price, :barcode, :description, product_attachment_attributes: [:id, :product_id, :image, :filename, :image_cache, :_destroy])
    end

end

product_attachments_controller.rb

class ProductAttachmentsController < ApplicationController
  before_action :set_product_attachment, only: [:show, :edit, :update, :destroy]

  def index
    @product_attachments = ProductAttachment.all
  end

  def show
  end

  def new
    @product_attachment = ProductAttachment.new
  end

  def edit
  end

  def create
    @product_attachment = ProductAttachment.new(product_attachment_params)

    respond_to do |format|
      if @product_attachment.save
        @product_attachment.image = params[:image]
        format.html { redirect_to @product_attachment, notice: 'Product attachment was successfully created.' }
      else
        format.html { render :new }
      end
    end
  end

  def update
    respond_to do |format|
      if @product_attachment.update(product_attachment_params)
        @product_attachment.image = params[:image]
        format.html { redirect_to @product_attachment.product, notice: 'Product attachment was successfully updated.' }
      else
        format.html { render :edit }
      end
    end
  end

  def destroy
    @product_attachment.destroy
    respond_to do |format|
      format.html { redirect_to product_attachments_url, notice: 'Product attachment was successfully destroyed.' }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_product_attachment
      @product_attachment = ProductAttachment.find(params[:id])
    end

    def product_attachment_params
      params.require(:product_attachment).permit(:id, :product_id, :image, :image_cache)
    end
end

_form.html.slim (这里使用 simple_form + slim + cocoon...)

= simple_form_for @product do |f|

  - if @product.errors.any?
    #error_explanation
      h2
        = pluralize(@product.errors.count, "error")
        |  prohibited this product from being saved:
      ul 
        - @product.errors.each do |attribute, message|
          - if message.is_a?(String)
            li= message

  = f.input :title
  = f.input :price, required: true
  = f.input :barcode
  = f.input :description

  h3 attach product images
  #product_attachment
    = f.simple_fields_for :product_attachments do |product_attachment|
      = render 'product_attachment_fields', f: product_attachment
    .links
      = link_to_add_association 'add product attachment', f, :product_attachments
  = f.submit

_product_attachment_fields.html.slim 注意到我需要以这种方式命名我的文件字段,以便我的控制器正确使用文件,但仍然不确定为什么。

.nested-fields
  = f.file_field :image , :multiple => true , name: "product_attachments[image][]"
  = link_to_remove_association "remove", f

如果我能提供其他任何东西,请告诉我。

感谢您抽出宝贵时间阅读/回复。

Edit1: 在撰写本文时,我目前的调试方法是剥离代码块并通过浏览器测试功能。我读过我应该更熟悉 Rails 控制台,但还没有到那里。

编辑2:

undefined method `[]' for nil:NilClass

        params[:product_attachments]['image'].each do |a|


app/controllers/products_controller.rb:47:in `block in create'
app/controllers/products_controller.rb:44:in `create'

Request

Parameters:

{"utf8"=>"✓",
 "authenticity_token"=>"{mytoken}",
 "product"=>{"title"=>"pleasefailwell",
 "commit"=>"Create Product"}

Edit3:为了清晰起见,对原始部分进行了重新措辞

【问题讨论】:

  • 您观察到的意外行为究竟是什么? @product.save 是否返回 true?如果是这样,您是否检查了 product_attachments 的哪些属性是否已设置?还有“控制器抱怨”是什么意思?
  • 您似乎正试图从产品控制器中保存product_attachments。这不是处理嵌套属性的正确方法。如果该循环正常工作,那么我认为您的表单有问题,product_attachments 不应该在您的参数的顶层。它应该嵌套在产品中。
  • @JanBussieck 感谢您的提问。意外行为如下:当我从自己的表单中添加子“product_attachment”时,它的验证成功,这意味着我不会让我提交没有文件的附件。但是,如果我提交一个新产品,它会忽略附件上的验证并让我提交表单,因为 product.save 会返回 true,尽管附件是空白的。我将在几秒钟内编辑上面的内容,当控制器在跳过验证后尝试使用空附件保存时得到的“抱怨”。
  • @DickieBoy 感谢您的想法。我现在正在围绕这个想法尝试一些代码变体,看看是否可行。

标签: ruby-on-rails ruby activerecord carrierwave cocoon-gem


【解决方案1】:

看到您的最新编辑,我认为这正在发生,这意味着 product_attachments 为零。这是正确的,参数应该是product_attachments_attributes

这会导致您的 product_params 方法出现另一个问题。

你有product_attachment_attributes,基本形式“字段”应该是复数形式:product_attachments_attributes

简而言之:

从控制器中移除产品附件环。修复 strong params 方法,之后应该可以工作了。

编辑:

同时从file_field 中删除name 属性,这没有帮助。

【讨论】:

  • 感谢您的指导,现在测试这些变化。
  • 我从您的编辑中删除了 name 属性。我正在删除循环并测试您的强大参数捕获。很惊讶我错过了那个。仅供参考,我从这个答案中得到了循环,它有效,但我不知道为什么。很好奇此时删除 ti 是否会在您提出建议后起作用。 stackoverflow.com/questions/21411988/…
  • 这些更新使得子验证没有通过,所以 product.save 在没有太多细节的情况下一直失败。尚不确定如何表达或调试更多信息,将全天研究。作为来自支持世界的新 Web/Rails 开发人员,学习调试一直是我最大的障碍。将测试一些东西/浏览我购买的亚马逊书籍/参考 gems 文档/并注释掉代码块,以便我可以更新更好的信息。
  • 为了获得体面的调试体验,请将这些 gem 放入您的开发组gem 'pry-byebug' gem 'pry-stack_explorer' gem 'pry-rails',这也会为您提供网络控制台。
猜你喜欢
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多