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


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

#Rails バージョンの確認

```
$ rails -v
Rails 6.0.2.1
```

#ActiveStrageのインストール

```
$ rails active_storage:install
$ rails db:migrate
```

#画像添付の準備
最終成果物を載せます。

routeにpreviewを追加します。


```ruby:route.rb
Rails.application.routes.draw do
  resources :users do
    collection do
      post :preview
      patch :preview
    end
  end
end
```
モデルにhas_one_attachedで画像を扱えるようにします。


```ruby:user.rb
class User < ApplicationRecord
  has_one_attached :image
end
```

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

```ruby: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です。

```ruby:new.html.erb

<h1>New User</h1>

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

<%= link_to 'Back', users_path %>
```

```ruby:_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 %>
```
```ruby: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 %>
```
```ruby:_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での画像の保存のタイミング

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

<img width="351" alt="スクリーンショット 2020-05-21 18.04.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633297/3a22bd19-00d2-065d-399b-f19ad7819c11.png">

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

<img width="835" alt="スクリーンショット 2020-05-21 18.18.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633297/a106c6c8-9fcd-e314-3926-608d271cc281.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を試みます。

<img width="395" alt="スクリーンショット 2020-05-21 18.20.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633297/833b3764-b89f-01e5-a36b-46ebd88c168e.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)
```

<img width="793" alt="スクリーンショット 2020-05-22 10.48.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633297/e0215934-3d72-2f32-db64-d80c546dd308.png">

できました！！

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

```
record.attachment_changes['<attributename>'].attachable.read
```
でバイナリ読み出してBase64！！(Base64でなくてもいいけど)

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