Railsで添付ファイルを扱う attachment-fu プラグイン (その1)
社内のシステムを作っていて何かと要望として多いのが、添付ファイルを保存できないかということだ。スキャンしたデータなどをとりあえずサーバーにアップロードしておいて、好きな時にダウンロードできるようにしたいという。うちのような税理士業界では、少しはIT化(死語のような気もするが・・・)が進んだとは言え、デジタル化されていない紙の資料がまだまだ山のように存在するのだ。
Rails で添付ファイルを保存したり、ファイルの内容の検証や加工を行うためのプラグインとして代表的なものに、attachment-fu というものがあった。
手元にあった本「Railsレシピブック 183の技」にも載っていたので、これを参考にやってみた。
環境
今回は例題として、作家(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
enddef 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 enddef 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 :novelistend
さらに、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 %>
よし。とりあえず一覧画面、新規登録画面はできたので、アクセスしてみよう。
まだ何も登録されていない一覧画面が表示された
一覧画面の「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 に書き込まれないという状態だが、半角英数字のファイル名であれば問題ない。
今日はここまでにして、次回は添付ファイルの削除や複数添付ファイルのアップロードなどをしていこう。