はじめに
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版の 13章 13.4 マイクロポストの画像投稿の演習まとめ&解答例です。
個人の解答例なので、誤りがあればご指摘ください。
動作環境
- cloud9
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
- Rails 5.0.0.1
13.4 マイクロポストの画像投稿
本章での学び
画像付きマイクロポストを投稿できるようにする。
13.4.1 基本的な画像アップロード
本章での学び
【事前準備】CarrierWaveのインストール
画像アップローダーであるCarrierWaveをGemfileに追加する。 また、画像処理を行うmini_magickと、クラウド環境へアップロードするためのfogも追加する。
gem 'carrierwave', '1.1.0' gem 'mini_magick', '4.7.0' gem 'fog', '1.40.0'
インストール。
yokoyan:~/workspace/sample_app (user-microposts) $ bundle install The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from https://rubygems.org/.......... Fetching version metadata from https://rubygems.org/... Fetching dependency metadata from https://rubygems.org/.. Resolving dependencies... Using rake 12.0.0 Installing CFPropertyList 2.3.5 Using concurrent-ruby 1.0.5 Using i18n 0.8.1 Using minitest 5.10.1 Using thread_safe 0.3.6 Using builder 3.2.3 Using erubis 2.7.0 Using mini_portile2 2.1.0 Using rack 2.0.1 Using nio4r 1.2.1 Using websocket-extensions 0.1.2 Using mime-types-data 3.2016.0521 Using arel 7.1.4 Using ansi 1.5.0 Using execjs 2.7.0 Using bcrypt 3.1.11 Using sass 3.4.23 Using will_paginate 3.1.0 Using bundler 1.14.6 Using byebug 9.0.0 Using coderay 1.1.1 Using coffee-script-source 1.12.2 Using method_source 0.8.2 Using thor 0.19.4 Using debug_inspector 0.0.2 Installing excon 0.58.0 Using ffi 1.9.18 Using formatador 0.2.5 Using multi_json 1.12.1 Installing ipaddress 0.8.3 Installing xml-simple 1.1.5 Installing inflecto 0.0.2 Installing json 1.8.6 with native extensions Installing trollop 2.1.2 Using rb-fsevent 0.9.8 Using lumberjack 1.0.11 Using nenv 0.3.0 Using shellany 0.0.1 Using slop 3.6.0 Using guard-compat 1.2.1 Installing mini_magick 4.7.0 Using ruby-progressbar 1.8.1 Using puma 3.4.0 Using tilt 2.0.6 Using spring 1.7.2 Using sqlite3 1.3.11 Using turbolinks-source 5.0.0 Installing fission 0.5.0 Using faker 1.6.6 Using tzinfo 1.2.2 Using nokogiri 1.7.0.1 Using rack-test 0.6.3 Using sprockets 3.7.1 Using websocket-driver 0.6.5 Using mime-types 3.1 Using autoprefixer-rails 6.7.7.1 Using uglifier 3.0.0 Using bootstrap-will_paginate 0.0.10 Using coffee-script 2.4.1 Using rb-inotify 0.9.8 Installing fog-core 1.45.0 Using notiffany 0.1.1 Using pry 0.10.4 Using guard-minitest 2.4.4 Using minitest-reporters 1.1.9 Using turbolinks 5.0.1 Using activesupport 5.0.0.1 Using loofah 2.0.3 Installing rbvmomi 1.11.3 Using mail 2.6.4 Using bootstrap-sass 3.3.6 Using listen 3.0.8 Installing fog-json 1.0.2 Installing fog-xml 0.1.3 Installing fog-local 0.3.1 Installing fog-vmfusion 0.1.0 Using rails-dom-testing 2.0.2 Using globalid 0.3.7 Using activemodel 5.0.0.1 Using jbuilder 2.4.1 Using rails-html-sanitizer 1.0.3 Installing fog-vsphere 1.11.3 Using guard 2.13.0 Using spring-watcher-listen 2.0.0 Installing fog-aliyun 0.2.0 Installing fog-brightbox 0.13.0 Installing fog-dnsimple 1.0.0 Installing fog-openstack 0.1.21 Installing fog-profitbricks 4.0.0 Installing fog-sakuracloud 1.7.5 Installing fog-serverlove 0.1.2 Installing fog-softlayer 1.1.4 Installing fog-storm_on_demand 0.1.1 Installing fog-atmos 0.1.0 Installing fog-aws 1.4.0 Installing fog-cloudatcost 0.1.2 Installing fog-digitalocean 0.3.0 Installing fog-dynect 0.0.3 Installing fog-ecloud 0.3.0 Installing fog-google 0.1.0 Installing fog-powerdns 0.1.1 Installing fog-rackspace 0.1.5 Installing fog-radosgw 0.0.5 Installing fog-riakcs 0.1.0 Installing fog-terremark 0.1.0 Installing fog-voxel 0.1.0 Installing fog-xenserver 0.3.0 Using activejob 5.0.0.1 Using activerecord 5.0.0.1 Installing carrierwave 1.1.0 Using actionview 5.0.0.1 Installing fog 1.40.0 Using actionpack 5.0.0.1 Using actioncable 5.0.0.1 Using actionmailer 5.0.0.1 Using railties 5.0.0.1 Using sprockets-rails 3.2.0 Using rails-controller-testing 0.1.1 Using coffee-rails 4.2.1 Using jquery-rails 4.1.1 Using web-console 3.1.1 Using rails 5.0.0.1 Using sass-rails 5.0.6 Bundle complete! 28 Gemfile dependencies, 124 gems now installed. Gems in the group production were not installed. Use `bundle show [gemname]` to see where a bundled gem is installed. Post-install message from fog: ------------------------------ Thank you for installing fog! IMPORTANT NOTICE: If there's a metagem available for your cloud provider, e.g. `fog-aws`, you should be using it instead of requiring the full fog collection to avoid unnecessary dependencies. 'fog' should be required explicitly only if: - The provider you use doesn't yet have a metagem available. - You require Ruby 1.9.3 support. ------------------------------
【事前準備】画像アップローダーの作成
Railsのジェネレーターで画像アップローダーを生成する。
yokoyan:~/workspace/sample_app (user-microposts) $ rails generate uploader Picture Running via Spring preloader in process 9141 Expected string default value for '--jbuilder'; got true (boolean) create app/uploaders/picture_uploader.rb
【事前準備】DBのマイグレーション
自動生成したPictureとマイクロポストを関連付けるために、 Micropostモデルにpicture属性を追加する。
yokoyan:~/workspace/sample_app (user-microposts) $ rails generate migration add_picture_to_microposts picture:string Running via Spring preloader in process 9155 Expected string default value for '--jbuilder'; got true (boolean) invoke active_record create db/migrate/20170820040443_add_picture_to_microposts.rb
自動生成されたマイグレーションファイル。
class AddPictureToMicroposts < ActiveRecord::Migration[5.0] def change add_column :microposts, :picture, :string end end
マイグレーションの実行。
yokoyan:~/workspace/sample_app (user-microposts) $ rails db:migrate == 20170820040443 AddPictureToMicroposts: migrating =========================== -- add_column(:microposts, :picture, :string) -> 0.0048s == 20170820040443 AddPictureToMicroposts: migrated (0.0050s) ==================
【model】Micropostモデルに画像を追加する
CarrierWaveに画像と関連付けたモデルを伝えるために、mount_uploader
メソッドを使用する。引数に、属性名(今回はpicture)と、クラス名(PictureUploader)を指定する。
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 } end
チュートリアルでは、この時点でのtestはgreenになると書かれているが、redになる。
(Micropostモデルから、mount_uploader
メソッドを削除するとgreenになる)
yokoyan:~/workspace/sample_app (user-microposts) $ rails test Running via Spring preloader in process 9762 Started with run options --seed 39087 ERROR["test_profile_display", UsersProfileTest, 0.09379067993722856] test_profile_display#UsersProfileTest (0.09s) NameError: NameError: uninitialized constant Micropost::PictureUploader app/models/micropost.rb:4:in `<class:Micropost>' app/models/micropost.rb:1:in `<top (required)>' FAIL["test_content_should_be_at_most_140_characters", MicropostTest, 2.966606823960319] test_content_should_be_at_most_140_characters#MicropostTest (2.97s) Expected true to be nil or false test/models/micropost_test.rb:32:in `block in <class:MicropostTest>' FAIL["test_user_id_should_be_present", MicropostTest, 2.974773451918736] test_user_id_should_be_present#MicropostTest (2.97s) Expected true to be nil or false test/models/micropost_test.rb:22:in `block in <class:MicropostTest>' FAIL["test_content_should_be_present", MicropostTest, 2.9872157878708094] test_content_should_be_present#MicropostTest (2.99s) Expected true to be nil or false test/models/micropost_test.rb:27:in `block in <class:MicropostTest>' FAIL["test_micropost_interface", MicropostsInterfaceTest, 3.539895322872326] test_micropost_interface#MicropostsInterfaceTest (3.54s) "Micropost.count" didn't change by 0. Expected: 38 Actual: 39 test/integration/microposts_interface_test.rb:16:in `block in <class:MicropostsInterfaceTest>' 59/59: [==================================================================================================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.68161s 59 tests, 245 assertions, 4 failures, 1 errors, 0 skips
一旦先に進めることにする。
【view】パーシャルに画像を追加する。
f.file_field
属性を使って、マイクロポスト投稿フォームに画像を表示する。
file_fieldは、ファイル選択ボックスを表示するとのこと。
<%= 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 %> </span> <% end %>
【controller】Micropostsコントローラのリファクタリング
picture属性をブラウザから更新できる許可リストに追加する。
private def micropost_params params.require(:micropost).permit(:content, :picture) end
【view】マイクロポスト表示パーシャルのリファクタリング
image_tag
ヘルパーを使って、マイクロポストの表示項目に画像を追加する。
画像が存在する場合のみ、画像を表示する。
image_tagヘルパーは以下参照。 Railsドキュメント image_tag
<span class="content"> <%= micropost.content %> <%= image_tag micropost.picture.url if micropost.picture? %> </span>
演習1
画像付きのマイクロポストを投稿してみましょう。もしかして、大きすぎる画像が表示されてしまいましたか? (心配しないでください、次の13.4.3でこの問題を直します)。
画像がアップロードできることを確認。
演習2
リスト 13.63に示すテンプレートを参考に、13.4で実装した画像アップローダーをテストしてください。テストの準備として、まずはサンプル画像をfixtureディレクトリに追加してください (コマンド例: cp app/assets/images/rails.png test/fixtures/)。リスト 13.63で追加したテストでは、Homeページにあるファイルアップロードと、投稿に成功した時に画像が表示されているかどうかをチェックしています。なお、テスト内にあるfixture_file_uploadというメソッドは、fixtureで定義されたファイルをアップロードする特別なメソッドです18。ヒント: picture属性が有効かどうかを確かめるときは、11.3.3で紹介したassignsメソッドを使ってください。このメソッドを使うと、投稿に成功した後にcreateアクション内のマイクロポストにアクセスするようになります。
サンプル画像をfixtureディレクトリに配置する。
yokoyan:~/workspace/sample_app (user-microposts) $ cp app/assets/images/rails.png test/fixtures/
テストを追加する。
- HTML属性に、input type=file属性があることを確認
- fixtureディレクトリにあるrails.pngを、image/pngにアップロードして、picture変数に代入
- マイクロポスト投稿時に、picture変数を引数に追加して投稿できることを確認
- マイクロポストにpictureが存在することを確認
※しれっと、params:
ハッシュがなくなっているので注意。
test "micropost interface" do log_in_as(@user) get root_path assert_select 'div.pagination' assert_select 'input[type=file]' # 無効な送信 assert_no_difference 'Micropost.count' do # post microposts_path params: { micropost: { content: "" } } post microposts_path micropost: { content: "" } end assert_select 'div#error_explanation' # 有効な送信 content = "This micropost really ties the room together" picture = fixture_file_upload('test/fixtures/rails.png','image/png') assert_difference 'Micropost.count', 1 do # post microposts_path, params: { micropost: { content: content } } post microposts_path, micropost: { content: content, picture: picture } end assert @user.microposts.first.picture? follow_redirect! assert_match content, response.body # 投稿を削除する assert_select 'a', text: 'delete' micropost = @user.microposts.paginate(page: 1).first assert_difference 'Micropost.count', -1 do delete micropost_path(micropost) end # 違うユーザーのプロフィールにアクセス(削除リンクがないことを確認) get user_path(users(:archer)) assert_select 'a', text: 'delete', count: 0 end
13.4.2 画像の検証
本章での学び
画像サイズや拡張子に対するバリデーションを実装する。
画像フォーマットのバリデーション追加
extension_whitelist
メソッドのコメントアウトを外す。
def extension_whitelist %w(jpg jpeg gif png) end
【model】画像サイズのバリデーション追加
画像のサイズを制限する独自のバリデーションを追加する。
独自のバリデーションを定義するためには、validates
メソッドではなく、validate
メソッドを使用する。
5MBを超えた場合はエラーメッセージを表示する。
validate :picture_size private # アップロードされた画像サイズをバリデーションする def picture_size if picture.size > 5.megabytes errors.add(:picture, "should be less than 5MB") end end
【view】パーシャルにファイルサイズチェックを追加する
マイクロポスト投稿フォームに、画像の拡張子の許可と、ファイルサイズチェックを行うjQuery関数を追加する。
jQuery関数では、ブラウザのインスペクタ機能でjavaScriptを直接修正したり、curlで直接POSTしてきた場合、防ぐことができない。そのために前項で実装したサーバサイド側のバリデーションも必要となる。
<%= 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>
演習1
5MB以上の画像ファイルを送信しようとした場合、どうなりますか?
jQueryの警告が表示されアップロードできない。
演習2
無効な拡張子のファイルを送信しようとした場合、どうなりますか?
ホワイトリストのエラーメッセージが表示される。
13.4.3 画像のリサイズ
本章での学び
画像のリサイズ処理を実装する。
【事前準備】ImageMagickのインストール
ImageMagickをインストールする。
yokoyan:~/workspace/sample_app (user-microposts) $ sudo apt-get update yokoyan:~/workspace/sample_app (user-microposts) $ sudo apt-get install imagemagick --fix-missing
アップローダーのリファクタリング
MiniMagickというImageMagickとRubyをつなぐgemを使って画像をリサイズする。 縦横どちらかが400pxを超えていた場合、適切なサイズにリサイズする。
class PictureUploader < CarrierWave::Uploader::Base # Include RMagick or MiniMagick support: # include CarrierWave::RMagick include CarrierWave::MiniMagick process reseize_to_limit: [400, 400]
演習1
解像度の高い画像をアップロードし、リサイズされているかどうか確認してみましょう。画像が長方形だった場合、リサイズはうまく行われているでしょうか?
上記の実装をした後、動作確認を行うとtranslation missing: en.errors.messages.mini_magick_processing_error
エラーが発生する。
以下の人と同じ現象。未解決。
Image translation missing: en.errors.messages.mini_magick_processing_error #2102
演習2
既にリスト 13.63のテストを追加していた場合、この時点でテストスイートを走らせるとエラーメッセージが表示されるようになるはずです。このエラーを取り除いてみましょう。ヒント: リスト 13.68にある設定ファイルを修正し、テスト時はCarrierWaveに画像のリサイズをさせないようにしてみましょう。
現時点でテストがredになることを確認。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test Running via Spring preloader in process 3120 Started with run options --seed 50042 DEPRECATION WARNING: ActionDispatch::IntegrationTest HTTP request methods will accept only======================================================================= ] 83% Time: 00:00:02, ETA: 00:00:01 the following keyword arguments in future Rails versions: params, headers, env, xhr, as Examples: get '/profile', params: { id: 1 }, headers: { 'X-Extra-Header' => '123' }, env: { 'action_dispatch.custom' => 'custom' }, xhr: true, as: :json (called from block (2 levels) in <class:MicropostsInterfaceTest> at /home/ubuntu/workspace/sample_app/test/integration/microposts_interface_test.rb:27) FAIL["test_micropost_interface", MicropostsInterfaceTest, 2.907269152114168] test_micropost_interface#MicropostsInterfaceTest (2.91s) "Micropost.count" didn't change by 1. Expected: 39 Actual: 38 test/integration/microposts_interface_test.rb:25:in `block in <class:MicropostsInterfaceTest>' 59/59: [======================================================================================================================================================================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.41207s 59 tests, 256 assertions, 1 failures, 0 errors, 0 skips
設定ファイルを生成する。
yokoyan:~/workspace/sample_app (user-microposts) $ touch config/initializers/skip_image_resizing.rb
テスト時はリサイズ処理をスキップする。
if Rails.env.test? CarrierWave.configure do |config| config.enable_processing = false end end
テストがgreenになることを確認。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test Running via Spring preloader in process 1833 Started with run options --seed 2677 DEPRECATION WARNING: ActionDispatch::IntegrationTest HTTP request methods will accept only========== ] 79% Time: 00:00:02, ETA: 00:00:01 the following keyword arguments in future Rails versions: params, headers, env, xhr, as Examples: get '/profile', params: { id: 1 }, headers: { 'X-Extra-Header' => '123' }, env: { 'action_dispatch.custom' => 'custom' }, xhr: true, as: :json (called from block (2 levels) in <class:MicropostsInterfaceTest> at /home/ubuntu/workspace/sample_app/test/integration/microposts_interface_test.rb:27) 59/59: [==================================================================================================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.69379s 59 tests, 262 assertions, 0 failures, 0 errors, 0 skips
13.4.4 本番環境での画像アップロード
本章での学び
設定の切り替え
本番環境ではfog gemを使って、AWSのS3に画像をアップロードできるようにする。
if Rails.env.production? storage :fog else storage :file end
【事前準備】AWSのIAM設定
IAMユーザを追加する。
プログラムによるアクセスにチェックを入れる。
ユーザーを作成して、AccessキーとSecretキーを取得する。
【事前準備】S3バケットの作成
任意のバケットを作成する。 バケット名はユニークにする必要があるので注意。 リージョンは アジアパシフィック (東京)を選択する。
他の設定はデフォルトのまま。
パブリックアクセスは付与しない。
バケットの完成。
【事前準備】CarrierWaveの設定ファイル作成
本番環境の場合に使用する、carrier_waveの設定ファイルを作成する。
yokoyan:~/workspace/sample_app (user-microposts) $ touch config/initializers/carrier_wave.rb
if Rails.env.production? CarrierWave.configure do |config| config.fog_credentials = { # Amazon S3用の設定 :provider => 'AWS', :region => ENV['S3_REGION'], :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側のAWS設定
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の編集
アップロードされたテスト画像が、gitで管理されないように、.gitignoreに追加する。
# アップロードされたテスト画像を無視する /public/uploads
gitへのコミット、マージ
commit、mergeを行い、gitへpushする。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test yokoyan:~/workspace/sample_app (user-microposts) $ git add -A yokoyan:~/workspace/sample_app (user-microposts) $ git commit -m "Add user microposts" yokoyan:~/workspace/sample_app (user-microposts) $ git checkout master Switched to branch 'master' Your branch is up-to-date with 'origin/master'. yokoyan:~/workspace/sample_app (master) $ git merge user-microposts yokoyan:~/workspace/sample_app (master) $ git push
本番環境へのリリース
Herokuへのデプロイを行う。
yokoyan:~/workspace/sample_app (master) $ git push heroku
データベースのリセットを行う。
yokoyan:~/workspace/sample_app (master) $ heroku pg:reset DATABASE ▸ heroku-cli: This CLI is deprecated. Please reinstall from https://cli.heroku.com ▸ WARNING: Destructive action ▸ postgresql-infinite-44520 will lose all of its data ▸ ▸ To proceed, type enigmatic-everglades-70434 or re-run this command with --confirm enigmatic-everglades-70434 > enigmatic-everglades-70434 Resetting postgresql-infinite-44520... done
データベースのマイグレーションを行う。
yokoyan:~/workspace/sample_app (master) $ heroku run rails db:migrate ▸ heroku-cli: This CLI is deprecated. Please reinstall from https://cli.heroku.com Running rails db:migrate on ⬢ enigmatic-everglades-70434... up, run.7170 (Free) D, [2017-08-23T04:06:30.854856 #4] DEBUG -- : (37.2ms) CREATE TABLE "schema_migrations" ("version" character varying PRIMARY KEY) ・・・略・・・ D, [2017-08-23T04:06:31.159450 #4] DEBUG -- : (2.2ms) COMMIT D, [2017-08-23T04:06:31.161335 #4] DEBUG -- : (1.7ms) SELECT pg_advisory_unlock(414146247089748600)
本番環境のデータベースでサンプルデータを生成する。
yokoyan:~/workspace/sample_app (master) $ heroku run rails db:seed ・・・略・・・ D, [2017-08-23T04:08:49.010414 #4] DEBUG -- : (0.9ms) BEGIN D, [2017-08-23T04:08:49.014906 #4] DEBUG -- : SQL (1.1ms) INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["content", "Recusandae asperiores dicta et consequatur praesentium error officia."], ["user_id", 6], ["created_at", 2017-08-23 04:08:49 UTC], ["updated_at", 2017-08-23 04:08:49 UTC]] D, [2017-08-23T04:08:49.024077 #4] DEBUG -- : (8.3ms) COMMIT
演習1
本番環境で解像度の高い画像をアップロードし、適切にリサイズされているか確認してみましょう。長方形の画像であっても、適切にリサイズされていますか?
本番環境にて解像度の高い画像をアップロードする。 本番環境では問題なくリサイズされていることを確認。
おわりに
画像の投稿ができるようになり、AWSのS3とも連携できるようになりました。 S3連携は敷居が高いかなと思っていたのですが、fog gemを入れれば簡単に連携することができました。 こういう豊富なgemが用意されているのもRailsの魅力ですね。
ローカルで発生した、translation missing: en.errors.messages.mini_magick_processing_errorエラーは未解決です。 gem側のバグなのか、環境依存の問題なのかの切り分けがまだできていませんが、 後日改めて調査したいと思います。