🖼️

一度に複数の画像を投稿でき、画像が投稿されなかった時は用意していた画像を表示させる

2023/11/27に公開

画像投稿機能を作成

画像を投稿できるように今回は、ActiveStorageを使います。

ActiveStorageとは

Railsで画像の投稿や表示を行うためのものです。画像は通常のカラムとして保存できないため、特別な保存方法が必要になり、それを行うのがActiveStorageです。

ActiveStorageをインストール

ターミナル
アプリケーション名 $ rails active_storage:install

インストールが完了するとdb/migrateディレクトリ内にActiveStorageのマイグレーションファイルが追加されます。
#
その後migrateさせ、schema.rbを確認し、反映されてることを確認

ターミナル
$ rails db:migrate

db/schema.rbには、+の部分が追加されます。

db/schema.rb
 ActiveRecord::Schema.define(version: migrate日時) do
 
+  create_table "active_storage_attachments", force: :cascade do |t|
+    t.string "name", null: false
+    t.string "record_type", null: false
+    t.bigint "record_id", null: false
+    t.bigint "blob_id", null: false
+    t.datetime "created_at", null: false
+    t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
+    t.index ["record_type", "record_id", "name", "blob_id"], name: +"index_active_storage_attachments_uniqueness", unique: true
+  end
+
+  create_table "active_storage_blobs", force: :cascade do |t|
+    t.string "key", null: false
+    t.string "filename", null: false
+    t.string "content_type"
+    t.text "metadata"
+    t.string "service_name", null: false
+    t.bigint "byte_size", null: false
+    t.string "checksum", null: false
+    t.datetime "created_at", null: false
+    t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
+  end
+
+  create_table "active_storage_variant_records", force: :cascade do |t|
+    t.bigint "blob_id", null: false
+    t.string "variation_digest", null: false
+    t.index ["blob_id", "variation_digest"], name: +"index_active_storage_variant_records_uniqueness", unique: true
+  end
+
  create_table "lists", force: :cascade do |t|
    t.string "title"
    t.string "body"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
+
+  add_foreign_key "active_storage_attachments", "active_storage_blobs", column: +"blob_id"
+  add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: +"blob_id"
 end

以上でActiveStorageのインストール完了です。

モデルに記述を追加する

ActiveStorageを使って画像を表示する際には、どのモデルに対して画像を使うのかを宣言する必要があります。

今回は、Listモデルに画像をつけたいので、app/models/list.rbを開きましょう。 ファイルが開けたら、以下のように記述します。

app/model/list.rb
 class List < ApplicationRecord
+  has_many_attached :list_images
 end

今回は複数の画像を投稿したいのでhas_many_attachedを使います。
list_imagesに関しては、list_imagesカラムが追記されたと考えてください。

コントローラを編集

app/controllers/lists_controller.rb
 class ListsController < ApplicationController
 :
 :
 :
   private
   # ストロングパラメータ
   def list_params# 保存機能を追加
-    params.require(:list).permit(:title, :body)
+    params.require(:list).permit(:title, :body, list_images: [])
   end
 end

※ここで重要なことは、:lise_imagesにせず、list_images: []とすることとすることで、パラメーターを許容させます。

新規投稿viewを編集

app/views/lists/new.html.erb
 <h1>新規投稿</h1>
 <%= form_with model: List.new do |f| %>
   <h4>タイトル</h4>
   <%= f.text_field :title %>
 
   <h4>本文</h4>
   <%= f.text_area :body %>
 
+  <h4>画像</h4>
+  <%= f.file_field :list_images, multiple: true %> 

   <%= f.submit '投稿' %>
 <% end %>

※ここで重要なことは、file_fieldにmultiple: trueを追記して、複数ファイルの選択を許可していることです。

投稿詳細viewを編集

app/views/lists/show.html.erb
 <h2>タイトル</h2>
 <p><%= @list.title %></p>
 
 <h2>本文</h2>
 <p><%= @list.body %></p>

+<% @list.list_images.each do |image| %>
+  <%= image_tag image, size: "200x200" %> <br>
+<% end %>

 <%= link_to "編集", edit_list_path(@list.id) %>
 <%= link_to "削除", list_path(@list.id), method: :delete, "data-confirm" => "本当に削除しますか?" %>

※ここで重要なことは、each文を使用し、保存された画像データを表示していることです。

投稿編集viewを編集

app/views/lists/edit.html.erb
 <h1>編集画面</h1>
 
 <%= form_with model: @list, url: list_path(@list.id), method: :patch do |f| %>
 
   <h4>タイトル</h4>
   <%= f.text_field :title %>
 
   <h4>本文</h4>
   <%= f.text_area :body %>
   
+  <h4>画像</h4>
+  <%= f.file_field :list_images, multiple: true %> 
 
   <%= f.submit '保存' %>
 
 <% end %>

新規投稿viewと同じです。

画像が投稿されなかった時は用意していた画像を表示させる

