プログラミング備忘録

プログラミングの学習状況をメモしています

62日目

今日の学習

Ruby on Rails

Rails チュートリアル第13章

第13章の〆として、画像を投稿できるようにしていく。

railstutorial.jp

マイクロポストの画像投稿

画像付きマイクロポストを投稿できるようにするに当たって、開発環境用のβ版を実装して、改善を施してから本番環境用の完成版を実装するという流れになる。

画像アップロード機能のために必要な視覚的要素は以下の二点。

  • 画像をアップロードするためのフォーム
  • 投稿された画像
Active Storage

Railsでファイルをアップロードするために、Railsに組み込まれているActive Storageという機能を利用する。

これは画像のみならず、PDFや音声ファイルも扱えるようだ。

まず、以下のコマンドでActive Storageをアプリケーションに追加する。

rails active_storage:install

上記のコマンドを実行すると、添付ファイルの保存に用いるデータモデルを作成するためのデータベースマイグレーションが生成されるため、マイグレーションを実行すること。

インストール後使えるメソッドで、最初に使う必要があるものはhas_one_attachedメソッド。

has_one_attatchedメソッドは、指定のモデルとアップロードされたファイルの関連付に使用する。

今回はMicropostモデルと画像のimageファイルを紐づけるので、以下のようにする。

app/models/micropost.rb

has_one_attached :image

has_one_attachedでは1つのファイルを添付できるようになっているが、has_many_attachedを使うと複数のファイルが添付できるようになる。Railsチュートリアルで作成するものは、マイクロポスト1つにつき画像は1つという設計をしているので、今回は前者のオプションを利用する。

次に、マイクロポストのフォーム部分にファイルを添付できるようにfile_fieldタグを追加する。

ビューの編集ができたら、最後にMicropostsコントローラを更新し、micropostオブジェクトに画像が追加できるようにする。

Active Strage APIは、attachメソッドを用意しており、これを使って行う。また、micropost_paramspermit:imageを追加し、画像を許可する必要もある。

app/controllers/microposts_controller.rb

def create
  @micropost = current_user.microposts.build(micropost_params)
  @micropost.image.attach(params[:micropost][:image])
...
...
  def micropost_params
    params.require(:micropost).permit(:content, :image)
  end

画像の投稿ができるようになったので、マイクロポストで投稿された画像が見られるようにする。

image_tagヘルパーを用いて、micropost.imageを描画する。

画像の投稿がない場合は、画像を表示させないようにするためにattached?という論理値を返すメソッドを使う。

app/views/microposts/_micropost.html.erb

<%= image_tag micropost.image if micropost.image.attached? %>

この項目の、画像アップロードをテストするためのテンプレートを作成する演習で少しつまづいた。

assert_select 'input[type= ???]'で、???のところに何を埋めればファイル添付機能が実装されていることがテストできるだろうか。

<input>-HTML5タグリファレンス

こちらのサイトでinputのtype属性を一通り確認し、その中でfileが最も適切だったためfileで埋めてみたところ、テストが通った。

画像が表示されているかをチェックするためには、コントローラのインスタンス変数にアクセスできるassignsメソッドを利用する。

今回の場合は、@micropostがあるかどうかを確かめたいので、以下のようなコードになる。

assert assigns(:micropost).image.attached?
画像ファイルのバリデーション

今までの工程で実装したアップローダーを使って実際に画像を投稿してみると、リサイズされず素の状態で投稿されてしまうため非常によろしくない。もしもユーザーが巨大なファイルをあげたり、無効なファイルをあげたりすると問題が発生する。

これを解決するために、画像サイズやフォーマットに対するバリデーションを実装する。

Active Strageでは、フォーマットやバリデーション機能がサポートされていないので、新しくactive_storage_validationsといったgemを追加する。

このgemを使うと、content_typeを利用して画像のバリデーションを設定できる。また、sizeを利用してファイルサイズもバリデーションできる。

content_type: { in: %w[image/jpeg image/gif image/png],
                message: "must be a valid image format" }

size: { less_than: 5.megabytes,
        message: "should be less than 5MB" }

このようなバリデーションを、フロント側からもチェックできるようにする。

Railsチュートリアルでは、jQueryでファイルサイズをチェックしている。もしもユーザーがアップロードしようとする画像サイズが巨大すぎる場合、アラートを表示するようにする。

<script type="text/javascript">
  $("#micropost_image").bind("change", function() {
    var size_in_megabytes = this.file[0].size/1024/1024;
    if (size_in_megabytes > 5) {
      alert("Maximum file size is 5MB. Please choose a smaller file.");
      $("#micropost_image").val("");
    }
  });
</script>

CSS idのmicropost_imageを含んだ要素を見つけ出して、この要素を監視する。このCSS idを持つ要素が変化したとき、このjQueryの関数が動き出す。

