画像ファイルをアップロードして自由に表示したい!その4
複数の画像ファイルを扱えるようにしてみた。ファイルを保存する時に、id番号のフォルダを作成して、その中にファイルを保存する。ファイルやフォルダに関する処理はモデルで処理することにした。
ファイルは以下のように保存される。
モデル
- ファイルを保存する時は、id番号のディレクトリ/ファイル名のように階層管理することにした。
- destroy_filesは、ファイルをフォルダ後と削除するメソッド。
- delete_file(name)は、ファイルを一つだけnameで指定して、削除するメソッド。
- file_entriesは、id番号のフォルダに含まれるファイル名を配列で返すメソッド。
- file_stat(name)は、ファイル情報を保持したFile::Statオブジェクトを返すメソッド。edit.rhtmlでファイル情報を表示する時に利用する。
- rescue nilは、エラーを無視するために利用している。
# ------ app/models/software.rb ------ class Software < ActiveRecord::Base after_save :save_file after_destroy :destroy_files #validates_presence_of :file_name def validate errors.add(:file, "アップロードするファイルを指定してください。") if @file.nil? || @file.size == 0 # content_typeが、image/ で始まっていなければ、エラーにする。 errors.add(:file, "画像ファイルではありません。") unless @file.nil? || /^image\// =~ @file.content_type end # Tempfileオブジェクトをインスタンス変数に保存する。 # new()や、update_attributes()の時、呼び出される。 def temp_file=(file) return if file.nil? || file.size == 0 @file = file #@old_file_path = file_path #self.file_name = file.original_filename #self.file_type = file.content_type #self.file_data = file.read end # ファイルを保存する。 # saveメソッドの後、呼び出される。 def save_file #File.delete(@old_file_path) rescue new_directory new_directory File.open(file_path + @file.original_filename, "wb") do |f| f.write(@file.read) end end # idディレクトリと、その中身を全て削除する。 # destroyメソッドの後に呼び出される。 # 削除されたレコードのid、file_name等のフィールドにアクセス可能。 # 削除するファイルやディレクトリが存在しないとエラーになるので、rescue nilでエラーを無視する。 def destroy_files file_entries.each do |f| delete_file(f) end Dir.delete(file_path) rescue nil end # 引数nameのファイルを削除する。 def delete_file(name) File.delete(file_path + name) rescue nil end # idに紐付くファイルを全て取得する。 # 「.」と「..」もファイルに含まれてしまうため、正規表現で「.」で始まるファイル名を除外する。 def file_entries Dir.entries(file_path).delete_if {|f| /^\./ =~ f} end # ファイル情報を保持したオブジェクトを返す。 def file_stat(name) File.stat(file_path + name) end # RAILS_ROOTからのディレクトリを取得する。 # "#{RAILS_ROOT}/piblic"としなくてもOKのようだ。 def file_path "public" + image_source_path end # image_tagがファイルを参照するルールに合わせたパスを返す。 # デフォルトは、public/images/から参照する。 # 例)"files/1_test.png" >> "public/images/files/1_test.png" # もし先頭が/で始まれば、pblic/から参照する。 # 例)"/files/1_test.png" >> "public/files/1_test.png" # 新規作成の時、idがnilなので、rescue nilでエラーを無視する。 def image_source_path "/files/#{id}/" rescue nil end # idディレクトリを新規作成する。 # ディレクトリが既に存在する時、エラーが発生するので、rescue nilでエラーを無視する。 def new_directory Dir.mkdir(file_path) rescue nil end end
コントローラー
- ファイルを一つだけ指定して、削除するメソッド delete_file を追加した。
# ------ app/controllers/softwares_controller.rb ------ class SoftwaresController < ApplicationController ...(途中省略)... # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) verify :method => :post, :only => [ :destroy, :create, :update, :delete_file ], :redirect_to => { :action => :list } ...(途中省略)... def delete_file @software = Software.find(params[:id]) @software.delete_file(params[:file]) redirect_to :action => 'edit', :id => @software end
ビュー
- @software.file_entries.each do |f| で、id番号のフォルダ中のファイルを全て取得して、表示している。
- @software.file_stat(f).size で、ファイルサイズを取得できる。他にもいろいろな情報を取得できる。
- link_to "#{f}を削除する"... で、ファイルを一つだけ削除するリンクを設定している。パラメーターにファイル名をセットしてdelete_fileアクションを実行している。
<%#------ app/views/softwares/edit.rhtml ------%> <h1>Editing software</h1> <%= start_form_tag({:action => 'update', :id => @software}, :multipart => true) %> <% @software.file_entries.each do |f| %> <p> ファイル名 :<%= f %><br /> サイズ :<%= @software.file_stat(f).size %><br /> 最終アクセス日時:<%= @software.file_stat(f).mtime %><br /> タイプ :<%= @software.file_stat(f).ftype %><br /> <b><%= link_to "#{f}を削除する", {:action => 'delete_file', :id => @software, :file => "#{f}"}, :confirm => 'Are you sure?', :post => true %></b><br /> <%= image_tag "#{@software.image_source_path}#{f}", :alt => f %><hr /> </p> <% end %> <%= render :partial => 'form' %> <%= submit_tag 'Edit' %> <%= end_form_tag %> <%#= link_to 'Show', :action => 'show', :id => @software %> <%= link_to 'Back', :action => 'list' %>
- リスト表示についても、小さな画像を複数表示するようにしてみた。
<%#------ app/views/softwares/list.rhtml ------%> ...(途中省略)... <% for software in @softwares %> <tr> <% for column in Software.content_columns %> <td> <% if column.name == 'file_data' %> <% software.file_entries.each do |f| %> <%= image_tag "#{software.image_source_path}#{f}", :alt => f, :size => '32x32' %> <%= f %> <br /> <% end %> <% else %> <%=h software.send(column.name) %> <% end %> </td> <% end %> <td><%#= link_to 'Show', :action => 'show', :id => software %></td> <td><%= link_to 'Edit', { :action => 'edit', :id => software }, :post => true %></td> <td><%= link_to 'Destroy', { :action => 'destroy', :id => software }, :confirm => 'Are you sure?', :post => true %></td> </tr> <% end %> ...(途中省略)...
- ファイルをフォルダで階層管理しようとすると、処理がちょっと複雑になる。画像管理用のテーブルを作成して、1レコード1ファイルに対応させてしまった方が簡単かもしれない。