Railsで添付ファイルを扱う attachment-fu プラグイン (その1)

社内のシステムを作っていて何かと要望として多いのが、添付ファイルを保存できないかということだ。スキャンしたデータなどをとりあえずサーバーにアップロードしておいて、好きな時にダウンロードできるようにしたいという。うちのような税理士業界では、少しはIT化(死語のような気もするが・・・)が進んだとは言え、デジタル化されていない紙の資料がまだまだ山のように存在するのだ。

Rails で添付ファイルを保存したり、ファイルの内容の検証や加工を行うためのプラグインとして代表的なものに、attachment-fu というものがあった。

手元にあった本「Railsレシピブック 183の技」にも載っていたので、これを参考にやってみた。

環境

Ubuntu 8.0.4
jruby 1.1.4
Rails 2.1.1
MySQL 5.0

今回は例題として、作家(Novelists)のリストがあって、それにその作家の著書の添付ファイルが付くという簡単なアプリにしてみよう。
作家の情報を表現するクラスを「Novelist」とし、それに対応するテーブルを「novelists」とする。

では早速 attachment-fu プラグインをインストールしてみる。

jruby -S script/plugin install http://svn.techno-weenie.net/projects/plugins/attachment_fu/

続いて、添付ファイルのメタデータ(ファイル名、サイズ、Content-Type)を表現するためのクラスとそれに対応するDBのテーブルを作成する。
ここでは「Attachment」という名前にする。(名前は何でもOK)

jruby -S script/generate model Attachment

attachment-fuプラグインでは、この添付ファイルを表現するモデルに以下の4つの属性が必要となる。
1. size (integer・添付ファイルのサイズ)
2. content_type (string。添付ファイルのContent_Type)
3. filename (string・ファイル名)
4. db_file_id (integer・実際の添付ファイルを保存する先のテーブルとの紐付けのためのID)

Attachmentクラスは、作家を表すNovelistクラスを参照し、多対1の関係になっているものとすると、上記4つの属性にもう一つ、このNovelistクラスを参照するための属性として、「novelist_id」も必要となってくるのでこれを追加しよう。

db/migrate/yyyymmddxxxxxx_create_attachments.rb ファイルを編集


class CreateAttachments < ActiveRecord::Migration
def self.up
create_table :attachments do |t|
t.integer :size
t.string :content_type
t.string :filename
t.integer :db_file_id
t.integer :novelist_id

t.timestamps
end
end

def self.down
drop_table :attachments
end
end

次に、作家を表すモデル「Novelist」を生成する。

jruby -S script/generate model Novelist name:string

次に、実際に添付ファイルを保存するテーブルを作成する。先ほど作成した「Attachment」はあくまでもメタデータを保存するものなので、実体はいまから作成する「db_files」というテーブルに保存される。attachment-fu の仕様上、「db_files」という名前は、他の名前に変更すると動かないようだ。

jruby -S script/generate migration create_db_files

db/migrate/yyyymmddxxxxxx_create_db_files.rb ファイルを編集


class CreateDbFiles < ActiveRecord::Migration
def self.up
create_table :db_files do |t|
t.binary :data , :null=>false , :default=>''
t.timestamps
end
end

def self.down
drop_table :db_files
end
end

rakeコマンドで、DBとテーブルを生成

rake db:create
rake db:migrate

モデルに添付ファイルを扱う宣言「has_attachment」と、Novelistクラスとの関係を追記する。
app/models/attachment.rb を編集


class Attachment < ActiveRecord::Base
has_attachment
belongs_to :novelist
end

さらに、NovelistモデルがAttachmentモデルから参照されているので、
app/models/novelist.rb を編集


class Novelist < ActiveRecord::Base
has_many :attachments
end

ここまででとりあえず添付ファイルを扱う前準備ができたことになる。
ここからは実際にViewとControllerに手を入れていこうと思うが、このサンプルアプリの簡単な仕様を決めておくことにしよう。

1. 作家の一覧画面では作家の名前と添付ファイルが一覧表示され、添付ファイルをクリックしたらダウンロードできる
2. 作家の新規作成画面では、作家の名前と添付ファイルのアップロードが可能

添付ファイルの編集や削除、複数添付ファイルがある場合については次回以降の記事で書こうと思うので、今回はかなりシンプルに、登録と表示というごく簡単な機能だけを紹介する。

Scaffoldを作成しておこう。

jruby -S script/generate scaffold novelist

まずは登録画面(view)から
app/views/novelists/new.html.erb の編集(赤色部が追記したところ)


<h1>New novelist</h1>

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

<%= label(:novelist, :name, "作家名") -%>
<br/>
<%= text_field :novelist, :name -%>
<br/>
<br/>
<%= label(:attachment, :uploaded_data, "添付ファイル") -%>
<br/>
<%= file_field :attachment, :uploaded_data %>

