前提
自分の理解を深めるためのメモです。
やりたいこと
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 %>
参考

