【发布时间】: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