Rails アプリで画像などのファイルを保存したいと思った際、有力な候補として上がる gem としては CarrierWave などが挙げられるでしょう。
で、多くの記事においては
./app/uploaders/file_uploader.rb
class FileUploader < CarrierWave::Uploader::Base
storage :file
end
./app/models/post.rb
class Post < ApplicationRecord
mount_uploader :file, FileUploader
end
./app/views/posts/_form.html.erb
<%= f.file_field :file %>
./app/controllers/posts_controller.rb
class PostsController < ApplicationController
def create
post = Post.new(post_params)
post.save!
end
private
def post_params
params.require(:post).permit(:file)
end
end
といった GUI でのアップロードを前提としたやり方が説明されていると思います。
しかし、コード上で生成したファイルを CarrierWave に保存する場合、どうすれば良いのか...。気になったので調べてみました。
たとえば、以下のようなケースでこのやり方は有効になると思います。
- 何かしらのサービス(運営とユーザーが存在)を想定
- 管理画面から「ユーザーに〇〇(PDF)を送信」というボタンを押す
- その場で PDF ファイルが自動生成され、ダイレクトメッセージ(DirectMessage)に添付してユーザーに送信
「PDF ファイルが自動生成され」
という部分がキモです。ここが「GUI でのアップロードを介さずに」が意味するところですね。
StringIO クラスを使用
結論から言うと、StringIO クラス を使います。
参照: RubyのStringIOから値を回収する
まず、StringIo クラスを継承した CarrierwaveStringIO というクラスを作成。
./lib/carrierwave_string_i_o.rb
class CarrierwaveStringIO < StringIO
def initialize(filename, content_type, data)
super(data)
@filename = filename
@content_type = content_type
end
def original_filename
@filename
end
def content_type
@content_type
end
end
あとは次のような感じで実装していけば OK です。
あくまでイメージなので、適宜自分のコード向けに調整してください。
./lib/pdf_generator.rb
# PDF の生成には Prawn(gem) を使用するのを想定
# https://github.com/cortiz/prawn-rails
class PdfWorkingConditionNoticeGenerator < Prawn::Document
def initialize
super(
page_size: "A4",
margin: 20
)
font Rails.root.join("app/assets/fonts/mplus-2p-regular.ttf")
header
move_down 30
contents
end
private
def header
text "Test PDF"
end
def contents
text "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
end
end
./app/models/direct_message.rb
class DirectMessage < ApplicationRecord
mount_uploader :file, FileUploader
# GUI からアップロードされたファイルではなくコード上で生成したファイルを carrierwave に渡すためのメソッド
def attach_file_not_via_gui_upload(filename, content_type, data)
self.file = CarrierwaveStringIO.new(filename, content_type, data)
end
end
./app/controllers/direct_messages_controller.rb
class DirectMessagesController < ApplicationController
def send_pdf_to_user
direct_message = DirectMessage.new(
user_id: params[:user_id],
body: "〇〇(PDF)を送信しました"
)
filename = "test.pdf"
pdf = PdfGenerator.new
direct_message.attach_file_not_via_gui_upload(filename, "application/pdf", pdf.render)
direct_message.save!
end
end
あとがき
以上、GUI でのアップロードを介さず CarrierWave にファイルを保存する方法でした。
同じような疑問を抱えていた人は参考にしてみてください。