1

I have 2 models. Product and ProductImage. Product has_many ProductImages. ProductImage belongs_to Product.

In my New view. Everything creates perfect. I can add as many images as i need. Product and ProductImages persist.

However in the edit view it doesn't show the images. In the blow example you can see that there are 2 images for the product. However they don't show in the view. It shows whats below. How do i get the images to show so i can add or remove them in this edit view? enter image description here Product Form. Product Images are near the bottom.

<div class="container">
  <div class=“row”>
    <div class="col-md-6 col-md-offset-3">
      <div class="panel panel-primary">
        <div class="panel-body">
          <%= simple_nested_form_for product, html: { class: :js_product_form, id: "js_root_category_#{root_category.id}" } do |f| %>
            <h3><%= root_category.name %></h3>

            <%= f.collection_select :category_id, children_categories_allowed_for(root_category, product),
              :id, :name, include_blank: true, prompt: "Select One Category" %>
​
            <%= f.simple_fields_for :product_sizes do |psf| %>
              <% ps = psf.object %>
        ​
              <%= psf.input :quantity, label: "Quantity for size #{ps.size} for category #{ps.size.category}",
                wrapper_html: { 'data-size_category_id' => ps.size.category_id } %>
              <%= psf.hidden_field :size_id, value: ps.size_id %>
            <% end %>

            <hr>
            <%= f.input :title, label:"Title"%>
            <%= f.input :price, label:"Price"%>
            <%= f.input :description,label:"Description" %>
            <%= f.input :size_description, label:"Size Details"%>
            <%= f.input :shipping_description, label:"Shipping Details"%>
            <%= f.input :tag_list,label:"Tags - Seperate tags using comma ','. 5 tags allowed per product" %>

            <p><%= f.link_to_add "Add a image", :product_images, :data => { :product_image => "#product_images" } %></p>

            <%= f.fields_for :product_images do |product_image| %>
              <% if product_image.object.new_record? %>
                <%= product_image.file_field(:product_image) %>
                <%= product_image.link_to_remove "Remove Image", data: { confirm: "Are you sure you want to delete this image?" } %>
              <% else %>
                <%= product_image.hidden_field :_destroy %>
              <% end %>
            <% end %>
​
            <%= f.button :submit, "Create new product", class: "btn-lg btn-success" %>

          <% end %>
        </div>
      </div>
    </div>
  </div>
</div>
​

Product Controller

class ProductsController < ApplicationController
  before_action :authenticate_user!, only: [:new, :edit, :update, :destroy, :create]
  before_action :set_product, only: [:edit, :show, :update]
  before_action :correct_user_edit, only: [:edit, :update, :destroy]

  def index
    @products = Product.all
  end

  def new
    @product = Product.new
    @root_categories = Category.where(ancestry: nil).preload(:sizes).order(:name)
    @product.product_images.build

    @root_categories.each do |root_category|
      root_category.children.each do |category|
        category.sizes.each do |size|
          @product.product_sizes.build(size_id: size.id, quantity: 0)
        end
      end
    end
  end

  def edit
    category = @product.category         # T-Shirt
    @root_categories = [category.parent] # Men

    category.sizes.each do |size|
      @product.product_sizes.detect do |ps|
        ps.size_id == size.id
      end || @product.product_sizes.build(size_id: size.id, quantity: 0)
    end

    @product.product_images.build unless @product.product_images.any?
  end

  def show
  end

  def update
    if @product.update(product_params)
       redirect_to @product
       flash[:success] = 'Item was successfully updated.'
    else
      render "edit"
    end
  end

  def create
    @product = Product.new product_params
    @product.user_id = current_user.id
    @root_categories = Category.preload(:sizes).order(:name)

    if @product.save
      redirect_to @product
      flash[:success] = "You have created a new product"
    else
      flash[:danger] = "Your product didn't save #{@product.errors.full_messages}"
      render "new"
    end
  end


  private

  def set_product
    @product = Product.find(params[:id])
  end

  def product_params
     params.require(:product).permit(
      :title,
      :price,
      :description,
      :tag_list,
      :category_id,
      :size_description,
      :shipping_description,
      product_images_attributes: [:product_image, :_destroy, :id],
      product_sizes_attributes: [:size_id, :quantity, :id]
    )
  end

  def correct_user_edit
    if @product = current_user.products.find_by(id: params[:id])
    else
      redirect_to root_path if @product.nil?
    end
  end
end

Javascript

this.productForm = {
  showRootCategory: function () {
    var $forms  = $('form.js_product_form'),
        $select = $('select#js_root_category')

    if($forms.length > 1) {
      var categoryId = $select.val();
      var $selectedForm = $('form#js_root_category_' + categoryId);
    } else {
      var $selectedForm = $forms;
    }

    $forms.hide();
    $selectedForm.show();
  },

  showCategory: function() {
    var $form   = $('form.js_product_form'),
        $select = $('select#product_category_id'),
        $sizes  = $('[data-size_category_id]');

    $sizes.hide();
    if($select.val()) {
      $('[data-size_category_id=' + $select.val() + ']').show();
    }
  }
};

$(function() {
  productForm.showRootCategory();
  $('select#js_root_category').on('change', productForm.showRootCategory);

  productForm.showCategory();
  $('select#product_category_id').on('change', productForm.showCategory);
});

Here is the Product and ProductImage in console

2.1.2 :002 > p = _
 => #<Product id: 2, title: "Shorts", description: "description test", created_at: "2016-02-02 09:39:42", updated_at: "2016-02-02 09:39:42", user_id: 1, category_id: 3, price: #<BigDecimal:7fff230514d0,'0.49E2',9(27)>, size_description: "size test", shipping_description: "shipping test">
