以下のサイトがわかりやすかった。
http://tolarian-academy.net/create-new-rails-react-app-pt2/
S3に画像の投稿…あたりからみていただけると良いかと
①画像データをStateに保存する
ここでの注意点は2つ
- file_fieldはvalueを変更できないのでスクリプト内でファイルを取得する別メソッドを使う
- file_readerを使う
<FormBlock>
<label htmlFor="avatar">アバター画像</label>
<input type="file" name="avatar" id="avatar" accept="image/*,.png,.jpg,.jpeg,.gif" onChange={props.change}/>
</FormBlock>
こういうfile_fieldがあったときに画像を選択した瞬間にStateに画像情報を入れる。後でStateの情報をaxiosでサーバーに送るためです。
onChangeイベントではfile_filedに変更があった際に以下のように処理を行う。
ファイルはstateのuserのavatarとして管理され、axiosではuserというキーでuser全体を送信している。
const file = e.target.files[0];
const reader = new FileReader()
reader.onload = () => {
user.avatar.data = reader.result
user.avatar.filename = file.name
}
reader.readAsDataURL(file)
this.setState({
user: user
})
axios
.post('/api/v1/users', {user: this.state.user} )
file情報はchangeイベントのe.target.files[0]で取得できる。
reader.onloadでファイルを読み込む記述を入れる。ここではfileのdataとなる文字列をreader.resultで取得し、
fileの名前をfile.nameで取得する。これらの情報がActiveStorage::Bolbsの保存で必要になる。
②コントローラーで暗号化された画像データを復元する
今のままだとファイルの情報は文字列なのでdecodeしてファイル情報に変換する必要がある。
def create
@user = User.new(sign_up_params)
if @user.valid?
if params[:user][:avatar]
blob = ActiveStorage::Blob.create_after_upload!(
io: StringIO.new(decode(params[:user][:avatar][:data]) + "\n"),
filename: params[:user][:avatar][:filename]
)
@user.avatar.attach(blob)
end
@user.save
render json: { user: @user }
else
render status: 422, json: { errors: @user.errors.full_messages } #手動でステータス入れないと200になるぽい
end
end
def decode(str)
Base64.decode64(str.split(',').last)
end
今回は以下の部分で画像データを保存し、ユーザーのデータに添付する
if params[:user][:avatar]
blob = ActiveStorage::Blob.create_after_upload!(
io: StringIO.new(decode(params[:user][:avatar][:data]) + "\n"),
filename: params[:user][:avatar][:filename]
)
@user.avatar.attach(blob)
先程、axiosを使った際のparamsが
{user: this.state.user}
となっており、この構造がこんな感じ
this.state = {
user: {
nickname: '',
email: '',
password: '',
password_confirmation: '',
avatar: {
data: '',
filename: ''
}
},
なのでfileのdataは
params[:user][:avatar][:data]
fileの名前は
params[:user][:avatar][:filename]
で取り出せる。
まずはファイル情報を複合する処理を行う。それが以下の処理
def decode(str)
Base64.decode64(str.split(',').last)
end
クソ長い文字列を短くしてくれている感じ。
filenameはそのまま取り出す。
filename: params[:user][:avatar][:filename]
これで画像が保存できる。あとはuserにattachして完了。
@user.avatar.attach(blob)
挙動
こんな感じになります。少しわかりにくいかもですが
非同期にした割にはあんまり処理が速くなっていないのが気になるところです。
調べること
正直コピペでなんとなく実装したのでBase64とかfilereaderの仕組みが理解できていない。これは後で調べる。
とにもかくにもaxiosで画像保存できたので良しとする。