Top View


Author Koki Matsumoto

Rails6での様々な画像プレビュー

2021/06/10


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 #33303https://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

Koki Matsumoto

Ruby/Python/Reactやってます。