WeBlog

Webに関する情報を中心に発信してるブログ

carrier_waveとmini_magickを使った画像アップロード

ImageMagicとは?

ImageMagickは、画像のサイズ変更、反転、ミラーリング、回転、変形、せん断、変換、画像の色の調整などができる無料のソフトウェアです。

ImageMagickのインストール

MiniMagickを使うためにImageMagickが必要なのでインストールします。

ターミナルで下記を実行します。

% brew install imagemagick

ImageMagicのバージョンを確認します。

バージョンが表示されたら問題ないです。

% convert --version

Version: ImageMagick 7.0.11-4 Q16 x86_64 2021-03-20 https://imagemagick.org
Copyright: (C) 1999-2021 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenMP(4.5) 
Delegates (built-in): bzlib freetype gslib heic jng jp2 jpeg lcms lqr ltdl lzma openexr png ps tiff webp xml zlib

https://imagemagick.org/

carrierwaveとは?

Rubyアプリケーションからファイルをアップロードするためのgemです。

簡単に画像をアップロードする便利機能を提供してくれているパッケージみたいな感じです。

https://github.com/carrierwaveuploader/carrierwave

mini_magickとは?

MiniMagickとは、画像加工をしてくれるgemです。

MiniMagickを使うには、ImageMagickが必要なのでインストールしました。

https://github.com/minimagick/minimagick

carrierwaveとmini_magickをインストール

Gemfileに'carrierwave''mini_magick'を追加します。

bundle installしてgemをインストールします。

gem 'carrierwave'
gem 'mini_magick'

画像アップローダーを作成するためのコマンドを実行します。

% rails g uploader image
Running via Spring preloader in process 66081
Could not find generator 'uploader'. Maybe you meant 'helper', 'jbuilder' or 'model'
Run `rails generate --help` for more options.

何かエラーになっています。

エラー内容としては、

プロセス66081のSpringプリローダーを介して実行 ジェネレータ「アップローダー」が見つかりませんでした。多分あなたは「メーラー」、「タスク」または「ヘルパー」を意味しました その他のオプションについては、'rails generate --help'を実行してください。

対処法としては、Running via Spring preloader in process 66081の1行でググると対策が出てくるみたい。

下記コマンドを実行してspringを止めるらしい。

% spring stop
rbenv: spring: command not found

https://qiita.com/kohei_wd/items/d4809076df5a3cef5d1a

sprignがないというコマンドが出ました。

対処方として、sudoを使ってspringをインストールすると良いみたい。

sudoはsuper doの略で管理者権限でコマンドを実行するものです。

下記コマンドを実行します。

パスワードが聞かれるので、パソコンのパスワードを入力します。

% sudo gem install spring
Password:
Fetching spring-2.1.1.gem
Successfully installed spring-2.1.1
Parsing documentation for spring-2.1.1
Installing ri documentation for spring-2.1.1
Done installing documentation for spring after 0 seconds
1 gem installed

https://hachimaki37.hatenablog.com/entry/2020/06/17/191914

https://github.com/rails/spring

もう一度spring stopを実行します。

何とか解決です。

% spring stop
Spring stopped.

再度 rails g uploader imageを実行します。

image_uploader.rbというファイルができました。

% rails g uploader image
Running via Spring preloader in process 66607
      create  app/uploaders/image_uploader.rb

マイグレーションファイルを作成

rails gernerateコマンドでテーブルにカラムを追加するためにマイグレーションファイルを作成する。

AddBoardImageToBoardsは、boardsテーブルにboard_imageというカラムを追加するという意味でつけています。

データの方はstring型です。

board_imageというカラムには画像データではなく、画像ファイルの名前を追加します。

% rails g migration AddBoardImageToBoards board_image:string
Running via Spring preloader in process 67337
      invoke  active_record
      create    db/migrate/20210325044126_add_board_image_to_boards.rb

マイグレーションファイルを実行するには、rails db:migrateコマンドを実行します。

ストロングパラメーターにカラム名を追加

ユーザーが画像を送信してきた際に、適切に受け取ることができるよう、ストロングパラメーターにカラム名を追加します。

今回は、:board_imageとします。

:board_image_cacheはモデルのカラムにはないですが、バリデーションに引っかかった際に画像をキャッシュ(残しておく)するために記載します。

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

カラムとアップローダーの関連付け

画像をアップロードするカラムと、rails g uploader imageコマンドで作成したアップローダークラスを紐付けます。

今回は掲示板の画像をアップロードするので、Boardモデルに記載します。

class Board < ApplicationRecord
  # mount_uploader カラム名, アップローダークラス名
  mount_uploader :board_image, ImageUploader
end

MiniMagickで画像を加工する設定

今回はminimagicで画像を加工して、アップロードしたいので、minimagicを使えるように設定します。

minimagicを使えるようにするには、アップローダークラスでincludeします。

minimagicを使って画像を加工する設定は色々ありますが、画像のサイズを加工するが1番メジャーではないかと思います。

https://qiita.com/wann/items/c6d4c3f17b97bb33936f

class ImageUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  # MiniMagickを使えるように読み込む
    include CarrierWave::MiniMagick

    # アップロードファイルの保存場所を指定(public/ 配下に保存する)
  # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog # <= 外部ストレージ(AWSなど)に保存する場合はこっち!!

    # 画像データを保存するパスを設定
  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  # 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

  # Process files as they are uploaded:
  # process scale: [200, 300]
  #
  # def scale(width, height)
  #   # do something
  # end

    # 画像の縦横比を維持したまま、 width を最大 300px、height を最大 200 pxにリサイズする設定
  # Create different versions of your uploaded files:
  version :thumb do
    process resize_to_fit: [300, 200]
  end

  # アップロードできる画像の拡張子を制限する設定
  # Add an allowlist of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  def extension_allowlist
    %w(jpg jpeg gif png)
  end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   "something.jpg" if original_filename
  # end
end

画像投稿のViewファイルを作成する

Viewファイルは下記のような感じです。

f.file_fieldinput type="file"を作ってくれます。

<%= f.hidden_field :image_cache %>を追記しておくことで、

バリデーションエラーでrenderされた場合でも、画像を残しておくことができます。

# viewファイルの中身
<div class="form-group">
  <%= f.label :board_image %>
  <%= f.file_field :board_image %>
    <%= f.hidden_field :image_cache %>
</div>

# こんなHTMLが出力される
<input type="file" name="board[board_image]" id="board_board_image">

https://github.com/carrierwaveuploader/carrierwave#making-uploads-work-across-form-redisplays

画像データの中身を見てみる

paramsの中にfomから送信された全ての情報が格納されていますが、"board_image"の中に画像のデータが格納されています。

@original_filename="images.jpeg"が送信された画像の名前です。

> params
=> <ActionController::Parameters {"utf8"=>"", "authenticity_token"=>"hVPXVju4pKWgS9KVLu5l3rvBbC2JdFxHSMgw9z8KzUL9dRQqU3NSKe+PP+UGTeFS1WABv53cNMohXgPrOZyHzw==", 
"board"=><ActionController::Parameters {"title"=>"タイトル03", "body"=>"本文03", 
"board_image"=>#<ActionDispatch::Http::UploadedFile:0x00007f92973136d0 @tempfile=#<Tempfile:/var/folders/zh/p27r1mk14l53g68j2p914yvw0000gn/T/RackMultipart20210325-69376-vq81o3.jpeg>, @original_filename="images.jpeg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"board[board_image]\"; filename=\"images.jpeg\"\r\nContent-Type: image/jpeg\r\n">} permitted: false>, 
"commit"=>"登録する", "controller"=>"boards", "action"=>"create"} permitted: false>

画像の保存先

uploades/image_uploader.rbの中身を見てみると下記のような設定があります。

storage :filepublic/に画像が保存されることを表しています。

store_dirメソッドは画像が保存されるパスを示しています。

"uploads/モデル名/カラム名/レコードのid"に保存されます。

   # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

DBに保存された画像のパスを取得する

投稿された画像を画面に表示するには、画像のパスがパスが必要です。

モデルにmount_uploader :board_image, ImageUploaderを追加したことで、ImageUploaderクラスのメソッドを使うことができます。

ImageUploaderクラスのメソッドを使うことで、画像のパスを取得することができます。

> board = Board.last
  Board Load (0.2ms)  SELECT  "boards".* FROM "boards" ORDER BY "boards"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<Board id: 24, title: "test03", body: "test03", user_id: 1, created_at: "2021-03-25 08:20:00", updated_at: "2021-03-25 08:20:00", board_image: "images.jpeg">

# オブジェクト名.カラム名.url でuploads配下の画像までのパスを取得できる
> board.board_image.url
=> "/uploads/board/board_image/24/images.jpeg"

# オブジェクト名.カラム名_identifier で画像ファイルの名前を取得できる
> board.board_image_identifier
=> "images.jpeg"

画像を表示する

画像を表示するにはimgタグを生成して、src属性の画像のパスを指定する必要があります。

このimgタグを生成するのがimage_tagメソッドです。

引数に画像のパスを取得する方法で紹介したboard.board_image.urlを書いています。

これでsrc属性にpublic/uploads配下のパスが自動で生成されます。

<%= image_tag board.board_image.url %>

# HTMLは下記のように表示される
<img src="/uploads/board/board_image/24/images.jpeg" >

画像ない場合の表示方法

画像がない場合は画像がないという画像を表示したいです。

その場合に、if文で条件分岐する方法もあります。

下記のように分岐すれば、画像があるかどうかで表示する画像を変えることができます。

<% if board.board_image.present? %>
  <%= image_tag board.board_image.url %>
<% else %>
  <%= image_tag 'no_img.png'%>
<% end %>

しかし、実際が分岐する必要はありません。

アップローダークラスの中にdefault_urlメソッドが用意されています。

このメソッド戻り値にデフォルトで表示したい画像名を指定すると、画像がない場合にdefault_urlで設定した画像のパスがimgタグに設定されます。

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('_')
    'no_img.png' # <= app/assets/image/no_img.png のパスになる
end

# 下記のようなパスになる
<img class="card-img-top" src="/assets/no_img.png >

参考サイト

https://pikawaka.com/rails/carrierwave