基礎編11

2021.3.17

 

掲示板の画像アップロード機能

参照

【Rails】CarrierwaveとMiniMagickを使って画像を投稿する方法|TechTechMedia

 

<使用するGemについて>

Carrierwave : アップロード用のgem。Ruby on Rails の画像投稿の仕組み

 

MiniMagick : 画像加工をしてくれるgem。前提としてImageMagickというものをインストールしておく必要がある。

 

ImageMagick画像処理ライブラリのこと。画像のサイズ、変転、回転、フォーマット変換、色調整、グラデーションなどさまざまな画像処理を行うことができる。

 

<流れ>

❶ MiniMagick を使用するために、 ImageMagickをインストールする。

$ brew install imagemagick

 

❷ Carrierwave と MiniMagick をインストールする。

Gemfileに記入し、$ bundle installを実行する。

 

❸画像のアップロードファイルを作成する。

$ rails g uploader 〇〇(アップロードファイル名)

 

ex)今回はboard_image_uploader.rbという名前のアップロードファイルを作成

$ rails g uploader BoardImage

app/uploadersディレクトディレクトリ以下にboard_image_uploader.rbが作成される。

 

❹ image_uploader.rbを編集する

デフォルトで作成されるコメントは消してしまって大丈夫。

 

(1)include CarrierWave::MiniMagickがコメントアウトされているので解除する。
コメントアウトを解除することでgem 'mini_magick'を使用可能になる。

(image_uploader.rb)

class ImageUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
include CarrierWave::MiniMagick

 

(2)画像がアップロードされていない時に、carrierwaveが自動的にデフォルトで画像を設定するようにする。

参照

【Rails】image_tagの基本とデフォルト画像の設定方法 - Qiita

ex)今回は画像が選択されていない場合は、board_placeholder.pngを表示する

①(image_uploader.rb)

これを

# def default_url(*args)
# # For Rails 3.1+ asset pipeline compatibility:
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
#
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
# end

このように変更する

def default_url
# # For Rails 3.1+ asset pipeline compatibility:
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
'board_placeholder.png'
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
end

 

②デフォルトのファイルをapp/assets/images直下にファイルを置く

今回は app/assets/images/board_placeholder.pngの形になる。

 

(3) アップロードできるファイルは jpg jpeg png gif のみに制限する。

参照

写真投稿機能の追加 | Nanayaku blog

 

これを

# def extension_allowlist
# %w(jpg jpeg gif png)
# end

このようにする

def extension_allowlist
%w(jpg jpeg png gif)
end

 

 (4)ファイルの保存場所を記載する

def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end

→DBに保存するのは画像そのものではなく、「画像ファイルの在りどころ」が保存される。だから board_image :string になる。 

<解説>

public/uploads/boards/10/foo.jpg

上の文でこれができるようになる。

public/uploads/boards/10

↑ここは勝手に推測してくれる

 

結果、このように使用できるようになる。

board = Board.first
board.board_image.url # => http://localhost:3000/uploads/boards/10/foo.jpg

 

<%= image_tag board.board_image.url %>

↓このように表すことができ

↓htmlでこのようになる

 

 

 

(5)アップロード先のフォルダを、.gitignoreに登録する

このファイルに書かれてファイルはGitのトラッキング(=追跡)の対象外になる。

ローカル環境でアップロードした画像ファイルはアップロードされない。

*すでにファイルをコミットしてから.gitignoreを追記しても、ファイルが管理対象に含まれてしまっているので、コミットした後であれば、$ git rm --cached ファイル名」 でgitの対象外に設定する。

(ここで今回出たエラー)

今回.gitignore内に/public/uploads を指定したので、このディレクトリ以下のファイルを全てgit rm したい。

$ git rm --cached public/uploads を実行したところ

fatal: not removing 'public/uploads' recursively without -r

というエラーが出た。

-rはディレクトリごとに指定するときに、必要である。
--cachedはファイルを残したいときにつけるオプションであり、その際はgit rm --cached [削除したいファイル]
という形になる。
今回このディレクトリ以下のファイルを全てgit rm したいので、
その際は$ git rm -r --cached [削除したいディレクトリ]という形になる。

(.gitignore)

# Ignore vendor
/vendor
/config/database.yml
/public/uploads #ここ追加

 

 

❹テーブルに画像のカラムを追加する

ex)今回はBoardモデルにboard_imageカラムを追加する。

$ rails g migration AddBoardImageToBoards board_image:string

class AddBoardImageToBoards < ActiveRecord::Migration[5.2]
def change
add_column :boards, :board_image, :string
end
end

$ rails db:migrate で反映させる

 

❺ 作成したアップロードファイルと関連するモデルの紐付けをする。関連するモデルの画像アップロード処理をアップローダーにやってもらうから。

ex)今回はBoardモデルと付けする。

Boadモデルに、アップローダーの仕様を宣言する。

(app/models/board.rb)

class Board < ApplicationRecord
#moubt_uploader :carriewave用に作った画像のカラム名, carrierwaveの設定ファイルのクラス名
mount_uploader :board_image(❹で作成), BoardImageUploader(↓)
belongs_to :user
end

