質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Q&A

1回答

1856閲覧

[Rails]ActionDispatch::Http::UploadedFileオブジェクトを表示・保存させる方法を知りたい

s_diff

総合スコア107

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

0グッド

0クリップ

投稿2019/02/15 02:06

編集2019/02/15 03:09

###知りたいこと
以下の3つがわからず開発が止まっています

  1. ActionDispatch::Http::UploadedFileを表示する方法
  2. ActionDispatch::Http::UploadedFileを保存する方法
  3. ビューを隔ててもzipファイルを保存する方法

###環境
rails 4.2.6
jquery-ui-rails 6.0.1
mysql Ver 8.0.11 for osx10.13 on x86_64 (Homebrew)

productsテーブルのカラムは以下です。

t.integer "area_id", limit: 4
t.string "building_name", limit: 255
t.string "number", limit: 255
t.string "station", limit: 255
t.string "address", limit: 255
t.text "zip", limit: 65535
t.text "products_image1", limit: 65535
※ products_image1 ~ 30まであります。

###実装手順

  1. pre_sort.html.erbで6つのカラムに入力し、その後ドラッグ&ドロップ(dropzone.js)でinputタグ(name=products_images[])に画像ファイルを複数入れて「作成」を押します。

イメージ説明
2. sort.html.erbでproducts_imagesを一覧表示させて、jQuery-UIのSortableで並び替えて「作成」を押し、保存します。
イメージ説明

###問題点

  1. sort.html.erbに遷移した時点で、products_imagesにはActionDispatch::Http::UploadedFileオブジェクトの配列が入っていましたが、個々をそのままimage_tagやdivタグのbackground-imageにいれても画像が表示されません。

  2. sort.html.erbでバリデーションを外して送信すると、products_image130とzipに値が入っていますが、何も表示されません。products_image130とzip両方共、バリデーションを記述するとレコード作成は弾かれます。

###実装コード

ルーティング

ruby

1#routes.rb 2namespace :manage do 3 resources :products do 4 member do 5 get 'pre_sort' 6 post 'sort' 7 post 'create_sorted' 8 end 9 end 10end

コントローラ

ruby

1#manage/products_controller.rb 2 def pre_sort 3 @product = Product.new 4 @areas = Area.all 5 end 6 7 def sort 8 @product = Product.new(product_params) 9 @products_images = params[:products_images] 10 end 11 12 def create_sorted 13 @product = Product.new(product_params) 14 15 @product.building_name = ActiveSupport::Multibyte::Unicode.normalize(@product.building_name, :c) 16 @product.address = ActiveSupport::Multibyte::Unicode.normalize(@product.address, :c) 17 18 num = 1 19 params[:products_images].each do |img| 20 @product.send("products_image#{num}=", img) 21 num += 1 22 end 23 24 respond_to do |format| 25 if @product.save 26 format.html { redirect_to [:manage, @product], notice: "#{@product.building_name}を作成しました。" } 27 format.json { render :show, status: :created, location: @product } 28 else 29 format.html { render :pre_sort } 30 format.json { render json: @product.errors, status: :unprocessable_entity } 31 end 32 end 33 end 34

ビュー①(pre_sort.html.erb)

ruby

