Railsで画像アップローダーを作ってみる

試験前の現実逃避にrailsでも。環境はDebian Lenny + Rails 2.3.5。

まず名前を決める。upia: UPloader for ImAges。...無理があるな。まあいいや。

名前を決めたら雛形をrailsに生成させる。

$ rails upia

Railsのサーバーを起動させる。

$ cd upia
$ script/server

この状態でlocalhost:3000にアクセスすると"Ruby on Rails: Welcome aboard"というタイトルのページが見える。

次にデータベースの設計をする。とりあえず画像は次のようなテーブルで管理する事にする。

名前
id integer
title string
filename string
content_type string
description text
file binary
thumbnail binary
created_at timestamp
updated_at timestamp

次にscaffoldでimageに関する雛形を作成する。idやcreated_at, updated_atは指定しなくても自動的にrailsが付けてくれる。

$ script/generate scaffold image title:string filename:string content_type:string description:text file:binary thumbnail:binary

これでimageに対するCRUD (「作成(Create)」「読み出し(Read)」「更新(Update)」「削除(Delete)」)の雛形が生成された。

次にmigrationを実行してデータベースを作成する。

$ rake db:migrate

これで上で指定したテーブルを含むデータベースが生成された。この状態で http://localhost:3000/images/ にアクセスするとimageの作成や編集などが出来るようになってる。(アップロードは出来ないけど)

次にアップロードできるようにする。app/views/images/new.html.erb を開いて

<h1>画像をアップロード</h1>

<% form_for(@image, :html => {:multipart => true}) do |f| %>
  <%= f.error_messages %>

  <p> 
    <%= f.label :title, 'タイトル' %><br />
    <%= f.text_field :title %>
  </p>
  <p> 
    <%= f.label :description, '説明' %><br />
    <%= f.text_area :description %>
  </p>
  <p> 
    <%= f.label :image_file, '画像ファイル...' %><br />
    <%= f.file_field :image_file %>
  </p>
  <p> 
  <%= f.submit '送信', :disable_with => '送信中...' %>
  </p>
<% end %>

<%= link_to '戻る', images_path %>

と編集する。filenameやcontent_typeなどはサーバー側で自動的に付加できるので削除した。また、画像ファイルアップロード用に file_field を追加した。form_forの第2引数はフォームにmultipart/form-dataを指定するもので、ファイルをアップロードするときに必要になる。

次にmodelを書く。app/models/image.rb を開いて

class Image < ActiveRecord::Base
  def image_file=(f)
    @image_file = f
    unless @image_file.blank?
      self.filename = f.original_filename.gsub(/[^\w!\#$%&()=^~|@`\[\]\{\};+,.-]/u, '')
      self.content_type = f.content_type.gsub(/[^\w.+;=_\/-]/n, '')
      self.file = f.read
    end
  end

  def validate
    if @image_file.blank?
      errors.add(:image_fille, 'が指定されていません。')
    elsif @image_file.content_type !~ /^image/
      errors.add(:image_file, 'は画像ではありません。')
    end
  end
end

となるように編集する。これで画像のアップロードが出来る。http://localhost:3000/images/new で画像ファイルを指定してアップロードすると、ちゃんとデータベースに画像が登録され、showにアクセスすると(バイナリがテキストとして)表示される。これではうれしくないのでちゃんと画像として見えるようにする。

まず、画像をバイナリとして送信するメソッドをcontrollerに作成する。app/controllers/images_controller.rbを開いて次のメソッドをImagesControllerに追加する。

  def download
    @image = Image.find(params[:id])
    send_data(@image.file,
              :filename => @image.filename, :type => @image.content_type, :disposition => 'inline')
  end

画像を一枚以上アップロードした状態で http://localhost:3000/images/download/1 にアクセスすると画像が画像として表示される。これをimgタグで参照するようにviewを書き換える。app/views/images/show.html.erb を以下のように編集する。

<p>
  <strong>タイトル:</strong>
  <%=h @image.title %>
</p>

<p>
  <strong>ファイル名:</strong>
  <%=h @image.filename %>
</p>

<p>
  <strong>説明:</strong>
  <%=h @image.description %>
</p>

<p>
  <%= image_tag url_for(:action => 'download', :id => @image.id),
                :alt => @image.filename, :title => @image.description %>
</p>

<%= link_to '編集', edit_image_path(@image) %> |
<%= link_to '戻る', images_path %>

これで http://localhost:3000/images/show/1 にアクセスするとアップロードされた画像が表示される。

edit.html.erbやindex.html.erbもそれっぽく編集する。

<p>
  <strong>タイトル:</strong>
  <%=h @image.title %>
</p>

<p> 
  <strong>ファイル名:</strong>
  <%=h @image.filename %>
</p>
  
<p> 
  <strong>説明:</strong>
  <%=h @image.description %>
</p>

<p> 
  <%= image_tag url_for(:action => 'download', :id => @image.id),
                :alt => @image.filename, :title => @image.description %>
</p>
    
<%= link_to '編集', edit_image_path(@image) %> |
<%= link_to '戻る', images_path %>
<h1>画像のリスト</h1>
  
<table summary="images">
  <tr>
    <th>サムネイル</th>
    <th>タイトル</th>
    <th>ファイル名</th> 
    <th>Content type</th>
    <th>説明</th>
  </tr>
    
<% @images.each do |image| %>
  <tr> 
    <td><%= image_tag url_for(:action => 'download', :id => image.id),
      :alt => image.filename, :title => image.description, :size => '100x100' %></td>
    <td><%=h image.title %></td>
    <td><%=h image.filename %></td>
    <td><%=h image.content_type %></td>
    <td><%=h image.description %></td>
    <td><%= link_to '表示', image %></td>
    <td><%= link_to '編集', edit_image_path(image) %></td>
    <td><%= link_to '削除', image, :confirm => '本当に削除してよろしいですか?', :method => :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to '画像をアップロード', new_image_path %>

サムネイルはまだ作ってないのでindexではimgタグでサイズを指定して縮小している。とりあえず今日はここまで。

[追記]

  • image.rbの正規表現エスケープが抜けていたので修正。
  • 何故かdispositionをdescriptionと書いてたので修正。