Ruby on Rails: ファイルアップロード機能を作る ver.2

Posted in rails,ruby by o-taki on the 2009/2/13

はじめに

Ruby on Rails: ファイルアップロード機能を作る」の書き直し。変更点は以下の通り。

正直、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

index new
error1 error2

とりあえずこれでファイルはアップロードできるようになりましたが、まだまだ拡張の余地があります。たとえば…

などが考えられます。色々拡張して遊んでみましょう。

Comment

Trackback URL