Railsで複数画像アップロードをする

Rails + Paperclipで画像アップロードが出来ますが、複数の画像を一気にアップロードしたい場合があると思います。その場合はjquery-upload-railsを使うといいと思います。

使い方はgithubページに書いてあるので分かると思いますが、一点注意が必要です。

以下順に手順を書いていきます

1.Gemfileに追記してbundle installする

gem "jquery-fileupload-rails"


$ bundle install --path vendor/bundle

2.複数画像アップロード用のviewの追加
こちらはサンプルとしてみたページのソースを、使わせてもらいました。

 1 <div class="container">
  2   <h2>Upload file</h2>
  3   <%= form_for @photo, :html => { :multipart => true, :id => "fileupload"  } do |f| %>
  4     <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload -->
  5     アルバム選択:<%= f.select "album_id", options_from_collection_for_select(@albums,"id","title") %> &nbsp;&nbsp;| &nbsp;&nbsp;<%= link_to 'アルバム作成', {controller: "    albums", action: "new", from_url: 'photo'}  %><br/>
  6     <%= f.label :title %>:<%= f.text_field :title %><br/>
  7     <%= f.label :description %>:<%= f.text_area :description, :size => "40x20" %><br/>
  8     <div class="row fileupload-buttonbar">
  9       <div class="span7">
 10         <!-- The fileinput-button span is used to style the file input field as button -->
 11         <span class="btn btn-success fileinput-button">
 12           <i class="icon-plus icon-white"></i>
 13           <span>Add files...</span>
 14           <%= f.file_field :photo %>
 15         </span>
 16         <button type="submit" class="btn btn-primary start">
 17           <i class="icon-upload icon-white"></i>
 18           <span>Start upload</span>
 19         </button>
 20         <button type="reset" class="btn btn-warning cancel">
 21           <i class="icon-ban-circle icon-white"></i>
 22           <span>Cancel upload</span>
 23         </button>
 24         <button type="button" class="btn btn-danger delete">
 25           <i class="icon-trash icon-white"></i>
 26           <span>Delete</span>
 27         </button>
 28         <input type="checkbox" class="toggle">
 29       </div>
 30       <div class="span5">
 31         <!-- The global progress bar -->
 32         <div class="progress progress-success progress-striped active fade">
 33           <div class="bar" style="width:0%;"></div>
 34         </div>
 35       </div>
 36     </div>
 37     <!-- The loading indicator is shown during image processing -->
 38     <div class="fileupload-loading"></div>
 39     <br>
 40     <!-- The table listing the files available for upload/download -->
 41     <table class="table table-striped"><tbody class="files" data-toggle="modal-gallery" data-target="#modal-gallery"></tbody>
 42     </table>
 43   <% end %>
 44
 45 </div>
 46 <script>
 47   var fileUploadErrors = {
 48     maxFileSize: 'File is too big',
 49     minFileSize: 'File is too small',
 50     acceptFileTypes: 'Filetype not allowed',
 51     maxNumberOfFiles: 'Max number of files exceeded',
 52     uploadedBytes: 'Uploaded bytes exceed file size',
 53     emptyResult: 'Empty file upload result'
 54   };
 55 </script>
 56
 57 <!-- The template to display files available for upload -->
 58 <script id="template-upload" type="text/x-tmpl">
 59   {% for (var i=0, file; file=o.files[i]; i++) { %}
 60     <tr class="template-upload fade">
 61       <td class="preview"><span class="fade"></span></td>
 62       <td class="name"><span>{%=file.name%}</span></td>
 63       <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
 64       {% if (file.error) { %}
 65         <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
 66         {% } else if (o.files.valid && !i) { %}
 67         <td>
 68           <div class="progress progress-success progress-striped active"><div class="bar" style="width:0%;"></div></div>
 69         </td>
 70         <td class="start">{% if (!o.options.autoUpload) { %}
 71           <button class="btn btn-primary">
 72             <i class="icon-upload icon-white"></i>
 73             <span>{%=locale.fileupload.start%}</span>
 74           </button>
 75           {% } %}</td>
 76         {% } else { %}
 77         <td colspan="2"></td>
 78         {% } %}
 79       <td class="cancel">{% if (!i) { %}
 80         <button class="btn btn-warning">
 81           <i class="icon-ban-circle icon-white"></i>
 82           <span>{%=locale.fileupload.cancel%}</span>
 83         </button>
 84         {% } %}</td>
 85     </tr>
 86     {% } %}
 87 </script>
 88 <!-- The template to display files available for download -->
 89 <script id="template-download" type="text/x-tmpl">
 90   {% for (var i=0, file; file=o.files[i]; i++) { %}
 91     <tr class="template-download fade">
 92       {% if (file.error) { %}
 93         <td></td>
 94         <td class="name"><span>{%=file.name%}</span></td>
 95         <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
 96         <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
 97         {% } else { %}
 98         <td class="preview">{% if (file.thumbnail_url) { %}
 99           <a href="{%=file.url%}" title="{%=file.name%}" rel="gallery" download="{%=file.name%}"><img src="{%=file.thumbnail_url%}"></a>
100           {% } %}</td>
101         <td class="name">
102           <a href="{%=file.url%}" title="{%=file.name%}" rel="{%=file.thumbnail_url&&'gallery'%}" download="{%=file.name%}">{%=file.name%}</a>
103         </td>
104         <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
105         <td colspan="2"></td>
106         {% } %}
107       <td class="delete">
108         <button class="btn btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}">
109           <i class="icon-trash icon-white"></i>
110           <span>{%=locale.fileupload.destroy%}</span>
111         </button>
112         <input type="checkbox" name="delete" value="1">
113       </td>
114     </tr>
115     {% } %}
116 </script>
117
118 <script type="text/javascript" charset="utf-8">
119   $(function () {
120       // Initialize the jQuery File Upload widget:
121       $('#fileupload').fileupload();
122       //
123       // Load existing files:
124       $.getJSON($('#fileupload').prop('action'), function (files) {
125         var fu = $('#fileupload').data('blueimpFileupload'),
126         template;
127         fu._adjustMaxNumberOfFiles(-files.length);
128         console.log(files);
129         template = fu._renderDownload(files)
130         .appendTo($('#fileupload .files'));
131         // Force reflow:
132         fu._reflow = fu._transition && template.length &&
133         template[0].offsetWidth;
134         template.addClass('in');
135         $('#loading').remove();
136         });
137
138       });
139 </script>