2.1.2 :003 > p.product_images
  ProductImage Load (0.2ms)  SELECT "product_images".* FROM "product_images" WHERE "product_images"."product_id" = ?  [["product_id", 2]]
 => #<ActiveRecord::Associations::CollectionProxy [#<ProductImage id: 2, product_id: 2, product_image_file_name: "20090a.jpg", product_image_content_type: "image/jpeg", product_image_file_size: 124938, product_image_updated_at: "2016-02-02 09:39:41">, #<ProductImage id: 3, product_id: 2, product_image_file_name: "David-Marvier-Beauty-Fashion-Photography17.jpg", product_image_content_type: "image/jpeg", product_image_file_size: 375907, product_image_updated_at: "2016-02-02 09:39:41">]>
2.1.2 :004 >

Error for Rich

    30:             <%= f.fields_for :product_images do |product_image| %>
    31:               <% if product_image.object.new_record? %>
    32:                 <%= product_image.file_field(:product_image) %>
    33:                 <%= product_image.link_to_remove "Remove Image", data: { confirm: "Are you sure you want to delete this image?" } %>
    34:               <% else %>
 => 35:               <%= binding.pry %>
    36:                 <%= image_tag product_image.object.attachment.url %>
    37:                 <%= product_image.hidden_field :_destroy %>
    38:               <% end %>
    39:             <% end %>
    40:

[1] pry(#<#<Class:0x007fc6fab8acb8>>)> product_image.object.attachment.url
NoMethodError: undefined method `attachment' for #<ProductImage:0x007fc6f363e6a8>
from /Users/josephkonop/.rvm/gems/ruby-2.1.2/gems/activemodel-4.2.0/lib/active_model/attribute_methods.rb:433:in `method_missing'
[2] pry(#<#<Class:0x007fc6fab8acb8>>)> product_image.object.attachment
NoMethodError: undefined method `attachment' for #<ProductImage:0x007fc6f363e6a8>
from /Users/josephkonop/.rvm/gems/ruby-2.1.2/gems/activemodel-4.2.0/lib/active_model/attribute_methods.rb:433:in `method_missing'
[3] pry(#<#<Class:0x007fc6fab8acb8>>)> product_image.object
=> #<ProductImage:0x007fc6f363e6a8
 id: 1,
 product_id: 1,
 product_image_file_name: "hero.jpg",
 product_image_content_type: "image/jpeg",
 product_image_file_size: 157977,
 product_image_updated_at: Tue, 02 Feb

ProductImage.rb

class ProductImage < ActiveRecord::Base
  belongs_to :product

  has_attached_file :product_image, styles: { large: "600x600", medium: "250x250", thumb:"100x100#"}, keep_old_files: true
  validates_attachment_content_type :product_image, content_type: /\Aimage\/.*\Z/
end
joeyk16
  • 1,357
  • 22
  • 49

1 Answers1

1

You have 2 issues:

  1. You cannot auto-populate file_field
  2. Paperclip overrides your attachment if the attributes are passed as nil

#Form
...
 <%= f.fields_for :product_images do |product_image| %>
      <% if product_image.object.new_record? %>
          <%= product_image.file_field(:product_image) %>
          <%= product_image.link_to_remove "Remove Image", data: { confirm: "Are you sure you want to delete this image?" } %>
      <% else %>
          <%= image_tag product_image.object.product_image.url %>
          <%= product_image.hidden_field :_destroy %>
      <% end %>
<% end %>

file_field won't populate with existing data.

If you want to work with existing images, you need to explicitly output the image (image_tag). As you're already evaluating whether the object is new_record?, it makes sense to output the image in the else bracket.

This will "show" the image.

We do it here (sign up and go to profile if you want test. It's a dummy app):

enter image description here


In terms of sending the attributes, you need several things:

#app/models/product.rb
class Product < ActiveRecord::Base
   has_many :product_images
   accepts_nested_attributes_for :product_images, allow_destroy: true
end


#app/models/product_image.rb
class ProductImage < ActiveRecord::Base
   belongs_to :product
   has_attached_file :product_image, keep_old_files: true
end

keep_old_files preserves any files which Paperclip could overwrite. The issue is that since file_field does not populate any data automatically, Paperclip interprets an empty set as "delete" the image.

I've forgotten if this is the solution (there were others), but it's something like it. In short, you want Paperclip to ignore empty update params.

Community
  • 1
  • 1
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • Thanks Rich. I added what you said to the products model. I had something very similar. I just didnt have allow_nil. However it appears allow_nil doesnt work. I get this error. Did you mean something else? `Unknown key: :allow_nil. Valid keys are: :allow_destroy, :reject_if, :limit, :update_only` – joeyk16 Feb 02 '16 at 11:34
  • Yes I meant `reject_if`, but it's probably best to remove for now – Richard Peck Feb 02 '16 at 11:42
  • Thanks Rich. Almost there. I added an error to my question. Its the second last snippet which is a binding.pry. Im assuming you're trying to get the url of the attachment. I've tried for awhile now. Do you know how? – joeyk16 Feb 02 '16 at 12:09
  • Just replace `attachment` in my code with `product_image` – Richard Peck Feb 02 '16 at 12:32
  • Hi Rich. If you want want a remote job building rails apps. Send me an email joseph@netengine.com.au – joeyk16 Feb 03 '16 at 11:48
  • Sorry bud I already have a job, in-between projects I go on SO. I have friends in Ukraine who can work freelance if you need. I worked with them in 2012 and they are very good. – Richard Peck Feb 03 '16 at 12:02