railsでは、5→6にグレードアップした際に active storageの保存のタイミングが変更されました。
そこでrails5では簡単に実装できたプレビュー機能ですが rails6からは少し工夫が必要になったのでここで記事にしたいと思います。
まずはルーティングです。 今回は、ユーザーテーブルとイベントテーブルを用意してユーザーがイベントを持つようにします。
また、一覧、詳細、新規作成、編集時にプレビューできるようにget,post,patchを使用します。
route.rb
Rails.application.routes.draw do
namespace :users do
resources :events do
collection do
get :preview
post :preview
patch :preview
end
end
end
end
イベントコントローラを用意しました。
編集や一覧等はparamsから渡ってくるidを参照します。
しかし、新規作成時はparams[:id]が無いのでbuildでインスタンスを作成します。
バイナリデータ(image_binary)は後述するpreviewableで作成して、エンコードしてviewに渡します。
events_controller.rb
def preview
@event = if params[:id]
Event.find_by!(id: params[:id])
else
current_user.events.build(event_params)
end
if @event.valid?
image_binary = extract_image_binary(@event)
@image_enconded_by_base64 = Base64.strict_encode64(image_binary)
render template: 'users/events/show', layout: 'application'
else
flash[:notice] = I18n.t('views.notice.invalid_error', model: Event.t)
render 'new'
end
end
previewableではconcernに切り出しています。
条件が複雑ですが、画像がアップロードされている場合のみバイナリデータを返すようにしています。
新規作成 |- 画像がアップロードされている場合 編集、一覧、詳細 |- 既に画像が保存されている場合 |- 画像が保存されていない場合 |- 画像がアップロードされていない場合
・Store newly-uploaded files on save rather than assignment #33303 ( https://github.com/rails/rails/pull/33303 )
・Reading from Active Storage Attachment Before Save ( https://stackoverflow.com/questions/57564796/reading-from-active-storage-attachment-before-save )
上記を参考にして
instance.attachment_changes['thumbnail_image'].attachable.read
instance.thumbnail_image.blob
params[:event][:thumbnail_image].read
状況に応じて様々な方法でバイナリを取得できました。
previewableでは、以下のようにバイナリデータを返します。
concerns/previewable.rb
module Previewable
extend ActiveSupport::Concern
def extract_image_binary(instance)
# 新規作成時でかつ画像がアップロードされている場合、バイナリを返す
if instance.new_record? && instance.thumbnail_image.attached?
return instance.attachment_changes['thumbnail_image'].attachable.read
end
# 編集時のインスタンスに画像が保存されている場合、画像のバイナリを返す
return instance.thumbnail_image.blob.download if instance.thumbnail_image.blob
# 編集時のインスタンスに画像が保存されていない場合
if params[:event] && params[:event][:thumbnail_image]
# 画像がアップロードされている場合、バイナリを返す
params[:event][:thumbnail_image].read
else
''
end
end
end
一覧ページではlink_toでプレビューさせます。
previewアクションに飛ばすだけなのでシンプルですね。
index.html.erb
<%= link_to preview_users_events_path(id: event), class: 'btn btn-sm btn-secondary', target: :_blank do %>
<%= t("views.role.preview") %>
<% end %>
編集と新規作成時にはsubmit以外に遷移したいのでformactionを追加してそれぞれのパスを追記しています。
また、Railsのsubmit等では自動でauthenticity_tokenが付与されますが,
今回はformactionでプレビューさせているのでこちらでauthenticity_tokenを付与させる必要があります。
_form.html.erb
<%= form_with(model: [:users, model] ) do |f| %>
.
.
.
<div class="card-footer">
<button type="submit" class="btn btn-primary"><%= t('views.role.submit') %></button>
<% if action_name == 'edit' %>
<%= button_tag t('views.role.preview'), formaction: preview_users_events_path(id: @event, authenticity_token: form_authenticity_token),
class: 'btn btn-secondary', formtarget: '_blank' %>
<% else %>
<%= button_tag t('views.role.preview'), formaction: preview_users_events_path(authenticity_token: form_authenticity_token),
class: 'btn btn-secondary', formtarget: '_blank' %>
<% end %>
</div>
<% end %>
まとめ
今回はRails6での画像のプレビューについてまとめました。
インスタンスがDBに保存されたタイミングで画像がactive storageに保存されるようになったので少し工夫が必要でした。
以下のように様々な場面で画像を取得できます。
どなたかのお役に立てれば幸いです。
instance.attachment_changes['thumbnail_image'].attachable.read
instance.thumbnail_image.blob
params[:event][:thumbnail_image].read
参考
・Store newly-uploaded files on save rather than assignment #33303 ( https://github.com/rails/rails/pull/33303 )
・Reading from Active Storage Attachment Before Save ( https://stackoverflow.com/questions/57564796/reading-from-active-storage-attachment-before-save )
Koki Matsumoto
Ruby/Python/Reactやってます。