Massuploading pictures using ActiveStorage

ActiveStorage is a gem, I really think so. Before AS, whenevener I needed to attach a “blob” (image or other document) to a model it took a day or two to update myself on the inner workings of Paperclip or Carrierwave. Both great tools but always a bit too complicated for my liking.

With ActiveStorage, you just add it to the project and migrate (since it needs two db tables to do its thing). Then you add an “has_one_attached :image” ior possibly a “has_many_attached :image” statements to you model and Bob’s your uncle!

However recently I had a different use case. I wanted to upload a bunch (10’s, hundreds, thousands?) of image files, and I wanted to automatically create one model instance for each image. Uploading the images one by one would take forever, since I have many thousands of photos. But batch uploading them, and then add captions and tagging afterwards sounded like a better idea. Maybe also friends and relatives could help out with that part.

I had this model definition

class Photo < ApplicationRecord
has_one_attached :image
end

and I tried this upload form (since I don’t have a model instance at the time of uploading.

<%= form_with(url: '/home/work', id: 'my-upload', 
    method: 'post', local: true
    <%= file_field_tag "upload[image][]", multiple: true %> <%= submit_tag("Upload") %>
<% end %>

Of course it was tricky with the “upload[image][]” string. I wish Rails could come up with a more type-safe way of specifying to Rack what is passed in the pramaters.

Anyhow, it arrives to the controller in the form:

{"authenticity_token"=>"SNIP", "upload"=>{"image"=>["image2.jpg", "image3.jpg", 
"image4.jpg", "image5.jpg"]}, "commit"=>"Upload", 
"controller"=>"home", "action"=>"do_upload"}

upload[“image] is an array of filenames, which is not what I wanted?!? I expected an array of #<ActionDispatch::Http::UploadedFile

I also tried a simpler version using the form_tag:

<%= form_tag ({:controller => "home", :action => "work"}) do %>
  <%= file_field_tag "upload[image][]", :multiple => true %>
  <%= submit_tag("Upload") %>
<% end %>

So what is it that adds that magic? This is where Rails sometimes can feel a bit hard to grasp. There are so much “convenience” under the hood that under normal circumstances “just work”, so when you step out of the normal range of operations you can sometimes feel a bit lost in space.

It took A LOT of googling and reading example code, but here it is:

Add this to your form_with:

:html => {:enctype =>"multipart/form-data"}

And if you are using form_tag, skip the :html part

:enctype =>"multipart/form-data"

Now in your controller it’s easy to create instances of your model:

params["upload"]["image"].each do |i|
  ph = Photo.create(:title => "Untitled", :comment => "No comment!", :image => i)
end

My nest step is to create an MD5 hash of the image, to avoids uploading the same image twice, but that will be in my next article!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.