× {{alert.msg}} Never ask again
Get notified about new tutorials RECEIVE NEW TUTORIALS

CarrierWave Now Natively Supports Multiple File Uploads in Rails (+ Gotchas!!!)

Andy Maleh
Jul 18, 2015
<p>CarrierWave enables Rails developers to mount an uploader model on an ActiveRecord model to enable seamless upload functionality in a web application. For example, <span style="color:rgb(95, 99, 102)">mounting ImageUploader on UserProfile as a "picture" attribute enables users to upload profile pictures.</span></p> <p>This has always worked out well of the box for attaching a single image or document to a model, but whenever multiple images or documents were involved, Rails developers had to add an extra intermediate model to form a has_many relationship, and then store a single image or document per intermediate model in the colleciton. For example, a BankTransaction ActiveRecord model has many receipts (ActiveRecord collection), whereby each Receipt ActiveRecord model has a DocumentUploader as "document" attribute.</p> <p>This process created unnecessary complexity and repetitive coding whenever one needed to attach multiple images or documents to an ActiveRecord model.</p> <p>But, worry no more! CarrierWave's got your back with their latest master (Edge) release. Now, they officially support embedding a collection of images or documents on an ActiveRecord model via a single Uploader model, using a JSON column, thus removing the need for creating an additional intermediate database table.</p> <p>Below, I am including a write up taken directly from their documents, which I amended with missing information and followed by a "Gotchas" section.</p> <p> </p> <h2><strong>Multiple file uploads</strong></h2> <p><strong>Note:</strong> You must specify using the master branch to enable this feature:</p> <p><code>gem 'carrierwave', github:'carrierwaveuploader/carrierwave'</code>.</p> <p>CarrierWave also has convenient support for multiple file upload fields.</p> <h3><strong>ActiveRecord</strong></h3> <p>Add a column which can store an array. This could be an array column or a JSON column for example. Your choice depends on what your database supports. For example, create a migration like this:</p> <pre><code>rails g migration add_avatars_to_users avatars:json rake db:migrate </code></pre> <p>Open your model file and mount the uploader:</p> <pre><span style="color:rgb(167, 29, 93)">class</span> <span style="color:rgb(121, 93, 163)">User &lt; ActiveRecord::Base</span> mount_uploaders <span style="color:rgb(0, 134, 179)">:avatars</span>, <span style="color:rgb(0, 134, 179)">AvatarUploader</span> <span style="color:rgb(167, 29, 93)">end</span></pre> <p>Make sure your file input fields are set up as multiple file fields. For example in Rails you'll want to do something like this:</p> <pre>&lt;%= form.file_field <span style="color:rgb(0, 134, 179)">:avatars</span>, <span style="color:rgb(0, 134, 179)">multiple:</span> <span style="color:rgb(0, 134, 179)">true</span> %&gt;</pre> <p>Also, make sure your upload controller permits the multiple file upload attribute, <em>pointing to an empty array in a hash</em>. For example:</p> <pre>params.<span style="color:rgb(167, 29, 93)">require</span>(<span style="color:rgb(0, 134, 179)">:user</span>).permit(<span style="color:rgb(0, 134, 179)">:email</span>, <span style="color:rgb(0, 134, 179)">:first_name</span>, <span style="color:rgb(0, 134, 179)">:last_name</span>, {<span style="color:rgb(0, 134, 179)">avatars:</span> []})</pre> <p>Now you can select multiple files in the upload dialog (e.g. SHIFT+SELECT), and they will automatically be stored when the record is saved.</p> <pre>u <span style="color:rgb(167, 29, 93)">=</span> <span style="color:rgb(0, 134, 179)">User</span>.<span style="color:rgb(167, 29, 93)">new</span>(params[<span style="color:rgb(0, 134, 179)">:user</span>]) u.save! u.avatars[<span style="color:rgb(0, 134, 179)">0</span>].url <span style="color:rgb(150, 152, 150)"># =&gt; '/url/to/file.png'</span> u.avatars[<span style="color:rgb(0, 134, 179)">0</span>].current_path <span style="color:rgb(150, 152, 150)"># =&gt; 'path/to/file.png'</span> u.avatars[<span style="color:rgb(0, 134, 179)">0</span>].identifier <span style="color:rgb(150, 152, 150)"># =&gt; 'file.png'</span></pre> <p> </p> <p><strong><span style="color:rgb(150, 152, 150)">Gotchas</span></strong></p> <ul> <li>The Controller accepting the submitted upload collection must permit the upload collection attribute (e.g. avatars) as a model parameter. However, a simple permit of the parameter would not work, yet yield an Unpermitted parameter error. <span style="color:rgb(95, 99, 102)">To get around that, the permitted parameter must also be pointing to an empty nested array inside a hash.</span> For example: "params.require(:user).permit(:avatars)" does not work and should instead be "<span style="color:rgb(95, 99, 102)">params.require(:user).permit({avatars: []})"</span></li> <li><span style="color:rgb(95, 99, 102)">The form must include {html: {multipart: true}} as one of its arguments for uploads to work.</span></li> <li><span style="color:rgb(95, 99, 102)">If you're renaming a single upload attachment to a plural collection name, don't forget to do the same to the mount_uploader method. For example: <span style="color:rgb(95, 99, 102)">"mount_uploader :avatar, </span><span style="color:rgb(95, 99, 102)">AvatarUploader" =&gt; "mount_uploaders :avatars, AvatarUploader"</span></span></li> </ul> <p><span style="color:rgb(95, 99, 102)">Happy multiple file uploading in Rails via CarrierWave!</span></p>
comments powered by Disqus