日別アーカイブ: 2021年12月7日

Rails-tutorial(13.5画像の検証)

その13.4から続く

13.4.2 画像の検証

アップローダーも悪くはありませんが、いくつかの目立つ欠点があります。例えば、アップロードされた画像に対する制限がないため、
もしユーザーが巨大なファイルを上げたり、無効なファイルを上げると問題が発生してしまいます。

この欠点を直すために、画像サイズやフォーマットに対するバリデーションを実装し、サーバー用とクライアント (ブラウザ)用の両方に追加しましょう。

画像のファイル名から有効な拡張子 (PNG/GIF/JPEGなど) を検証する
画像フォーマットのバリデーション
app/uploaders/picture_uploader.rb

class PictureUploader < CarrierWave::Uploader::Base
  storage :file

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

2つ目のバリデーションでは、画像のサイズを制御します。
これはMicropostモデルに書き足していきます。

今回は手動でpicture_sizeという独自のバリデーションを定義します。

今まで使っていたvalidatesメソッドではなく、
validateメソッドを使っている点に注目してください。

 画像に対するバリデーションを追加する
app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  mount_uploader :picture, PictureUploader
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
  validate  :picture_size

  private

    # アップロードされた画像のサイズをバリデーションする
    def picture_size
      if picture.size > 5.megabytes
        errors.add(:picture, "should be less than 5MB")
      end
    end

validateメソッドでは、引数にシンボル (:picture_size) を取り、
そのシンボル名に対応したメソッドを呼び出します。
また、呼び出されたpicture_sizeメソッドでは、5MBを上限とし、
それを超えた場合はカスタマイズした
エラーメッセージをerrorsコレクションに追加しています。

定義した画像のバリデーションをビューに組み込むために、
クライアント側に2つの処理を追加しましょう。

まずはフォーマットのバリデーションを反映するためには、
file_fieldタグにacceptパラメータを付与して使います。

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

このときacceptパラメータでは、リスト 13.64で許可したファイル形式を、MIMEタイプで指定するようにします。

次に、大きすぎるファイルサイズに対して警告を出すために、
ちょっとしたJavaScript (正確にはjQuery) を書き加えます。
こうすることで、長すぎるアップロード時間を防いだり、
サーバーへの負荷を抑えたりすることに繋がります。

$('#micropost_picture').bind('change', function() {
var size_in_megabytes = this.files[0].size/1024/1024;
if (size_in_megabytes > 5) {
alert('Maximum file size is 5MB. Please choose a smaller file.');
}
});