これで複数画像の投稿機能は完成しました。ですが、画像が投稿されなかった時の記述と用意をしてません。次は、それらを準備しましょう。

現状

複数の画像を投稿できるようになった。
詳細画面で画像を投稿した場合のみ、投稿した画像が表示される。

やりたいこと

詳細画面で画像を投稿していない場合、あらかじめ設定した画像が表示されるようにしたい。

画像を用意する

画像が投稿されなかった時に用意する画像を準備します。
今回は、app/assets/imagesディレクトリにno_image.jpgという名前で表示させる画像を保存します。

投稿詳細viewを編集

app/views/lists/show.html.erb
 <h2>タイトル</h2>
 <p><%= @list.title %></p>
 
 <h2>本文</h2>
 <p><%= @list.body %></p>

+<% if @list.list_images.attached? %>
   <% @list.list_images.each do |image| %>
     <%= image_tag image, size: "200x200" %> <br>
   <% end %>
+<% else %>
+  <%= image_tag 'no_image', size: "200x200" %><br>
+<% end %>

 <%= link_to "編集", edit_list_path(@list.id) %>
 <%= link_to "削除", list_path(@list.id), method: :delete, "data-confirm" => "本当に削除しますか?" %>

if文を使用し、画像が投稿されなかった時の記述を追記しました。これで画像が投稿されなかった場合、あらかじめ用意した画像が表示されるようになります。

EX.画像の投稿枚数に制限をつける。

JavaScriptを使用して、画像の選択枚数が3枚以上だった場合、ファイルの選択が解除され、アラートが表示されるようにします。

新規投稿viewを編集

app/views/lists/new.html.erb
 <h1>新規投稿</h1>
 
 <%= form_with model: List.new do |f| %>
   <h4>タイトル</h4>
   <%= f.text_field :title %>
 
   <h4>本文</h4>
   <%= f.text_area :body %>

   <h4>画像</h4>
   
-  <%= f.file_field :list_images, multiple: true %><br>
+  <%= f.file_field :list_images, multiple: true, id: 'list_images' %>
 
   <%= f.submit '投稿' %>
 <% end %>

id: 'list_images'を追加します。

投稿編集viewを編集

app/views/lists/edit.html.erb
 <h1>編集画面</h1>
 
 <%= form_with model: @list, url: list_path(@list.id), method: :patch do |f| %>
 
   <h4>タイトル</h4>
   <%= f.text_field :title %>
 
   <h4>本文</h4>
   <%= f.text_area :body %>
 
   <h4>画像</h4>
-  <%= f.file_field :list_images, multiple: true %>
+  <%= f.file_field :list_images, multiple: true, id: 'list_images' %>
 
   <%= f.submit '保存' %>
 
 <% end %>

新規投稿画面と同様にid: 'list_images'を追加します。

jsファイルを作成

app/javascriptフォルダ内でファイルを作成して、そこへ記述します。

app/javascript/list_images.js
+document.addEventListener('DOMContentLoaded', function () {
+  const maxFileCount = 3; // 最大ファイル選択数
+  const fileInput = document.querySelector('#list_images');
+
+  fileInput.addEventListener('change', function () {
+    if (fileInput.files.length > maxFileCount) {
+      alert(`最大で ${maxFileCount} 枚までしか選択できません。`);
+      // ファイルをリセットする
+      fileInput.value = '';
+    }
+  });
+});

application.jsにインポート

次にapp/javascript/packs/application.jsに上記で作成したファイルをインポートします。

app/javascript/packs/application.js
 :
 :
 import Rails from "@rails/ujs"
 import Turbolinks from "turbolinks"
 import * as ActiveStorage from "@rails/activestorage"
 import "channels"
+import "list_images.js"
 
 Rails.start()
 Turbolinks.start()
 ActiveStorage.start()
 
 :
 :

これで、画像の選択枚数が3枚以上だった場合、ファイルの選択が解除され、アラートが表示されるようになりました。

バリデーションの設定

JavaScript でのバリデーションは、ちょっと知識のある人がその気になれば、簡単にすり抜けできてしまいます。
なのでセキュリティ上の理由により、サーバ側(rails側)のバリデーションは必ず実装する必要があります。

モデルにバリデーションを記述します

app/model/list.rb
 class List < ApplicationRecord
 
   has_many_attached :list_images
 
+  FILE_NUMBER_LIMIT = 3
 
+  validate :validate_number_of_files
 
+  private
 
+  def validate_number_of_files
+  return if list_images.length <= FILE_NUMBER_LIMIT
+  errors.add(:list_images,"に添付できる画像は#{FILE_NUMBER_LIMIT}件までです。")
+  end
 end

これで完成!!

あとがき

私はこの記述で実装しましたが、ところどころ自身で調べながら記述していったのでもっと良い書き方もあると思います。あたたかい目で見てください。
間違ってるところや効率のいい方法などあればアドバイスいただけると助かります!

Discussion