1#manage/products/pre_sort.html.erb 2 3<%= form_for([:manage,@product], url: { action: "sort"}, html: { multipart: true }) do |f| %> 4 <div class="input manage-product-create-form"> 5 <p class="row"><%= f.label :area_id, class: "label" %><%= f.select :area_id, @areas.map{ |t| [t.name, t.id] }, include_blank: true %></p> 6 <p class="row"><%= f.label :building_name, class: "label" %><%= f.text_field :building_name %></p> 7 <p class="row"><%= f.label :number, class: "label" %><%= f.text_field :number %></p> 8 <p class="row"><%= f.label :floor, class: "label" %><%= f.select :floor, Request::FLOOR, include_blank: true %></p> 9 <p class="row"><%= f.label :station, class: "label" %><%= f.text_field :station %></p> 10 <p class="row"><%= f.label :address, class: "label" %><%= f.text_field :address %></p> 11 <p class="row" id="js-selectFile"> 12 <%= f.label "ダウンロードZIP", class: "label" %> 13 <%= f.file_field :zip, id: "js-upload" %> 14 <%= f.hidden_field :zip_cache, id: "js-upload-cache" %> 15 <button class="original-btn">ZIPファイルを選択</button> 16 <span class="icon">未選択</span> 17 </p> 18 <input type='file' name='products_images[]' multiple=true class='uploadFile' id="fileInput" /> 19 <div id="drop_zone"><i class="fa fa-picture-o" aria-hidden="true"></i> 物件写真をドラッグ&ドロップしてください</div> 20 <div id="preview"></div> 21 </div> 22 <div class="actions"> 23 <%= button_tag type: 'submit', class: "btn-edit" do %> 24 <i class="fa fa-pencil" aria-hidden="true"></i> 作成 25 <% end %> 26 </div> 27<% end %>

ビュー②(sort.html.erb)

ruby

1#manage/products/sort.html.erb 2<div class="input manage-product-create-form"> 3 <p class="row"><label class="label">エリア</label><%= @product.area_id %></p> 4 <p class="row"><label class="label">物件名</label><%= @product.building_name %></p> 5 <p class="row"><label class="label">号室</label><%= @product.number %></p> 6 <p class="row"><label class="label">間取り</label><%= @product.floor %></p> 7 <p class="row"><label class="label">駅名</label><%= @product.station %></p> 8 <p class="row"><label class="label">住所</label><%= @product.address %></p> 9 <p class="row"><label class="label">DL用zip</label><%= @product.zip.url %></p> 10 11 <% (@products_images).each do |img| %> 12 <%= image_tag img, size: '100x100' %> 13 <% end %> 14 15 <%= form_for([:manage,@product], url: { action: "create_sorted"}, html: { multipart: true }) do |f| %> 16 <div class="file-upload-area"> 17 <div class="sort-box-container sortable"> 18 <% (@products_images).each do |img| %> 19 <div class="sort-box"> 20 <input type="text" class="form-control" readonly="" /> 21 <div style='border: dashed 1px #ccc;'> 22 <%= hidden_field_tag 'products_images[]', value: img, class: 'uploadFile', style: 'width: 90px; font-size: 16px;' %> 23 <div class="imagePreview item" style='padding: 0; background-image: <%= img %>'></div> 24 </div> 25 </div> 26 <% end %> 27 </div> 28 </div> 29 <%= f.hidden_field :area_id, value: @product.area_id %> 30 <%= f.hidden_field :building_name, value: @product.building_name %> 31 <%= f.hidden_field :number, value: @product.number %> 32 <%= f.hidden_field :floor, value: @product.floor %> 33 <%= f.hidden_field :station, value: @product.station %> 34 <%= f.hidden_field :address, value: @product.address %> 35 <%= f.hidden_field :zip, value: @product.zip_url %> 36 </div> 37 38 <div class="actions"> 39 <%= button_tag type: 'submit', class: "btn-edit" do %> 40 <i class="fa fa-pencil" aria-hidden="true"></i> 作成 41 <% end %> 42 </div> 43<% end %>

Sortable.js

jquery

1$(function() { 2 $('.sortable').sortable({ 3 update: function(e, ui) { 4 $('.sortable').sortable('toArray'); 5 } 6 }); 7 $(document).on('change', '.uploadFile', function() { 8 var input = $(this); 9 10 label = input.val().replace(/\_/g, '').replace(/.[^/.].+$/, "").slice( -12 ); 11 input.parent('div').prev('.form-control').val(label); 12 13 var files = !!this.files ? this.files : []; 14 if (!files.length || !window.FileReader) return; 15 if (/^image/.test( files[0].type)){ 16 var reader = new FileReader(); 17 reader.readAsDataURL(files[0]); 18 reader.onloadend = function(){ 19 input.next('.imagePreview').css("background-image", "url("+this.result+")"); 20 } 21 } 22 }); 23});