↓carrierwaveの設定ファイルのクラス名

(app/uploaders/board_image_uploader.rb)

class BoardImageUploader < CarrierWave::Uploader::Base
省略
end

 

 ❻ Controllerの設定をする

今回は:board_imageと :board_image_cache 追加

(app/controllers/boards_controller.rb)

def board_params
params.require(:board).permit(:title, :body, :board_image, :board_image_cache)
end

 :board_image_cache <%= f.hidden_field :board_image_cache %>を指定している。

❼ 画像ファイルのフィールドを追加する

(app/views/boards/_form.html.erb)

<div class="form-group">
<%= f.label :board_image %>
<%= f.file_field :board_image, class:'form-control mb-3', accept: 'image/*' %>
<%= f.hidden_field :board_image_cache %>
</div>
<div class='mt-3 mb-3'>
<%= image_tag board.board_image_url, id: 'pewview', size: '300*200' %>
</div>

① accept: 'image/*'

参照

accept 属性(受け取るファイル種別を指定する) | HTML5 タグリファレンス | W3 Watch Reference

HTMLでは <input type="file" accept="入力欄が受け付けるファイル型">となる。

このaccept: 'image/*' は形式は問わず、画像だけを受け入れることを可能にする。

 

② <%= f.hidden_field :board_image_cache %>

アップロードに失敗した時にファイルが消えないようにするために必要。

参照

GitHub - carrierwaveuploader/carrierwave: Classier solution for file uploads for Rails, Sinatra and other Ruby web frameworks

often you'll notice ~のところ

 

(app/views/boards/_board.html.erb)

 
<div class="card">
<%= image_tag board.board_image_url, class: 'card-img-top', size: '300x200' %>
<div class="card-body">

board_image_url 上に解説あり

 

 

❽ メッセージを追加する

(config/locales/ja.yml)はgem rails-i18nの内部のファイル(デフォルトのってこと?)を読み込んで利用する為、別の階層で定義ファイルを用意する

 

(config/locales/activerecord/ja.yml)

ja:
 
board:
title: 'タイトル'
body: '本文'
board_image: 'サムネイル' #追加

 

(config/locales/carrierwave/ja.yml)

ja:
errors:
messages:
carrierwave_processing_error: '処理できませんでした'
carrierwave_integrity_error: 'は許可されていないファイルタイプです'
carrierwave_download_error: 'はダウンロードできません'
carrierwave_whitelist_error: "は %{allowted_types}の形式でアップロードしてください"
carrierwave_blacklist_error: "%{extension}ファイルのアプロードは許可されていません。アップロードできないタイプ: %{prohibited_types}"
content_type_whitelist_error: "%{content_type}ファイルのアップロードは許可されていません。アップロードできるファイルタイプ: %{allowed_types}"
content_type_blacklist_error: "%{content_type}ファイルのアップロードは許可されていません"
rmagick_processing_error: "rmagickがファイルを処理できませんでした。画像を確認してください。エラーメッセージ: %{e}"
mini_magick_processing_error: "MiniMagickがファイルを処理できませんでした。画像を確認してください。エラーメッセージ: %{e}"
min_size_error: "を%{min_size}以上のサイズにしてください"
max_size_error: "を%{max_size}以下のサイズにしてください"

 

「画像プレビュー機能について」

「まだDBに保存していない掲示板データに対して、選択された画像ファイルを表示する」という処理を行う。

DBから値を取得して表示するのではなく、選択された画像ファイルをJavaScriptを使用してクライアントに表示するという流れになる。

 

onchangeイベントハンドラ(=フォーム要素の選択、入力内容が変更された時に処理を行う)を使用し実装していく。

 

❶image_tagにidを与える

今回イベントに該当するのは画像ファイルの読み込み。

onchangeイベントハンドラによってid = previewがJSファイルに渡される。

<div class="form-group">
<%= f.label :board_image %>
<%= f.file_field :board_image, onchange: 'previewImage()', class: 'form-control mb-3', accept: 'image/*' %>
<%= f.hidden_field :board_image_cache %>
</div>
<div class='mt-3 mb-3'>
<%= image_tag board.board_image.url,
id: 'preview',
size: '300x200' %>
</div>

 

 

JavaScriptプレビュー機能を作成

画像フィールドの値が変化した際に、image_tagで画像ファイルのURLを読み込み、画像を表示する。

(app/assets/javascript/common.js)

function previewImage() {
const target = this.event.target; #イベントを発生させ
たオブジェクトを参照
const file = target.files[0]; #選択した画像ファイルを代入
const reader = new FileReader(); #fileオブジェクトを読み込み
reader.onloadend = function () { #読み込みが終了した時に
発火するイベント
const preview = document.querySelector("#preview") #1
if(preview) {
preview.src = reader.result;
 
}
}
if (file) {
reader.readAsDataURL(file); #ファイルのデータを示すURLを格納
}
}

 

#1 querySelector() 引数にjQueryで使用するようなCSSセレクタを指定することで、任意のhtml要素を取得することができる。

 

エラーを確認するときは、

binding.pry

などを使用して、確認してみる。

chromeの検証などでみると、エラー見れる。