<p>
<%= f.submit "Create" %>
</p>
<% end %>

<%= link_to 'Back', novelists_path %>

※form_forタグのところに、:html=>{:multipart=>true} を追記すると、生成されるhtmlのformタグのところに、「enctype="multipart/form-data"」が入る。
添付ファイルを送る場合は、これが必須となっている。もしこれがないと、添付ファイル名だけ送信されて実際のファイルは送信されない。

実際にはこんなhtmlが生成される

<h1>New novelist</h1>
<form id="new_novelist" class="new_novelist" method="post" enctype="multipart/form-data" action="/novelists">
<div style="margin: 0pt; padding: 0pt;">
<input type="hidden" value="d3b456561cd8736cca8c3c5881a4fa860f4b015d" name="authenticity_token"/>
</div>
<label for="novelist_name">作家名</label>
<br/>
<input id="novelist_name" type="text" size="30" name="novelist[name]"/>
<br/>
<br/>
<label for="attachment_uploaded_data">添付ファイル</label>
<br/>
<input id="attachment_uploaded_data" type="file" size="30" name="attachment[uploaded_data]"/>
<p>
<input id="novelist_submit" type="submit" value="Create" name="commit"/>
</p>
</form>
<a href="/novelists">Back</a>

今度は、いま作った登録画面のフォームデータを受け取るコントローラーの番だ。
app/controllers/novelist_controllers.rb の編集

編集前

# POST /novelists
# POST /novelists.xml
def create
  @novelist = Novelist.new(params[:novelist])

  respond_to do |format|
    if @novelist.save
      flash[:notice] = 'Novelist was successfully created.'
      format.html { redirect_to(@novelist) }
      format.xml  { render :xml => @novelist, :status => :created, :location => @novelist }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @novelist.errors, :status => :unprocessable_entity }
    end
  end
end

編集後

# POST /novelists
# POST /novelists.xml
def create
  @novelist = Novelist.new(params[:novelist])
  if params[:attachment][:uploaded_data] != ""
    @attachment = Attachment.new(params[:attachment])
    @attachment.novelist = @novelist
  end

  respond_to do |format|
    begin
      Novelist.transaction do
        @novelist.save!
        if params[:attachment][:uploaded_data] != ""
          @attachment.save!
        end
      end
      flash[:notice] = 'Novelist was successfully created.'
      format.html { redirect_to(@novelist) }
      format.xml  { render :xml => @novelist, :status => :created, :location => @novelist }
    rescue
      format.html { render :action => "new" }
      format.xml  { render :xml => @novelist.errors, :status => :unprocessable_entity }
    end
  end
end

登録機能ができたので、次は一覧表示画面だ。
作家名と添付ファイル名が出る一覧画面にする。
app/views/index.html.erb を編集(赤色部が追記したところ)


<h1>Listing novelists</h1>

<table border="1" cellspacing=0>
<tr>
<td>作家名</td>
<td>添付ファイル</td>
</tr>

<% for novelist in @novelists %>
<tr>
<td><%=h novelist.name %></td>
<td>
<% novelist.attachments.each do |a| %>
<%= link_to a.filename, :controller => "novelists",:action=>"download_attachment",:id=>novelist.id %>
<% end %>
</td>
<td><%= link_to 'Show', novelist %></td>
<td><%= link_to 'Edit', edit_novelist_path(novelist) %></td>
<td><%= link_to 'Destroy', novelist, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>

<br />

<%= link_to 'New novelist', new_novelist_path %>

よし。とりあえず一覧画面、新規登録画面はできたので、アクセスしてみよう。

http://localhost:3000/novelists

まだ何も登録されていない一覧画面が表示された
一覧画面の「New novelist」をクリックすると

新規登録画面が表示された

新規登録画面で、作家名を入力し、添付ファイル欄に書籍の画像を選んで「Create」ボタンを押す

無事登録完了。「Back」ボタンを押して一覧画面を見てみよう

作家名と添付ファイルが表示されている


さぁ、今度は一覧画面に表示されている添付ファイル名をクリックするとダウンロードされる機能をつける必要がある。
先ほど、app/views/index.html.erb の中で、クリックされたら AttachmentsControllers の download_attachment メソッドが呼ばれるように書いたので、コントローラーにメソッドを追加する。

app/controllers/novelist_controllers.rb に以下のコードを追記


def download_attachment
@attachment = Novelist.find(params[:id]).attachments.find(:first)
send_data(@attachment.current_data, :type=>@attachment.content_type)
end


もう一度一覧画面に戻って、添付ファイル名をクリックしてみる

プログラムで開いてみる。

添付ファイルが表示された!


今のところ日本語名のファイルだと db_files に書き込まれないという状態だが、半角英数字のファイル名であれば問題ない。

今日はここまでにして、次回は添付ファイルの削除や複数添付ファイルのアップロードなどをしていこう。