LoginSignup
10
3

More than 3 years have passed since last update.

【Rails 6】active_strageの画像の保存タイミングが変更された

Posted at

今回はRails6から画像の保存のタイミングが変更したようなので調べてみました。

Rails バージョンの確認

$ rails -v
Rails 6.0.2.1

ActiveStrageのインストール

$ rails active_storage:install
$ rails db:migrate

画像添付の準備

最終成果物を載せます。

routeにpreviewを追加します。

route.rb
Rails.application.routes.draw do
  resources :users do
    collection do
      post :preview
      patch :preview
    end
  end
end

モデルにhas_one_attachedで画像を扱えるようにします。

user.rb
class User < ApplicationRecord
  has_one_attached :image
end

次にcontrollerです。
今回はプレビュー機能をつけたいのでpreviewアクションを追加。

user_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    @users = User.all
  end

  # GET /users/1
  # GET /users/1.json
  def show
  end

  # GET /users/new
  def new
    @user = User.new
  end

  # GET /users/1/edit
  def edit
  end
.
.
.
.
  def preview
    @user = User.new(user_params)
    image_binary = ''
    if @user.image.attached?
      image_binary = @user.attachment_changes['image'].attachable.read
    else
      saved_user = User.find_by(id: params[:id])
      if saved_user.present? && saved_user.image.attached?
        image_binary = saved_user.image.blob.download
      end
    end
    @image_enconded_by_base64 = Base64.strict_encode64(image_binary)

    render template: 'users/_preview', layout: 'application'
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def user_params
      params.require(:user).permit(:name, :image)
    end
end

最後にhtmlです。

new.html.erb

<h1>New User</h1>

<%= render 'form', user: @user %>

<%= link_to 'Back', users_path %>
_form.html.erb

<%= form_with(model: user, local: true) do |form| %>
  <% if user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
        <% user.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
    <%= form.label :image %>
    <%= form.file_field :image %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
show.html.erb

<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @user.name %>
  <% if @user.image.attached? %>
    <%= image_tag @user.image %>
  <% end %>
</p>

<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>
_preview.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @user.name %>
  <% if action_name == 'preview' %>
    <img src="data:image/png;base64,<%= @image_enconded_by_base64 %>" />
  <% else %>
    <%= image_tag @member.image %>
  <% end %>
</p>

これで準備ができました。

Rails6での画像の保存のタイミング

早速画像をアップロードしていきます。

スクリーンショット 2020-05-21 18.04.06.png

テスト.jpegをアップロードしてCreate Userボタンを押すと

スクリーンショット 2020-05-21 18.18.57.png

createは成功しています。
ここでアップロードした画像を見に行くと

irb(main):005:0> @user.image.attachment
  ActiveStorage::Attachment Load (1.5ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 1], ["record_type", "User"], ["name", "image"], ["LIMIT", 1]]
=> #<ActiveStorage::Attachment id: 1, name: "image", record_type: "User", record_id: 1, blob_id: 1, created_at: "2020-05-21 09:04:12">

画像が存在するのがわかりました。

アップロードでは保存できていない

次に新しくpreviewを試みます。

スクリーンショット 2020-05-21 18.20.20.png

同じようにプレビュー.jpgをアップロードしてpreviewボタンを押して画像の確認をします。

すると

> @user.image.attachment
NameError: uninitialized constant #<Class:0x00007fd450065780>::Analyzable
from /usr/local/bundle/gems/activestorage-6.0.3.1/app/models/active_storage/blob.rb:26:in `<class:Blob>'

ない!!

> params[:user][:image]
=> #<ActionDispatch::Http::UploadedFile:0x000055892ec2b598
 @content_type="image/jpeg",
 @headers=
  "Content-Disposition: form-data; name=\"user[image]\"; filename=\"\xE3\x83\x95\xE3\x82\x9A\xE3\x83\xAC\xE3\x83\x92\xE3\x82\x99\xE3\x83\xA5\xE3\x83\xBC.jpg\"\r\nContent-Type: image/jpeg\r\n",
 @original_filename="プレビュー.jpg",
 @tempfile=#<File:/tmp/RackMultipart20200522-1-ha9tz1.jpg>>

パラメータを確認。
画像をアップロードしただけでは保存されないのか!

下記を参考にすると
https://github.com/rails/rails/pull/33303

rails5では

@user.image = params[:image]

attributeに入れたタイミングですね!

まさかrails6になってこんな変更があるなんて!!
このままだとプレビュー時(画像をインスタンスに保存せず)に画像をViewに渡せない。

どうにかいい方法が無いか調べていると

https://github.com/rails/rails/pull/33303

https://stackoverflow.com/questions/57564796/reading-from-active-storage-attachment-before-save

あった!!

record.attachment_changes['<attributename>'].attachable

試す!

> @user.attachment_changes['image'].attachable
=> #<ActionDispatch::Http::UploadedFile:0x000055892ec2b598
 @content_type="image/jpeg",
 @headers=
  "Content-Disposition: form-data; name=\"user[image]\"; filename=\"\xE3\x83\x95\xE3\x82\x9A\xE3\x83\xAC\xE3\x83\x92\xE3\x82\x99\xE3\x83\xA5\xE3\x83\xBC.jpg\"\r\nContent-Type: image/jpeg\r\n",
 @original_filename="プレビュー.jpg",
 @tempfile=#<File:/tmp/RackMultipart20200522-1-ha9tz1.jpg>>

さらに

@user.attachment_changes['image'].attachable.read

↑でバイナリデータを取ってくることができたのでBase64でエンコードして渡すと

@image_enconded_by_base64 = Base64.strict_encode64(@user.attachment_changes['image'].attachable.read)

スクリーンショット 2020-05-22 10.48.00.png

できました!!

まとめ

ActiveStrageの保存のタイミングに変更があるなんて知りませんでした。
保存せずに画像を触りたいなら

record.attachment_changes['<attributename>'].attachable.read

でバイナリ読み出してBase64!!(Base64でなくてもいいけど)

他に何かいい方法あれば教えてください。

10
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3