I've been worrying this bug for too many days and I'm not sure what I'm missing.
I have a parent model named 'product' and a child of it 'product_attachment'. Validation within child model is successful on create to disallow a blank image field via /product_attachments#new
However, when using its parent form /product#new (files below) I'm expecting it to validate to successfully fail without an image. However, Activerecord is ignoring the error, and my controller is tryign to save as a result if the validation passing then complaining that my product_attachments is null.
I'm currently in the understanding that using the 'validates_associated' would validate the child model as part of the Parent's process but this has not been working. Instead of passing a happy failed validation mid-form to allow user to take action, we leave form and the controller tries to process the create method which fails due to no attachment. Since I should always have an attachment have been trying to fix this validation to no avail.
Any help appreciated, I've include similar code samples before for your feedback. I'm fairly new to rails so I'm hoping I'm mis-using a key syntax or context.
Also curious what cause is and what best way was to troubleshoot as I'm still developing good debug practices.
product.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 to handle uploading used here, seems to work fine)
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 (using simple_form + slim + cocoon here...)
= 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 Noted I needed to name my file field this way for my controller to use files correctly, but unsure why still.
.nested-fields
= f.file_field :image , :multiple => true , name: "product_attachments[image][]"
= link_to_remove_association "remove", f
Let me know if I can provide anything else.
Thank you for your time to read/reply.
Edit1: My current method to debug I'm working through as of writing this is to strip out code chunks and test functionality by through browser. I've read I should be more familiar with rails console but have not got there yet.
Edit2:
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: Reworded original section for clarity