Building Form for Multiple File Upload in Rails Way
Contents
Background
When you build a form in “Rails way”, you don’t have to do much on your own. It automatically validates the parameter, and re-renders the form with proper messages in the event of errors.
However, when it comes to file upload form, things get complicated. For instance, when the form is redisplayed because of the form error, the file input value is lost. You don’t want to make users select the same file again, just because they mistyped their email address or phone number. Especially when they chose a bunch of files to upload. It’s also bad in terms of website performance (We don’t want them to send large image files again and again).
We could use javascript file upload plugins (lots of them are opensource, cool, well animated and fantastic). But they normally make source code difficult to manage if you’ve been doing things in “Rails way”.
Here I’ll introduce a way to build a file upload form in “Rails way”, which gets along with Rails activeform and requires minimum effort to build, but still does enough. Concretely,
- Uploads multiple files at a time.
- Can add/remove multiple file input fields dynamically.
- Shows preview when an image file is chosen.
- If an errors exist in the form, the file inputs memorize and redisplay the selected file.
- Also, the server caches the once-uploaded-file in the event of form redisplay, so no additional network traffic occurs.
- Can remove existing files.
- Use plain html form, NOT xhr (therefore, no file drag and drop feature).
See Github Repository for the complete app.
Gems to Use
CarrierWave
Use CarrierWave to handle files in the app.
You could also consider using the following.
- Dragonfly
- Paperclip (but this doesn’t support file cache )
Cocoon
Cocoon helps generating multiple input fields for has_many relation models.
Instruction
Scaffolding the App
New up a rails app, and add the following lines to Gemfile, then run bundle install
.
|
|
Create models and carrierwave uploaders.
|
|
And make models looks like followoing.
|
|
Form for has_many relation with Cocoon
Add the following lines inside form_for
block in the default user form.
|
|
Then, create the following partial file.
|
|
link_to_add_association
and link_to_remove_association
are helper methods provided by cocoon that generates link tags. These links dynamically adds/removes nested form for has_many relation model.
_user_photo_fields.html.slim
is the template included as nested form. You have to add the following line to application.js to take advantage of cocoon. (Also, you shouldn’t change div.nested-fields
surrounding link_to_remove_association
, because cocoon is depending on it)
|
|
tweak strong_parameter so that the backend accepts parameters for user_photos.
|
|
Now open up a browser and go to /users/new, you can at least create a user with photos through the form.
Showing Thumbnail
Currently if you go to /users/:id, only name of the user is shown. To show the thumbnails of the photos, add the following line to show.html.slim.
|
|
and some styles.
|
|
It’d be nice if you can also see thumbnails right after choosing photos. Add some javascript and style change to achieve this.
|
|
Pretiffy the nested form a bit by putting them in table tag.
|
|
Wrap nested forms in tr.
|
|
Add some styles.
|
|
Redisplaying thumbnail on validation fail
Now select some files and submit the form with name field being blank. The validation fails, and you see the selected files are competely lost in the redisplayed form. Carrierwave has a nice feature for handling this situation. It not only lets us keep track of the uploaded files, but also caches them so that we don’t have to send the same ones on every validation fail.
What we need to do is just to addd a hidden field for cached photo as #4 below shows. Then CarrierWave automatically associates the model with cached file and saves when validation passes. (if you select an another new file, this cache will be ignored)
|
|
Add photo_cache to strong_parameter
|
|
Rejecting Empty Photo Fields
If you fill in the name, click ‘add a photo’ button several times and post it, you’ll see models with no photo are created. To avoid this, use reject_if
option.
|
|
You could also add a validator on the UserPhoto model to prevent creation of model with empty file.