f:id:hasegawa_note:20210708103841p:plain

実際に5MB以上の画像をアップロードしようとしてみたところ、メッセージが表示される。

最後に、file_filedタグにacceptパラメータを用いて、有効なフォーマットでないとアップロードできないと視覚的に分かりやすくする。

<%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %>

こうしておくことで、ユーザーがファイルをアップロードするときに有効なファイルを灰色で表示してくれるようになる。

f:id:hasegawa_note:20210708104107p:plain

画像以外のファイル名が灰色になっていて分かりやすく、選択もできなくなる。

しかし、フロントだけのバリデーションではあくまでアップロードしにくくするだけであり、やろうと思えばPOSTリクエストを直接発行して無効なファイルをアップロードできてしまう状態。

そのため、サーバー側のバリデーションは必ず省略せず、どちらもやる方が望ましい。

画像のリサイズ

画像サイズをリサイズするための、画像を操作するプログラムをインストールする。

以前も使用したことがあるが、ImageMagickを利用する。他に、image_processinggemやmini_magickgemをインストールする。

インストール後、Active Storageが提供するvariantメソッドで変換した画像を作成できるようにする。

resize_to_limitオプションを利用して、画像の幅や高さを決めることができる。

Railsチュートリアルでは、リサイズ済み画像を返してくれるメソッドdisplay_imageを作成している。

app/models/micropost.rb

def display_image
# 縦横500ピクセルの制約
  image.variant(resize_to_limit: [500, 500])
end

作成したメソッドを使って、リサイズ済みの画像を表示するようにする。

app/views/microposts/_micropost.html.erb

<%= image_tag micropost.display_image if micropost.image.attached? %>
リサイズされた画像が表示されない

実際に画像をアップロードして、リサイズされているかどうか確認をしてみると、画像がうまくアップロードされていない。

f:id:hasegawa_note:20210708111035p:plain

検証から画像のURLをコピーし、URLに飛んでみるとエラー画面に。

MiniMagick::Error in ActiveStorage::RepresentationsController#show
You must have ImageMagick or GraphicsMagick installed

どうやら、ImageMagickのインストールに失敗しており、Minimagickが使えないということらしい。

Railsチュートリアルの指定通り、apt-getでインストールを行ったが、そのときうまく行っていないにも関わらずログを流し読みしてスルーしてしまっていた。

エラー文に、sudo apt --fix-broken installをしてねと書かれていたので実行。

その後、Railsチュートリアルに記載されているsudo apt-get -y install imagemagickをもう一度実行するとインストールできた。

これで無事にリサイズされた画像が表示されるようになった。

インストールするときはちゃんと結果を確認するようにしなければならない。

本番環境での画像アップロード

実装した画像アップロード機能では、ローカルのファイルシステムに画像を保存するようになっているため、本番環境に適さない。

本番環境では、ファイルシステムではなくクラウドストレージサービスに画像を保存するようにする。

Railsチュートリアルで利用するクラウドストレージは、AWSのサービスのひとつ「S3(Simple Storage Service)」。

これは有料サービスのようだが、チュートリアル中にテストする程度であれば月に1セント(つまり1円くらい)もかからないと書いてある。

本番環境でクラウドストレージを使うために、aqs-sdk-s3gemをインストールする。

AWSでユーザーを作成し、S3バケットを作成する。バケットとは、S3に保存される画像をはじめとしたさまざまなファイルを保管するための場所のことを指す。

このバケットを利用していくに当たって、機密にするべき情報を設定に直接書くこと(ハードコード)を避けるために、HerokuのENV変数を使う。

heroku config:setコマンドを使って、設定しなければならない情報を入力していく。

heroku config:set AWS_ACCESS_KEY=******
# ***にはユーザー作成時に表示された「アクセスキー ID」を入力

ここで設定したものを今の状態で使うのは危険なので、必ず環境変数ENV変数を利用する。

本番運用するアプリケーションは、暗号化されていないIDやパスワードのような重要なセキュリティ情報は絶対にソースコードに直接書き込まない

gemやconfigで使う設定値のyml...今回の場合、ストレージオプションにアクセスキーIDを書き込む際に、以下のような形で書く。

config/storage.yml

amazon:
  service: S3
# ENVを使って先程の定数を利用
  access_key_id: <%= ENV['AWS_ACCESS_KEY'] %>

定義したオプションを本番環境で使うために、`Active Storage`サービス設定パラメータを`config/environments/production.rb`ファイルに追加。

storage.ymlで設定したものを読み込み

config.active_storage.service = :amazon

Herokuにデプロイ後、画像を投稿してみたがうまくいった。

次でいよいよRailsチュートリアルも最終章だ。