画像ファイルをアップロードして自由に表示したい!
アイコンや操作画面のスクリーンショットなど、jpegやpngなどの画像ファイルをアップロードして、表示してみたくなった。画像ファイルはサイズが巨大になりがちなので、取り扱い方が、文字列や数値、日付とはちょっと異なる。いろいろな処理方法があるが、まずは、画像ファイルを直接データベースに登録する方法でやってみる。全体をシンプルに見渡したいので、新規プロジェクト名「file」を作成して以下のようにテストしてみた。
fileプロジェクトの新規作成
- ターミナルで新規プロジェクトを作成(データベースにsqlite3を指定)
cd ~/railsapp rails file -d sqlite3
- 上記プロジェクトをRadRailsで読み取るために、RadRailsでも新規プロジェクト名「file」を作成した。
- RadRailsにfileプロジェクトが読み込まれたら、「ジェネレーター」タブでmodel softwareを実行。
- そのあと、マイグレーションファイルは、以下のように設定。
#------ db/migrate/001_create_softwares.rb ------ class CreateSoftwares < ActiveRecord::Migration def self.up create_table :softwares do |t| t.column :file_name, :string t.column :file_type, :string t.column :file_data, :binary end end def self.down drop_table :softwares end end
- 「Rakeタスク」タブでdb:migrateを実行。
- 「ジェネレーター」タブでscaffold softwareを実行。
以上が、scaffoldまでのお決まりの手順。
データベースに画像ファイルを登録
ビュー
- file_fieldで、アップロードするファイルを指定する入力フォームを作る。
- ファイルをアップロードするには、start_form_tagで:multipart => trueの設定が必要。
- いつもはstart_form_tagの()を省略できるが、ここでは省略するとエラーになる。
- file_fieldで送信されるものは、オブジェクトのようだ。
- クラス名はTempfileになっていた。*1
- params[:software][:temp_file]として送信される。
<%#------ app/views/softwares/new.rhtml ------%> <h1>New software</h1> <%= start_form_tag({:action => 'create'}, :multipart => true) %> <p> <label for="software_temp_file">File</label><br/> <%= file_field 'software', 'temp_file' %> </p> <%= submit_tag "Create" %> <%= end_form_tag %> <%= link_to 'Back', :action => 'list' %>
コントローラー
- コントローラーにconvert_temp_fileメソッドを定義して、パラメータとして受け取ったTempfileオブジェクトを使い易い状態に変換する。
- データベースにはfile_name、file_type、file_dataフィールドしかない。
- file_filedからは、params[software][temp_file]としてオブジェクトを受け取る。
- フィールド名に合わせたパラメータに変換した後は、params[software][temp_file]を削除しておかないとエラーになる。(存在しないフィールドに書き込めないため)
- Tempfileオブジェクトのままデータベースに保存して、表示する時に必要なデータを取り出すという考えもあるが、Tempfileオブジェクトをそのままデータベースに保存することは出来なかった...。*2
- 変換したら、いつも通りにデータベースに書き込む。
#------ app/controller/softwares_controller.rb ------ class SoftwaresController < ApplicationController ...(途中省略)... def new @software = Software.new end def create convert_temp_file @software = Software.new(params[:software]) if @software.save flash[:notice] = 'Software was successfully created.' redirect_to :action => 'list' else render :action => 'new' end end private def convert_temp_file temp_file = params[:software][:temp_file] params[:software][:file_name] = temp_file.original_filename params[:software][:file_type] = temp_file.content_type params[:software][:file_data] = temp_file.read params[:software].delete(:temp_file) end ...(途中省略)...
- Tempfileオブジェクトから情報を取り出すメソッド
original_filename | file_fieldで指定したファイル名を返す。 | 例:stripe 1.png |
content_type | ファイルの種類を返す。 | 例:image/png |
read | ファイルのデータを返す。 | 画像ファイルのデータそのもの |
size | ファイルのサイズを返す。 | 例:83264 |
以上で、画像ファイルのアップロードが可能になった。
データベースに保存されている画像ファイルを表示
コントローラー
- コントローラーに画像表示用のメソッドputs_imageを追加した。
- send_dataがデータベースの中身をファイルとして送信してくれる。
#------ app/controller/softwares_controller.rb ------ class SoftwaresController < ApplicationController ...(途中省略)... public def puts_image @software = Software.find(params[:id].to_i) send_data @software.file_data, :filename => @software.file_name, :type => @software.file_type, :disposition => "inline" end
ビュー
- 画像を表示するにはimage_tagを利用する。
- 通常、画像ファイルまでのファイルディレクトリパスや、urlを指定する。
- データベースの中身を表示したいときは、url_for()を使って、send_dataを処理するコントローラーのアクションとidを指定する。
- ちょっとした問題が発生。image_tag url_for(:action => 'puts_image', :id => '1') のように指定すると...
<%#------ app/views/softwares/show.rhtml ------%> <% for column in Software.content_columns %> <p> <b><%= column.human_name %>:</b> <% if column.name == 'file_data' %> <%= image_tag url_for(:action => 'puts_image', :id => @software), :alt => @software.file_name %> <% else %> <%=h @software.send(column.name) %> <% end %> </p> <% end %> <p> <b>File size:</b> <%= @software.file_data.size %> byte </p> <%= link_to 'Edit', :action => 'edit', :id => @software %> | <%= link_to 'Back', :action => 'list' %>
以上で、画像ファイルを自由に表示できるようになった。
ついでにファイルとしてダウンロードする場合
コントローラー
- send_dataのオプション:disposition => "inlineを指定しなければ、ファイルとしてダウンロードされる。
def download_file @software = Software.find(params[:id]) send_data @software.file_data, :filename => @software.file_name, :type => @software.file_type end
ビュー
- link_toで上記コントローラーのアクションを指定したリンクを作っておけば、クリックすればダウンロードが始まる。
<%= link_to 'Download', :action => 'download_file', :id => @software %>
これで、ファイルのダウンロードにも対応できる。
参考
以下のページがとても参考になりました。感謝です。
RoR Wiki 翻訳 Wiki - HowtoUploadFiles
*2:試してないが、オブジェクトをバイト列に変換すれば可能かもしれない。7.24 Marshalの使い方を教えてください