Dropzone.js

jquery

1$(function(){ 2 var formData = new FormData(); 3 var dropZone = document.getElementById("drop_zone"); 4 var fileInput = document.getElementById("fileInput"); 5 6 if (!($('#drop_zone').length)){ 7 return false; 8 } 9 10 dropZone.addEventListener("dragover", function(e) { 11 e.stopPropagation(); 12 e.preventDefault(); 13 14 this.style.background = "#87cefa"; 15 }, false); 16 17 dropZone.addEventListener("dragleave", function(e) { 18 e.stopPropagation(); 19 e.preventDefault(); 20 21 this.style.background = "#ffffff"; 22 }, false); 23 24 dropZone.addEventListener("drop", function(e) { 25 e.stopPropagation(); 26 e.preventDefault(); 27 28 this.style.background = "#ffffff"; 29 30 var files = e.dataTransfer.files; 31 32 fileInput.files = files; 33 for (var i = 0; i < files.length; i++) { 34 (function() { 35 var fr = new FileReader(); 36 fr.onload = function() { 37 var div = document.createElement('div'); 38 39 var img = document.createElement('img'); 40 img.setAttribute('src', fr.result); 41 div.appendChild(img); 42 43 var preview = document.getElementById("preview"); 44 preview.appendChild(div); 45 }; 46 fr.readAsDataURL(files[i]); 47 })(); 48 49 formData.append("file", files[i]); 50 } 51 }, false); 52 53}); 54

長くなってしまい申し訳ありません。
ご指摘いただけることがあれば何卒よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

インスタンスをそのまま出力しても画像になるわけはないですが、なにぶん僕も経験が浅いのでActionDispatch::Http::UploadedFileについてはさほど詳しくないです。
ただActionDispatch::Http::UploadedFile#readを使ってバイナリデータを取得してそれを画像ファイルに書き出してパス指定してViewに表示って流れが妥当なんですかね?
保存もインスタンスをそのまま保存しようとするのではなくて、要素を取り出して保存すればいいのではないでしょうか?

ソースコード

投稿2019/02/15 05:03

編集2019/02/15 05:55
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

s_diff

2019/02/15 05:53

回答ありがとうございます。 バイナリを使う必要があるのですね。 なるほど、インスタンスをそのまま使っても表示される例があったのでそのまま使っているのですが、、、。 個人のコードなので問題ありませんよ。
退会済みユーザー

退会済みユーザー

2019/02/15 05:57

> インスタンスをそのまま使っても表示される例 そんな例があるんですか、それは気になりますね...。 よければ教えていただけますか? > 個人のコードなので問題ありませんよ 大変失礼しました。
s_diff

2019/02/15 06:10

> よければ教えていただけますか? このコードではDropzone.jsでファイルを複数選択しているのですが、これをzipファイルで入力してコントローラで展開し、一度カラムの中に入れて(保存はせず)次のビューにわたす、というやり方を試してみた時に、インスタンスをそのまま使って表示できました。なので問題ないのかと、、 いえいえ、お気遣いありがとうございます。
退会済みユーザー

退会済みユーザー

2019/02/15 06:32 編集

うーん、これはインスタンスをそのまま使っているってわけではないと思います。 ドロップされたファイルのDataTransferオブジェクトを取得して さらにループの中でごにょごにょと加工して表示しているように見えます。
退会済みユーザー

退会済みユーザー

2019/02/15 06:35 編集

別の箇所のことかもしれませんが それは本当にActionDispatch::Http::UploadedFileインスタンスでしたか? インスタンスをそのまま出力して画像になるのはちょっと想像がつかないです...。
s_diff

2019/02/15 06:42

「インスタンスをそのまま」の意味を取り違えていみたいですね。 申し訳ありません。 ん〜たしかそうだったはずなのですが。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問