上のコードでは(ハッシュマーク#から分かるように)
CSS idのmicropost_pictureを含んだ要素を見つけ出し、
この要素を監視しています。

そしてこのidを持った要素とは、マイクロポストのフォームを指します(なお、ブラウザ上で画面を右クリックし、インスペクターで要素を調べることで確認できます)。

つまり、このCSS idを持つ要素が変化したとき、このjQueryの関数が動き出します。そして、もしファイルサイズが大きすぎた場合、
alertメソッドで警告を出すといった仕組みです。

これらの追加的なチェック機能をまとめると、↓のようになります。

ファイルサイズをjQueryでチェックする
app/views/shared/_micropost_form.html.erb

<%= form_for(@micropost) do |f| %>
 <%= render 'shared/error_messages', object: f.object %>
 <div class="field">
  <%= f.text_area :content, placeholder: "Compose new micropost..." %>
 </div>
<%= f.submit "Post", class: "btn btn-primary" %>
 <span class="picture">
  <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
 </span>
<% end %>

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

実装はまだ不完全です。

仮に送信フォームを使った投稿をうまく制限できても、ブラウザのインスペクタ機能でJavaScriptをいじったり、curlなどを使って直接POSTリクエストを送信する場合には対応しきれません

演習

1:5MB以上の画像ファイルを送信しようとした場合、どうなりますか?
また無効な拡張子のファイルを送信しようとした場合、どうなりますか?

エラーメッセージが以下のようになる。
画像は5MB未満である必要があります
画像「xmind」ファイルのアップロードは許可されていません。
許可されているタイプ:jpg、jpeg、gif、png

13.4.3 画像のリサイズ

画像を表示させる前にサイズを変更する

画像をリサイズするためには、画像を操作するプログラムが必要になります。今回はImageMagickという
プログラムを使うので、これを開発環境にインストールします。

本番環境がHerokuであれば、既に本番環境でImageMagickが使えるようになっています。クラウドIDEでは、
次のコマンドでこのプログラムをインストールできます。

sudo yum install -y ImageMagick

もしローカル環境で開発している場合、それぞれの環境に応じてImagiMagickをインストールする手順が異なります。

例えばMacの場合であれば、Homebrewを導入し、
brew install imagemagick
コマンドを使ってインストールします。

次に、MiniMagickというImageMagickとRubyを繋ぐgemを使って、
画像をリサイズしてみましょう。

MiniMagickのドキュメントを見ると
様々な方法でリサイズできることがわかりますが、
今回はresize_to_limit: [400, 400]という方法を使います。これは、
縦横どちらかが400pxを超えていた場合、適切なサイズに縮小するオプションです(ただし小さい画像であっても拡大はしません)。

画像をリサイズするために画像アップローダーを修正する
app/uploaders/picture_uploader.rb

class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  process resize_to_limit: [400, 400]

  storage :file

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

演習

1:解像度の高い画像をアップロードし、リサイズされているかどうか確認してみましょう。画像が長方形だった場合、リサイズはうまく行われているでしょうか?

うまく出来る。バリデーション実装前のはもちろんリサイズされない

2:テストを追加していた場合、この時点でテストスイートを走らせると
紛らわしいエラーメッセージが表示されることがあります。
このエラーを取り除いてみましょう。
設定ファイルを修正し、テスト時はCarrierWaveに画像のリサイズを
させないようにしてみましょう。

テスト時は画像のリサイズをさせない設定config/initializers/skip_image_resizing.rb
if Rails.env.test?
  CarrierWave.configure do |config|
    config.enable_processing = false
  end
end

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

storage :fileという行によって、
ローカルのファイルシステムに画像を保存するようになっているからです。
本番環境では、ファイルシステムではなくクラウドストレージサービスに画像を保存するようにしてみましょう。

本番環境でクラウドストレージに保存するためにはfog gemを使うと簡単です。

本番環境での画像アップロードを調整する
app/uploaders/picture_uploader.rb

class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  process resize_to_limit: [400, 400]

  if Rails.env.production?
    storage :fog
  else
    storage :file
  end

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

世の中には多くのクラウドストレージサービスがありますが、今回は有名で信頼性も高いアマゾンの「Simple Storage Service (S3) 」を使います。

Amazon Web Servicesアカウントにサインアップする

AWS Identity and Access Management (IAM)でユーザーを
作成し、AccessキーとSecretキーをメモする

AWS ConsoleからS3 bucketを作成し(bucketの名前はなんでも大丈夫です)作成したユーザーに対してRead権限とWrite権限を付与する

fogでリージョンを指定する場合は
:region => ENV[‘S3_REGION’] といったパラメータを渡し、
heroku config:set S3_REGION=”リージョン名”
といったコマンドを実行することで設定できます。
なお、東京のリージョン名は “ap-northeast-1” です。

CarrierWaveを通してS3を使うように修正するconfig/initializers/carrier_wave.rb
if Rails.env.production?
  CarrierWave.configure do |config|
    config.fog_credentials = {
      # Amazon S3用の設定
      :provider              => 'AWS',
      :region                => ENV['S3_REGION'],     # 例: 'ap-northeast-1'
      :aws_access_key_id     => ENV['S3_ACCESS_KEY'],
      :aws_secret_access_key => ENV['S3_SECRET_KEY']
    }
    config.fog_directory     =  ENV['S3_BUCKET']
  end
end

今回は手動で設定する必要があります。heroku config:setコマンドを使って、次のようにHeroku上の環境変数を設定してください。

heroku config:set S3_ACCESS_KEY="ココに先ほどメモしたAccessキーを入力" 
heroku config:set S3_SECRET_KEY="同様に、Secretキーを入力" 
heroku config:set S3_BUCKET="Bucketの名前を入力" 
heroku config:set S3_REGION="Regionの名前を入力"

.gitignoreファイルにアップロード用ディレクトリを追加する

# アップロードされたテスト画像を無視する
/public/uploads

それでは、これまでの変更をトピックブランチにコミットし、masterブランチにマージしていきましょう。

rails test
git add -A
git commit -m "Add user microposts"
git checkout master
git merge user-microposts
git push

次に、Herokuへのデプロイ、データベースのリセット、サンプルデータの生成を順に実行していきます。

git push heroku
heroku pg:reset DATABASE
heroku run rails db:migrate
heroku run rails db:seed

最終状態のGemfile

source 'https://rubygems.org'

gem 'rails',                   '5.1.6'
gem 'bcrypt',                  '3.1.12'
gem 'faker',                   '1.7.3'
gem 'carrierwave',             '1.2.2'
gem 'mini_magick',             '4.7.0'
gem 'will_paginate',           '3.1.6'
gem 'bootstrap-will_paginate', '1.0.0'
gem 'bootstrap-sass',          '3.3.7'
gem 'puma',                    '3.9.1'
gem 'sass-rails',              '5.0.6'
gem 'uglifier',                '3.2.0'
gem 'coffee-rails',            '4.2.2'
gem 'jquery-rails',            '4.3.1'
gem 'turbolinks',              '5.0.1'
gem 'jbuilder',                '2.7.0'

group :development, :test do
  gem 'sqlite3', '1.3.13'
  gem 'byebug',  '9.0.6', platform: :mri
end

group :development do
  gem 'web-console',           '3.5.1'
  gem 'listen',                '3.1.5'
  gem 'spring',                '2.0.2'
  gem 'spring-watcher-listen', '2.0.1'
end

group :test do
  gem 'rails-controller-testing', '1.0.2'
  gem 'minitest',                 '5.10.3'
  gem 'minitest-reporters',       '1.1.14'
  gem 'guard',                    '2.14.1'
  gem 'guard-minitest',           '2.4.6'
end

group :production do
  gem 'pg',   '0.20.0'
  gem 'fog',  '1.42'
end

# Windows環境ではtzinfo-dataというgemを含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

13.5.1 本章のまとめ

1:Active Recordモデルの力によって、マイクロポストも
(ユーザーと同じで) リソースとして扱える

2:Railsは複数のキーインデックスをサポートしている

3:Userは複数のMicropostsを持っていて (has_many)、Micropostは
1人のUserに依存している (belongs_to) といった関係性をモデル化した

4:has_manyやbelongs_toを利用することで、
関連付けを通して多くのメソッドが使えるようになった

5:user.microposts.build(…)というコードは、
引数で与えたユーザーに関連付けされたマイクロポストを返す

6:default_scopeを使うとデフォルトの順序を変更できる

7:default_scopeは引数に無名関数 (->) を取る

8:dependent: :destroyオプションを使うと、
関連付けされたオブジェクトと自分自身を同時に削除する

9:paginateメソッドやcountメソッドは、
どちらも関連付けを通して実行され、効率的にデータベースに問い合わせしている

10:fixtureは、関連付けを使ったオブジェクトの作成もサポートしている

11:パーシャルを呼び出すときに、一緒に変数を渡すことができる

12:whereメソッドを使うと、Active Recordを通して選択 (部分集合を取り出すこと) ができる

13:依存しているオブジェクトを作成/削除するときは、
常に関連付けを通すようにすることで、よりセキュアな操作が実現できる

14:CarrierWaveを使うと画像アップロードや画像リサイズができる

14章に続く