Adding multiple images to a Rails model with paperclip

The Goal

I have been working on improving the admin section of a ski club website. The club sells ski trips online and they need to be able to create new trips from the admin panel. A trip has a title, description, date and price. For marketing reasons when the trip information is displayed on the website a number of pictures related to the trip are also displayed in a slideshow. The goal was to allow admins to add a new trip to the website (including the images) through a single form. Up until now, admins had to create the trip text data and then add the images separately through another form.

To install paperclip add the following line to the environment.rb file and run rake:gems:install

config.gem "thoughtbot-paperclip", :lib => "paperclip"

The Models

Let’s start by generating the two models. I like to use the nifty_scaffold because it’s a quick way to get going. I will generate the model, controller and RESTful views. A trip has many images so we will need two models, but we don’t actually need the controller for the trip_image model:

Nested Attributes

What we need to do is add some fields to the trip form that will let the user upload trip images. But trip_image and trip are two different models, so how do we make it all work in one form? That’s where nested attributes come in.

This allows us to access the trip images straight from the trip object like this: @trip.trip_images. The :reject_if call makes sure that we do not store empty TripImage records – sometimes users will not add an image for the trip right away.

In the new and edit actions of the TripsController let’s initialize three new trip images. This will let the user upload up to three images for a trip at once. No other changes are needed in the controller! As you can see the create and update actions are completely standard.

fields_for in the form

Finally, in the form add the actual form fields that will be used to upload the images using fields_for. Also edit the form_for call and mark the form as multipart so it can accept file uploads (this is an important step, because otherwise the images just won’t upload).

{:multipart => true} do |f| %>
...

The if builder.object.new_record? call allows us to use this same form for the edit action. It makes sure that if the trip already has some images stored the builder will not display fields for them.

Final Notes

My goal was to keep this example as simple as possible. The code above let’s a user add images to a trip, BUT:

1. The user can only add up to three images at once.

If he wants to add 5 images he will have to add three and then edit the trip to add another two. This could be solved by dynamically generating fields using javascript – see the Nested Attributes Railscast for more details.

2. The user cannot delete images.

Again, this is a minimal example. See the railscasts for inspiration on how to do this.

accepts_nested_attributes_for takes the code block provided through reject_if and passes in the attributes hash of the nested attribute (in our case the trip image). The code block is then executed for every nested attribute and if it returns true the record will be saved.

Here’s the call in accepts_nested_attributes_for that actually does the saving.

I didn’t actually verify this, but I suspect that the trip_image[‘trip_image’] attribute is an object (a Paperclip object of sorts). That’s why I was calling .nil? instead of .blank? on it. Calling blank? on an object will also work, but it just doesn’t feel right.

The code you are pasting here checks that ALL the parameters of the trip image are not blank. But what if we want the user to be able to submit a trip image without the caption?

The code in the post works. One thing to keep in mind is that passing in a symbol for the attribute name will not work:
:reject_if => lambda { |t| t[‘trip_image’].nil? } # works

hi!
very nice tutorial! but I have a problem…
I have categories, pages and uploads to the pages. If I create a new page I can select a cagegory and upload data files (images, documents, ..). So I followed your tutorial step-by-step and now if I would like to add data files, they won´t be save in the database. Do you have an idea what could be the problem?
In the form I already have set the html to multipart true.

ok, now i can upload data_files now
but, if I just give the data_file a name, and no upload, the name is saved in the database..

and if I just add a upload-file, it also works, and the file is not saved, but there should be an error with following for example “please enter data_file name first, than create” or something like that.

if data_file_name and upload-file is given it works good. nothing also works.

A question related to this post and your previous post about jquery validation: is it possible to use AJAX validation with images? I’d like to check with AJAX if the image that the user is about to send is correct using paperclip validators (but the image should not be saved at this point if it passes the validation, only validated!)

Another question: have you tested this code with cucumber? For me it seems that attaching a file works fine, but when i try to create a new entry without an image it fails to paperclip validations. Thus, it seems that the reject_if thing is not working in tests, even though it works just fine in my application.

Hi Juho! Regarding your first question I don’t know if it’s possible to validate file attachments on the client side..
As for your second question – the reject_if call works in my application just fine, but it seems that a lot of people are having trouble with it… Perhaps the .nil? call should be replaced with .blank? Let me know if this works for you…

Hi Monica, thanks for this tuto is great like all the tutos that you make… i run my site but not saved anything and a have to comments the lines of validates_attachment_presence and validates_attachment_size because fails with the next error: syntax error, unexpected ‘:’ any help??

Thanks for the links David! I’ll add them as further reading. As I explained in the conclusion, this example limits the number of images for simplicity – the point is to demonstrate how nested attributes work with multipart fields.

Hi, excellent post. I’m using your code to make my User model accept multiple uploads, but I also want to do the processing the background like in this blog post: http://ezror.com/blog/index.shtml. Can the two things work together? Sorry if this is a newbie question, just starting out with Rails. I’ve been having a very hard trying to get anyone to answer this question. 🙁

I had the same problem on reject_if. blank? does not work either, i had to test on photo.
I also tried to add a delete checkbox, in that case, i have not been able to use reject_if, i think it is impossible to keep it if you want to be able to delete your paperclip.

All seems to be working well, I have the form to upload several files, I click “submit” and it says everything is ok… but then, it writes to the “trip” table but not to the “trip_image”… anyone had this problem?

Any one having some trouble with strong parameters? I’m trying to emulate this with rails 4 which has this parameter security feature and it doesn’t work. When it starts to do the insert this error comes out of the log:
“Unpermitted parameter: trip_images_attributes”