Slowboy Slowboy - 1 month ago 15
Ruby Question

Multiple image uploads with paperclip/active admin

Hi I want to be able to upload multiple pictures for a

product
instead of just one as a can now through the code below.

I'm not sure how to manage my client request since I'm rather unexperienced working with
active admin
and
paperclip


I have googled around and checked out various posts on Stack Overflow, but I haven't found a solution yet. Any suggestions or help would be great....

This is the
product
model

class Product < ActiveRecord::Base
belongs_to :category
belongs_to :label

has_many :product_items, :dependent => :destroy

extend FriendlyId
friendly_id :title, use: [:slugged, :finders]


validates :title, :description, presence: true
validates :price_usd, :price_eu, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true


has_attached_file :image, styles: { medium: "500x500#", thumb: "100x100#" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/


def self.search(query)

where("title LIKE ? OR description LIKE ?", "%#{query}%", "%#{query}%")
end
end


and this is the
app/admin/product.rb


ActiveAdmin.register Product do


permit_params :title, :slug, :description, :stock_quantity, :image, :price_usd, :price_eu, :category_id, :label_id

index do
column :title
column :slug
column :category
column :label
column :created_at
column :stock_quantity

column :price_eu, :sortable => :price_eu do |product|
number_to_currency(product.price_eu, :unit => " € " , :precision => 0)
end
column :price_euro, :sortable => :price_usd do |product|
number_to_currency(product.price_usd, :unit => " $ " , :precision => 0)
end

actions

end

form do |f|
f.inputs do
f.input :title
f.input :slug
f.input :description, as: :ckeditor, input_html: { ckeditor: { toolbar: 'Full' } }
f.input :stock_quantity
f.input :image
f.input :price_usd
f.input :price_eu
f.input :category
f.input :label
end
actions
end

end


And here is the
products_controller.rb


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


def show
@meta_title = "Samoli #{@product.title}"
@meta_description = @product.description

end

def search

@product = Product.search(params[:query]).order("created_at DESC")
@categories = Category.joins(:products).where(:products => {:id => @product.map{|x| x.id }}).distinct


end

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

# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:title, :description, :price_usd, :price_eu, :image, :category_id, :stock_quantity, :label_id, :query, :slug)
end
end

Answer

I would suggest using something like jQuery file upload to handle the file uploads for you.

That way your controller still only handles one file upload at a time, though you can upload many files at a time as each upload is handled separately via an Ajax call.

I have tried other alternatives, but trying to post more than one file to a server at a time, you quickly run into server timeout issues (especially on heroku).

Here is a gem you can wire into ActiveAdmin

https://github.com/tors/jquery-fileupload-rails

Let me know if you need more help on the implementation.

UPDATE: (please see comments for context)

Here is some example code illustrating how to implement the code in active admin. I know it looks like a lot of code but just work through it step by step and you will see its pretty simple.

Product model:

class Product < ApplicationRecord
  has_many :photos
end

Photo model:

class Photo < ApplicationRecord
  include ActionView::Helpers

  belongs_to :product
  has_attached_file :image, :styles => { large: "500x500>",thumb: "100x100>" }
  validates_attachment_content_type :image, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]

  def thumb
    link_to(image_tag(image.url(:thumb)), thumb_url)
  end

  private

  def thumb_url
    Rails.application.routes.url_helpers.admin_product_photo_path(product, self)
  end
end

Then in active admin do the following.

ActiveAdmin Product:

ActiveAdmin.register Product do
  permit_params :title

  form do |f|
    f.inputs do
      f.input :title
    end
    f.actions
   end

  index do
    column :title
    column :images do |product|
      product.photos.map do |photo|
        link_to (image_tag photo.image.url(:thumb)), [:admin, photo.product, photo]
      end.join.html_safe
    end
    actions
  end

  show do
    attributes_table
    panel "Images" do
      div class: "js-product_photos" do
        product.photos.map do |photo|
          link_to (image_tag photo.image.url(:thumb)), [:admin, photo.product, photo]
        end.join.html_safe
      end
      div do
        semantic_form_for [:admin, resource, Photo.new], multipart: true do |f|
          f.inputs do
            f.input :image, as: :file,
                            input_html: {
                              class: 'js-photo_upload',
                              type: "file",
                              name: "photo[image]",
                              multiple: true
                            }
          end
         end
      end
    end
  end
end

Note the html options defined in the form. That is where jQuery upload derives a lot of its options. the form url is also important.

I could have added the form anywhere, but I think it works well on the product show page.

ActiveAdmin Photo:

ActiveAdmin.register Photo do
  belongs_to :product
  permit_params :image

  controller do
    def create
      create! do |format|
        format.json { render :json => {thumb: resource.thumb} }
      end
    end
  end

  show do
    attributes_table do
      row :product
      row :image do |product|
        image_tag product.image.url(:large)
      end
    end
  end
end

and finally in the active_admin.js.coffee

#= require active_admin/base
#= require jquery-fileupload/basic

$ ->
  $('.js-photo_upload').fileupload dataType: 'json', done: (e, data) ->
    $('.js-product_photos').append data.result.thumb

And that's it! The files should upload via an AJAX call as soon as you select them. Once uploaded an image tag will be appended to the list of images. You can select more than one image at a time

This really just scratches the surface of what the basic jQuery file uploader can do - read more about it here. https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin

Just for reference the app I built is a rails 5 app, here are the gems that were important for this example:

gem 'activeadmin', github: 'activeadmin'
gem 'inherited_resources', github: 'activeadmin/inherited_resources'
gem 'devise'
gem 'paperclip', "~> 5.0.0"
gem "jquery-fileupload-rails" 

UPDATE: Based on a further question

Now that you are able to upload images, you can display them on for example the product show page (show.html.erb):

<h1><%= @product.title %></h1>
<% @product.photos.each do |photo| %>
  <%= image_tag(photo.image.url(:large) %>  
<% end %>