ファイルのアップロードとダウンロード

[12]ファイルのアップロードとダウンロード
1.ファイルのアップロードとダウンロード
Windows環境でのファイルのアップロードやダウンロードを行うプログラムを紹介します。
1-1.プロジェクトの生成
(1) プロジェクトAppli018を生成する
(2) 日本語環境の設定
(3) データベースの作成

1-2.モデルの作成
(1) モデルの生成
NetBeansで生成を選択します。
    ジェネレータ(G): model
    引数(A): Folder


実行結果
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/folder.rb
create test/unit/folder_test.rb
create test/fixtures/folders.yml
create db/migrate
create db/migrate/20100217040348_create_folders.rb

(2) マイグレーションのプログラム修正


/db/migrate/20100217040348_create_folders.rb
class CreateFolders < ActiveRecord::Migration
def self.up
create_table :folders do |t|
t.string :filename
t.string :content_type
t.integer :size

t.timestamps
end
end

def self.down
drop_table :folders
end
end

(3) マイグレーションの実行
NetBeansで[データベースマイグレーション]→[現在のバージョンへ]を選択します。


実行結果
(in D:/Rails_Projects/Appli018)
== CreateFolders: migrating ==================================================
-- create_table(:folders)
-> 0.1100s
== CreateFolders: migrated (0.1100s) =========================================

(4) モデルの編集


/app/models/folder.rb
class Folder < ActiveRecord::Base
MAX_FILE_SIZE = 1.megabyte

validates_presence_of :filename, :message => 'Please choose a file.'
validates_inclusion_of :size, :in => (1..MAX_FILE_SIZE),
:message => "File size is too big or 0. Maximum size is #{MAX_FILE_SIZE} bytes."

def file=(file)
self.filename = file.original_filename if file.respond_to?(:original_filename)
self.content_type = file.content_type if file.respond_to?(:content_type)
self.size = file.size if file.respond_to?(:size)
@tmp = file
end

def after_save
File.open(filepath, "wb") { |f|
f.write @tmp.read
}
end

def after_destroy
File.delete filepath if File.exist? filepath
end

def filepath
"public/files/#{self.id}_#{self.filename}"
end
end

1-3.コントローラの作成
(1) コントローラの生成
NetBeansで[生成]を選択します。
    ジェネレータ(G): controller
    名前(N): folders
    ビュー(V): index, new, create, destroy, download


実行結果
exists app/controllers/
exists app/helpers/
create app/views/folder
exists test/functional/
create test/unit/helpers/
create app/controllers/folders_controller.rb
create test/functional/folders_controller_test.rb
create app/helpers/folders_helper.rb
create test/unit/helpers/folders_helper_test.rb
create app/views/folders/index.html.erb
create app/views/folders/new.html.erb
create app/views/folders/create.html.erb
create app/views/folders/destroy.html.erb
create app/views/folders/download.html.erb

(2) コントローラの編集


/app/controllers/folders_controller.rb
class FoldersController < ApplicationController
def index
@folders = Folder.find(:all)
end

def new
end

def create
@folder = Folder.new(params[:upload])
if @folder.valid? then
@folder.save
redirect_to :action => 'index'
end
end

def destroy
@folder = Folder.find(params[:id])
@folder.destroy
redirect_to :action => 'index'
end

def download
@folder = Folder.find(params[:id])
send_file(@folder.filepath,
{:filename => @folder.filename,
:type => @folder.content_type})
end
end

1-4.ビューの編集
(1) index.html.erb


/app/views/folders/index.html.erb
<h1>Index</h1>
<table>
<tr>
<th>filename</th>
<th>content_type</th>
<th>size</th>
<th>action</th>
</tr><% @folders.each do |folder| %>
<tr>
<td><%= link_to(h(folder.filename),
{:action => 'download',
:id => folder.id}) %>
</td>
<td><%= h(folder.content_type) %></td>
<td><%= h(folder.size) %></td>
<td>
<%= link_to('[destroy]',
{:action => 'destroy',
:id => folder.id}) %>
</td>
</tr><% end %>
</table><%= link_to('new', {:action => 'new'}) %>

(2) new.html.erb


/app/views/folders/new.html.erb
<h1>New</h1><% form_tag({:action => 'create'}, :multipart => true) do %>
<%= file_field('upload', 'file') %>
<%= submit_tag('upload') %><% end %><%= link_to('index', {:action => 'index'}) %>

