前回、ActiveStorage を使って画像を1枚添付するやり方について見ていきました。
今回はその続きで、ActiveStorage を使って複数枚の画像を添付できるようにしていきたいと思います。
まず、Rails 側を修正していきます。
# app/models/post.rb class Post < ApplicationRecord has_many_attached :images # 変更 # 省略 validates :images, content_type: [ "image/png", "image/jpeg" ] end
前回、モデルにバリデーションを指定していなかったため、アップロードできるファイルを png と jpeg のみに限定しました。
# app/controllers/posts_controller.rb class PostsController < ApplicationController # 省略 private def post_params params.expect(post: [ :title, :body, images: [] ]) # image を images: [] に修正 end end
# app/views/posts/_post.json.jb { id: post.id, created_at: post.created_at, title: post.title, body: post.body, image_urls: post.images.map { rails_blob_url(it) } # 修正 / 画像が添付されていない場合は空の配列 }
続いてEmber.js 側の修正です。ベースはできているため複数枚の画像を選択してリクエストできるようにしていきます。
// web/app/components/post-form.js import Component from '@glimmer/component'; import { DirectUpload } from '@rails/activestorage/src/direct_upload'; import { action } from '@ember/object'; export default class PostFormComponent extends Component { @action uploadImage(e) { this.args.post.images = []; for (const file of e.target.files) { const upload = new DirectUpload( file, 'http://localhost:3000/rails/active_storage/direct_uploads', {}, ); upload.create((error, blob) => { if (error) { console.error(error.message); } else { this.args.post.images.push(blob.signed_id); } }); } } }
今回は複数枚の画像を選択してそれぞれの signed_id を Rails 側へリクエストする必要があります。そのために this.args.post.images
の空の配列を用意して FileList を反復処理しながら this.args.post.images
へ signed_id を詰めていきます。
そしてそれをリクエスト時に送るようにします。
// web/app/controllers/admin/posts/new.js import Controller from '@ember/controller'; import { action } from '@ember/object'; import { service } from '@ember/service'; export default class AdminPostsNewController extends Controller { @service router; @service toast; @service session; @action async createPost(event) { event.preventDefault(); const response = await fetch('http://localhost:3000/posts', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.session.token}`, }, body: JSON.stringify({ post: { title: this.model.title, body: this.model.body, images: this.model.images, // images に修正 }, }), });
ロジック部分はこれで修正できました!
今度は画像選択ボタンの見た目を整えていきます。
現在の画像選択ボタンはこんな見た目です。
<input type="file" id="file" name="file" multiple {{on "change" this.uploadImage}} />
以下のように input
タグに hidden
属性を付け、label
タグの for
属性で hidden
にした input
を参照することで、bootstrap のボタンデザインを適用した状態でファイル選択ができます。
また、Rails 側でバリデーションを追加したためエラーがあった場合の対応もしています。
<!-- web/app/components/post-form.hbs --> <input type="file" id="file" name="file" class="{{if @post.errors.images "is-invalid"}}" multiple {{on "change" this.uploadImage}} hidden /> <label for="file" class="btn btn-outline-primary"> Choose image </label> {{#each @post.errors.images as |error|}} <div class="invalid-feedback"> {{error}} </div> {{/each}}
画像選択ボタンにデザインを当てたことで、何枚の画像を選択しているのかが表示されなくなっているのですが、今回は最終的にマークダウンで任意の箇所に画像を挿入できることを目指しているため、この状態で問題ありません。
後は画像の表示を複数枚対応に修正していきます。
<!-- web/app/components/post.hbs --> <div class="body card-text"> {{@post.body}} {{#each @post.image_urls as |url|}} <img src={{url}} alt="" class="img-fluid"> {{/each}} </div>
これで複数枚の画像を添付できるようにする修正が完了です!
まとめ
いきなり複数枚画像の添付を目指さず、まずは1枚添付できることを確認して修正をしたことで複数枚対応が進めやすくなりました。
次回はブログ記事の投稿をマークダウンでできるようにしていく部分を見ていきます。