今回はRails6から画像の保存のタイミングが変更したようなので調べてみました。
#Rails バージョンの確認
$ rails -v
Rails 6.0.2.1
#ActiveStrageのインストール
$ rails active_storage:install
$ rails db:migrate
#画像添付の準備
最終成果物を載せます。
routeにpreviewを追加します。
Rails.application.routes.draw do
resources :users do
collection do
post :preview
patch :preview
end
end
end
モデルにhas_one_attachedで画像を扱えるようにします。
class User < ApplicationRecord
has_one_attached :image
end
次にcontrollerです。
今回はプレビュー機能をつけたいのでpreviewアクションを追加。
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です。
<h1>New User</h1>
<%= render 'form', user: @user %>
<%= link_to 'Back', users_path %>
<%= 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 %>
<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 %>
<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での画像の保存のタイミング
早速画像をアップロードしていきます。
テスト.jpegをアップロードしてCreate User
ボタンを押すと
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を試みます。
同じようにプレビュー.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)
できました!!
#まとめ
ActiveStrageの保存のタイミングに変更があるなんて知りませんでした。
保存せずに画像を触りたいなら
record.attachment_changes['<attributename>'].attachable.read
でバイナリ読み出してBase64!!(Base64でなくてもいいけど)
他に何かいい方法あれば教えてください。