Ruby on Rails: ファイルアップロード機能を作る ver.2
はじめに
「Ruby on Rails: ファイルアップロード機能を作る」の書き直し。変更点は以下の通り。
- rails2.2 を使う。
- ファイルをデータベースに保存せずにフォルダに入れることにした。
- ファイルサイズの上限設定など、validation をわりとまじめにすることにした。
- 保存に関する作業をモデルに記述することにした。
正直、file_column とか paperclip とか使った方がいいと思うけど、まあ自分で書いたほうが何かと勉強になるので。
環境
| Ruby version | 1.8.6 (universal-darwin9.0) |
| RubyGems version | 1.3.1 |
| Rails version | 2.2.2 |
| Active Record version | 2.2.2 |
| Action Pack version | 2.2.2 |
| Active Resource version | 2.2.2 |
| Action Mailer version | 2.2.2 |
| Active Support version | 2.2.2 |
とりあえず
今回は、ファイルの属性情報をデータベースで管理し、ファイル本体は public/uploaded 以下に入れることとします。サーバで稼動させる時は、public/uploaded の書き込み権限に注意してください。
属性情報のモデル名は attachment。コントローラ名も attachment とします。scaffold は使わずに作ります。
$ rails uploader $ cd uploader $ mkdir public/uploaded
データベースを作ろう
まず、モデルの雛形と、対応するデータベースの雛形である migrate ファイルを作ります。
$ ruby script/generate model Attachment
つぎに、migrate ファイルを編集します。
db/migrate フォルダに 20090212134452_create_attachments.rb のような感じのファイルが生成されていると思いますので、次のように編集します。
20090212134452_create_attachments.rb
class CreateAttachments < ActiveRecord::Migration
def self.up
create_table :attachments do |t|
t.string :filename
t.string :content_type
t.integer :size
t.timestamps
end
end
def self.down
drop_table :attachments
end
end
ここでは、ファイルの属性として、名前 (:filename)、コンテンツタイプ (:content_type)、サイズ (:size) を持たせています。
では、これらのカラムを持ったデータベースを作成しましょう。
$ rake db:migrate
これでデータベースは完成です。
モデルを作ろう
app/models/attachment.rb にモデルの雛形が出来ているので、それを次のように編集します。
attachment.rb
class Attachment < ActiveRecord::Base
FILE_DIR = "#{RAILS_ROOT}/public/uploaded"
MAX_FILE_SIZE = 1.megabyte
validates_presence_of :filename, :message => 'ファイルを選択してください。'
validates_inclusion_of :size, :in => (1..MAX_FILE_SIZE),
:message => "ファイルサイズが大きすぎるか、0 です。最大サイズは #{MAX_FILE_SIZE} バイトです。"
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
"#{FILE_DIR}/#{self.id}_#{self.filename}"
end
end
FILE_DIR でファイルの保存先フォルダの指定、MAX_FILE_SIZE でファイルサイズの上限を指定します。 validates_presence_of と validates_inclusion_of を使って入力がブランクのときと、存在しないファイルを指定したとき (サイズが0)、ファイルサイズが上限を超えているときのエラーチェックを行います。
after_save, after_destroy は、それぞれデータベースへの保存後/データベースからの削除後に自動実行されるメソッドで、ここでファイルを保存/削除するようにしています。
ファイルは、#{ID}_#{filename} のファイル名で保存先フォルダに保存されます。
コントローラを作ろう
コントローラの雛形を作ります。アクションは index, new, create, destroy, download を作ります。
$ ruby script/generate controller attachment index new create destroy download
app/controllers/attachment_controller.rb にコントローラの雛形が出来ているので、それを次のように編集します。
attachment_controller.rb
class AttachmentController < ApplicationController
def index
@attachments = Attachment.find(:all)
end
def new
end
def create
@attachment = Attachment.new(params[:upload])
if @attachment.valid? then
@attachment.save
redirect_to :action => 'index'
end
end
def destroy
@attachment = Attachment.find(params[:id])
@attachment.destroy
redirect_to :action => 'index'
end
def download
@attachment = Attachment.find(params[:id])
send_file(@attachment.filepath,
{:filename => @attachment.filename,
:type => @attachment.content_type})
end
end
index でファイル一覧を取得します。
create では、ファイル選択フォームのパラメータを引数にして Attachment を new します。create, destroy を実行後は index にリダイレクトするように設定しています。
ビューを作ろう
コントローラの雛形を作ると、app/views/attachment フォルダにビューの雛形も出来ているので、それぞれ次のように編集します。destroy と download のビューは使いません。削除しても良いでしょう。
index.rhtml.erb
<h1>Attachment#index</h1>
<table>
<tr>
<th>filename</th>
<th>content_type</th>
<th>size</th>
<th>action</th>
</tr>
<% @attachments.each do |attachment| %>
<tr>
<td><%= link_to(h(attachment.filename),
{:action => 'download',
:id => attachment.id})
%></td>
<td><%= h(attachment.content_type) %></td>
<td><%= h(attachment.size) %></td>
<td>
<%= link_to('[destroy]',
{:action => 'destroy',
:id => attachment.id})
%></td>
</td></tr>
<% end %>
</table>
<%= link_to('new', {:action => 'new'}) %>
ファイル名とコンテンツタイプ、サイズを表示するベーシックなリスト形式です。ファイル名をクリックするとファイルをダウンロードできるようにしています。
new.rhtml.erb
<h1>Attachment#new</h1>
<% form_tag({:action => 'create'}, :multipart => true) do %>
<%= file_field('upload', 'file') %>
<%= submit_tag('upload') %>
<% end %>
<%= link_to('index', {:action => 'index'}) %>
新規アップロード用の画面です。file_field の第二引数に ‘file’ を指定することにより、create で Attachment が new される際に、モデルで定義した file= メソッドが自動的に呼ばれます。また rails2.0 から form_tag の使用法が変わっているので注意してください。
create.rhtml.erb
<h1>Attachment#create</h1>
<%= error_messages_for :attachment %>
<%= link_to('index', {:action => 'index'}) %>
create のビューは、アップロード時のエラー表示に使います。
完成です
次のコマンドでサーバを起動して http://localhost:3000/attachment/ にアクセスしてみましょう。
$ ruby script/server
![]() |
![]() |
![]() |
![]() |
とりあえずこれでファイルはアップロードできるようになりましたが、まだまだ拡張の余地があります。たとえば…
- スタイルシートやビューを編集して好みの外観を作る。
- acts_as_authenticated プラグインなどを用いてユーザ認証を実装する。
- will_paginate や paginating_find プラグインなどを用いてページングを実装する (rails2.0 から標準では paginate が用意されていません)。
- ファイルの削除時に確認メッセージを表示する。
などが考えられます。色々拡張して遊んでみましょう。




Comment