前提
自分の理解を深めるためのメモです。
やりたいこと
Railsでファイルをアップロードしたい。
実装
コントローラで受け取る
POSTリクエストで送られるので、subject_dataコントローラのcreateアクションに送られる。
class SubjectDataController < ApplicationController
def create
uploaded_file = params[:file]
binding.pry
end
end
フォームで<%= file_field_tag :file %>
と設定したので、params[:file]
で受け取れる。
pry(#<SubjectDataController>)> uploaded_file
=> #<ActionDispatch::Http::UploadedFile:0x0000000122986098
@content_type="text/rtf",
@headers="Content-Disposition: form-data; name=\"file\"; filename=\"ファイルアップロードのサンプル.rtf\"\r\nContent-Type: text/rtf\r\n",
@original_filename="ファイルアップロードのサンプル.rtf",
@tempfile=#<File:/var/folders/l_/r0xf9zf9603bhy8ym0lqxsdc0000gn/T/RackMultipart20240521-14058-8hvjxb.rtf>>
指定のディレクトリに格納する
uploaded_file = params[:file]
excel_file_path = Rails.root.join("public/uploads/#{uploaded_file.original_filename}")
File.open(excel_file_path, 'w+b') do |file|
file.write(uploaded_file.read)
end
Rails.root
でアプリのルートディレクトリを取得できる。
続くjoin
はRubyのjoin
メソッドではなく、RailsのRails.root.join
でファイルパスを取得できる。
ファイルで送られたファイルデータをparams[:file]
で受け取り、それをuploaded_file
に代入した。中身を見てみると、original_filenameプロパティーがある。それを使ってファイル名を取得する。
そしてファイルパスをexcel_file_path
に代入。(必須ではないがコードが読みやすくなると考えて入れた)
File.open(excel_file_path, 'w+b') do |file|
file.write(uploaded_file.read)
end
File.open
の第1引数はファイルパス、第2引数はモード。
File.open
は指定のファイルパスからファイルを取得するイメージだが、ファイルが存在しない場合も使える。
ファイルが存在しない場合は、新しくファイルを作成してオープンする。(今回はこっち)
読み込みモードだとエラーになります。書き込みモードのときだけでした。
ファイルを書き込みモードでオープンします。オープン時にファイルがすでに存在していればその内容を空にします。
https://docs.ruby-lang.org/ja/latest/method/Kernel/m/open.html
開いたファイルを|file|
とブロックで受け取って処理を渡す理由は、ブロックだと処理が終わったあと自動でクローズしてくれるかららしい。
新しく作成してオープンしたファイルは中身が空なので、さっきフォームから受け取ったファイルの内容を文字列として読み取って、その内容で上書きする。
ファイルに上書きするところの処理で、文字コードのエラーが出た。
なぜだろう?と思っていたが、そうか、File.open
の第2引数の読み取りモードとか書き取りモードとか設定するところで、バイナリーモードを外していたからだ。さっきはバイナリーモードを設定していたからエラーにならなかったんだ。
これらのいずれに対しても "b" フラグを ("r+b"のように) つけることができます (整数なら File::BINARY )。この場合、バイナリモードでオープンします (ただし、DOS/Windowsのようにシステムがテキスト/バイナリでファイルを区別する場合に限ります)
https://docs.ruby-lang.org/ja/latest/method/Kernel/m/open.html
完成したコード
def create
uploaded_file = params[:file]
excel_file_path = Rails.root.join("public/uploads/#{uploaded_file.original_filename}")
File.open(excel_file_path, 'w+b') do |file|
file.write(uploaded_file.read)
end
end
<%= form_with url: "/subject_data", multipart: true do |form| %>
<%= file_field_tag :file %>
<%= form.submit %>
<% end %>
参考