0

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

danielpiestrak
  • 5,279
  • 3
  • 30
  • 29
  • What exactly is the unexpected behavior you are observing? Doese `@product.save` return true? If so have you checked which if any attributes of the `product_attachments` have been set? Also what dies "controller complains" mean? – Jan Bussieck May 20 '16 at 14:05
  • You appear to be trying to save the `product_attachments` from within the products controller. This isn't the right way to do things with nested attributes. If that loop is working, then I think your your form has a problem, the `product_attachments` shouldn't be in the top level of your params. It should be nested within product. – DickieBoy May 20 '16 at 14:24
  • @JanBussieck Thanks for the questions. The unexpected behavior is as follows: When I add the child 'product_attachment' from tis own form Its validation is successful, meaning ti wont let me submit an attachment without a file. However, if I submit a new Product, it ignores the validation on the attachment and lets me submit the form because product.save returns true despite the attachment being blank. I'll edit the above in a few seconds with the 'complain' I get when the controller tries to save with a null attachment after it skips validation. – danielpiestrak May 20 '16 at 14:31
  • @DickieBoy Thanks for the idea here. I'm trying out some code variations now around this idea to see if anything works. – danielpiestrak May 20 '16 at 14:41

1 Answers1

1

Seeing your latest edit, I figured that was happening, which means product_attachments is nil. Which is correct, the param should be product_attachments_attributes.

This leads to another problem in your product_params method.

You have product_attachment_attributes, the the base form "field" should be pluralised: product_attachments_attributes

In short:

Remove the product attachment loops from your controller. Fix the strong params method, it should work after that.

Edit:

Also remove the name attribute from the file_field That isn't helping.

DickieBoy
  • 4,886
  • 1
  • 28
  • 47
  • Thank you for the direction, testing out these variations now. – danielpiestrak May 20 '16 at 14:41
  • I removed the name attribute from your edit. I'm removing the loops and testing your strong params catch. so surprised i missed that one. FYI though, I got the loop from this answer here which worked, but i'm not sure why. Curious if removing ti at this point will work after your suggestions. http://stackoverflow.com/questions/21411988/rails-4-multiple-image-or-file-upload-using-carrierwave – danielpiestrak May 20 '16 at 14:51
  • These updates made it so the child validation isn't passing so product.save keeps failing without many details. Not sure yet how to articulate or debug for more info, will look into it throughout the day. As a new Web/Rails developer coming from the backed world learning debugging has been my biggest hurdle. Will be testing some things out / skimming the amazon books i bought / referencing documentation for gems / and commentign out code blocks so that I can update with better info. – danielpiestrak May 20 '16 at 16:19
  • For a decent debugging experience put these gems in your development group `gem 'pry-byebug' gem 'pry-stack_explorer' gem 'pry-rails'`, this also gives you webconsole. – Jan Bussieck May 20 '16 at 17:18