今回はpaperclipでphotoモデルのデータ登録し、画像をアップロードしています。

3.続いてcontrollerです。2.で作成したviewを表示するメソッドを追加します。

  # Get multi_upload
  def multi_upload
     @photo = Photo.new
     @albums = Album.where(user_id: session[:user_id]).latest
     respond_to do |format|
       format.html
       format.json { head :ok }
     end
  end

今回はmulti_uploadという名前で定義します。

4.最後にcreateメソッドに追記します。
今回画像同時アップロード時にコントローラのcreateメソッドを画像の数分呼びますが、そのcreateメソッドの戻り値でjson形式でデータを決まったフォーマットで渡さないとアップロードが正常に完了してもエラーメッセージが表示されてしまいます。
参照

   # POST /photos
   # POST /photos.json
   def create
     @photo = Photo.new(photo_params)
     @photo.user_id = current_user.id
     respond_to do |format|
       if @photo.save
         format.html { redirect_to @photo, notice: 'Photo was successfully created.' }
         format.json { render json: { :files => [@photo.photo.url(:thumb)]}, status: :created, location: @photo }
       else
         format.html { render action: "new" }
         format.json { render json: @photo.errors, status: :unprocessable_entity }
       end
     end
   end

以上で複数画像をアップロードできるはずです。