(3) create.html.erb


/app/views/folders/create.html.erb
<h1>Create</h1><%= error_messages_for :folder %><%= link_to('index', {:action => 'index'}) %>

(4) 不用なビュープログラムの削除
/app/views/folders/destroy.html.erb
/app/views/folders/download.html.erb

(5) レイアウトファイルの作成
HTMLのヘッダー部分など各ページ共通に利用される表示要素を作成します。


/app/views/layouts/folders.html.erb
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Appli018: <%= controller.action_name %></title>
</head>
<body><%= yield %>

</body>
</html>

1-5.ファイルの格納先の作成
/public/の直下にfilesディレクトリーを作ります。

(1) filesディレクトリーの作成
公開ディレクトリー上で右クリックして[新規]→[フォルダ...]を選択します。

(2) フォルダ名(N):をfilesに設定

(3) ファイルの格納先
/public/files/[id]_[original_filename]

1-6.動作確認
http://127,0,0,1:3000/folders/で起動します。

1-7.表示の改善
(1) 数値のカンマ編集
サイズの表示方法をカンマ編集し見やすくしましょう。


/app/views/folders/index.html.erb
<h1>Index</h1>
<table>
<tr>
<th>filename</th>
<th>content_type</th>
<th>size</th>
<th>action</th>
</tr><% @folders.each do |folder| %>
<tr>
<td><%= link_to(h(folder.filename),
{:action => 'download',
:id => folder.id}) %>
</td>
<td><%= h(folder.content_type) %></td>
<td align="right"><%= folder.size.to_s.reverse.gsub( /(\d{3})(?=\d)/, '\1,' ).reverse %></td>
<td>
<%= link_to('[destroy]',
{:action => 'destroy',
:id => folder.id}) %>
</td>
</tr><% end %>
</table><%= link_to('new', {:action => 'new'}) %>

(2) 動作確認

(3) 漢字を含むファイル名をアップロードするときの不具合を修正
文字コードWindowsRuby on Railsでは異なるため、漢字を含むファイル名のときはファイル名称が正しく伝わりません。
例えばUTF8コードで、”すいれん_Water lilies.jpg”のファイルをそのままWindowsに渡すと”縺吶>繧後s_Water lilies.jpg”となってしまいます。
そこでRuby on Rails からWindowsOSへファイルを渡すときに文字コードをutf8からshiftjisにコンバージョンするロジックを組み込みます。


/app/models/folder.rb
class Attachment < ActiveRecord::Base
MAX_FILE_SIZE = 1.megabyte

validates_presence_of :filename, :message => 'Please choose a file.'
validates_inclusion_of :size, :in => (1..MAX_FILE_SIZE),
:message => "File size is too big or 0. Maximum size is #{MAX_FILE_SIZE} bytes."

def file=(file)
self.filename = file.original_filename if file.respond_to?(:original_filename)
self.content_type = file.content_type if file.respond_to?(:content_type)
self.size = file.size if file.respond_to?(:size)
@tmp = file
end

def after_save
File.open(filepath, "wb") { |f|
f.write @tmp.read
}
end

def after_destroy
File.delete filepath if File.exist? filepath
end

def filepath
str=self.filename
str_shiftjis = str.kconv(Kconv::SJIS, Kconv::UTF8) #UTF8 から Shift_JIS に変換
"public/files/#{self.id}_#{str_shiftjis}"

end
end

(4) 漢字を含むファイル名をダウンロードするときの不具合を修正
同様にダウンロードのときもRuby on RailsからWindowsにファイルを渡すとき、漢字を含むファイル名は正しく伝わりません。

そこで文字コードをutf8からshiftjisにコンバージョンするロジックを組み込みます。


/app/models/folder.rb
class FoldersController < ApplicationController


def download
@folder = Folder.find(params[:id])
str=@folder.filename
str_shiftjis = str.kconv(Kconv::SJIS, Kconv::UTF8) # UTF8 から Shift_JIS に変換
send_file(@folder.filepath,
{:filename => str_shiftjis,
:type => @folder.content_type})

end
end
これでファイル名はWindowsへも正しく引き継がれます。

(5) 文字コード関係のまとめ
アップロードおよびダウンロードにおける文字コードの推移については次の図が理解しやすいでしょう。



FileColumn | index | ProtoThinkBox