【160日目】【1日20分のRailsチュートリアル】【第11章】画像のバリデーションを定義する
今日は「11.4.2 画像の検証」から。
11.4.2 画像の検証
11.4.1のアップローダーも悪くはありませんが、いくつかの目立つ欠点があります。
例えば、アップロードされた画像に対する制限がないため、もしユーザーが巨大なファイルを上げたり、無効なファイルを上げると問題が発生してしまいます
とてつもなく容量の大きい画像がアップロードされたらレイアウト崩れだけじゃない問題発生しそう。
生成されたアップローダーの中にコメントアウトされたコードがありますが、ここのコメントアウトを取り消すことで、画像のファイル名から有効な拡張子 (PNG/GIF/JPEGなど) を検証することができます (リスト11.60)。
40行目あたりにあるextension_white_list
のコメントアウトを外す。有効な拡張子はこれでいいのかな。いいんだろうな。
app/uploaders/picture_uploader.rb
# アップロード可能な拡張子のリスト def extension_white_list %w(jpg jpeg gif png) end
2つ目のバリデーションでは、画像のサイズを制御します。これはMicropostモデルに書き足していきます。
画像の容量は5MBまで。
独自のバリデーションを定義するために、今まで使っていたvalidatesメソッドではなく、validateメソッドを使っている点に注目してください。
何が違うんだろう…。独自のバリデーションを定義するためにはvalidateメソッドってこと?
app/models/micropost.rb
: validate :picture_size private # アップロード画像のサイズを検証する def picture_size if picture.size > 5.megabytes errors.add(:picture, "should be less than 5MB") end end end
中途半端だけど今日はここまで。
今日の学習時間は【17分】。
次は「11.4.2 画像の検証」の画像のバリデーションをビューに組み込むところから。
【159日目】【1日20分のRailsチュートリアル】【第11章】基本的な画像アップロード機能を実装する
今日は「11.4.1 基本的な画像アップロード」のHomeページ上にアップローダーを追加するところから。
11.4.1 基本的な画像アップロード
図 11.18のようにHomeページ上にアップローダーを追加するためには、マイクロポストのフォームにfile_fieldタグを含める必要があります (リスト11.57)。
マイクロポストのフォームにfile_fieldタグを追加する。
app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost, html: { multipart: true }) do |f| %> : <span class="picture"> <%= f.file_field :picture %> </span> <% end %>
html: { multipart: true }
form_forの引数に上のオプションが追加されていることに注目してください。これはファイルをアップロードする際に必要となるオプションです。
へー。画像をアップロードするにはオプションの追加が必要、と。
最後に、Webから更新できる許可リストにpicture属性を追加しましょう。追加すると、micropost_paramsメソッドはリスト11.58のようになります。
picture属性追加する。
app/controllers/microposts_controller.rb
private def micropost_params params.require(:micropost).permit(:content, :picture) end
一度画像がアップロードされれば、Micropostパーシャルのimage_tagヘルパーでその画像を描画できるようになります (リスト11.59)。
spanタグの中に画像を描画するようにビューを修正。
app/views/microposts/_micropost.html.erb
: <span class="content"> <%= micropost.content %> <%= image_tag micropost.picture.url if micropost.picture? %> </span> :
手動で画像付きの投稿をしてみると、図 11.20のようになります。
動作確認してみよう。サーバーを起動して
$ rails server -b $IP -p $PORT
まずホーム画面にアクセス。
画像をアップロードするフォーム?追加されてる!
試しにWindowsに元々入ってたサンプル ピクチャをアップロードしてみる。
ひえっ!画像がデカすぎてレイアウトが残念な感じに…!!!
この辺はおいおい対応していくんでしょう。とりあえず画像のアップロードはできた、ということで。
今日の学習時間は【25分】。
次は「11.4.2 画像の検証」から。
【158日目】【1日20分のRailsチュートリアル】【第11章】画像アップロードの準備
今日は「11.4 マイクロポストの画像投稿」から。
11.4 マイクロポストの画像投稿
この節では、応用編として画像付きマイクロポストを投稿できるようにしてみます。
ほへー。画像も扱うのか…!
投稿された画像ってどこに保存するんだろう。
11.4.1 基本的な画像アップロード
投稿した画像を扱ったり、その画像をMicropostモデルと関連付けするために、今回はCarrierWaveという画像アップローダーを使います。まずはcarrierwave gemをGemfileに追加しましょう (リスト11.55)。
なるほど、画像を扱うためのgemがあってそれを使うのか…!色んなものあるね
Gemfile
gem 'carrierwave', '0.10.0' gem 'mini_magick', '3.8.0' gem 'fog', '1.36.0'
あとで必要になるmini_magick gemとfog gemsも含めている点に注目してください。これらのgemは画像をリサイズしたり (11.4.3)、本番環境で画像をアップロードする (11.4.4) ために使います。
色んなものあるね
いつものようにインストールします。
インストールしましょう。
$ bundle install : Bundle complete! 26 Gemfile dependencies, 116 gems now installed. Gems in the group production were not installed. Use `bundle show [gemname]` to see where a bundled gem is installed.
なんか関連するgemがいっぱいインストールされた。
CarrierWaveを導入すると、Railsのジェネレーターで画像アップローダーが生成できるようになります。早速、次のコマンドを実行してみましょう (画像のことをimageとすると一般的過ぎるので、今回はpictureと呼ぶことにします) 。
imageとpictureの違い分かってないや…まぁいっか。。。。
$ rails generate uploader Picture create app/uploaders/picture_uploader.rb
CarrierWaveでアップロードされた画像は、Active Recordモデルの属性と関連付けされているべきです。関連付けされる属性には画像のファイル名が格納されるため、String型にしておきます。(図11.19)
ふむふむ。データベースには画像のファイル名を格納して関連付けるのか。
必要となるpicture属性をMicropostモデルに追加するために、マイグレーションファイルを生成し、開発環境のデータベースに適用します。
マイグレーションしてデータベースを更新。
$ rails generate migration add_picture_to_microposts picture:string invoke active_record create db/migrate/20170629020527_add_picture_to_microposts.rb $ bundle exec rake db:migrate : == 20170629020527 AddPictureToMicroposts: migrating =========================== -- add_column(:microposts, :picture, :string) -> 0.0005s == 20170629020527 AddPictureToMicroposts: migrated (0.0006s) ==================
Micropostモデルにアップローダーを追加。
app/models/micropost.rb
: mount_uploader :picture, PictureUploader :
システムによっては、ここで一旦Railsサーバーを再起動させる必要があります。再起動させたらテストスイートを走らせてみてください。成功しているはずです。
今日はサーバーを起動させてないのでそのままテスト実行で大丈夫かな。
$ bundle exec rake test 59 tests, 306 assertions, 0 failures, 0 errors, 0 skips
うん、大丈夫でした。
項の途中だけど今日はここまで。
今日の学習時間は【20分】。
次は「11.4.1 基本的な画像アップロード」のHomeページ上にアップローダーを追加するところから。
【157日目】【1日20分のRailsチュートリアル】【第11章】フィード画面におけるマイクロポストのテストを追加する
今日は「11.3.5 フィード画面におけるマイクロポストのテスト」から。
11.3.5 フィード画面におけるマイクロポストのテスト
まずはマイクロポスト用のfixtureに、別々のユーザーに紐付けられたマイクロポストを追加していきます (リスト11.51)。
michael
以外のユーザーに紐付けられたマイクロポストをいくつか追加。
test/fixtures/microposts.yml
ants: content: "Oh, is that what you want? Because that's how you get ants!" created_at: <%= 2.years.ago %> user: archer zone: content: "Danger zone!" created_at: <%= 3.days.ago %> user: archer tone: content: "I'm sorry. Your words made sense, but your sarcastic tone did not." created_at: <%= 10.minutes.ago %> user: lana van: content: "Dude, this van's, like, rolling probable cause." created_at: <%= 4.hours.ago %> user: lana
次に、自分以外のユーザーのマイクロポストは削除をしようとすると、適切にリダイレクトされることをテストで確認します (リスト11.52)。
michael
がさっき追加したants
のマイクロポストを削除しようとするとルートにリダイレクトされることをテストで確認する。
test/controllers/microposts_controller_test.rb
test "should redirect destroy for wrong micropost" do log_in_as(users(:michael)) micropost = microposts(:ants) assert_no_difference 'Micropost.count' do delete :destroy, id: micropost end assert_redirected_to root_url end
最後に、統合テストを書きます。今回の統合テストでは、ログイン、マイクロポストのページ分割の確認、無効なマイクロポストを投稿、有効なマイクロポストを投稿、マイクロポストの削除、そして他のユーザーのマイクロポストには [delete] リンクが表示されないことを確認、といった順でテストしていきます。
いつものように、統合テストを生成するところから始めましょう。
統合テストは盛りだくさんだな…。
まずは統合テストを生成。
$ rails generate integration_test microposts_interface invoke test_unit create test/integration/microposts_interface_test.rb
先ほどの順で書いた統合テストは、リスト11.53のようになります。
ふーむ、サンプルコード読んだら何となく何してるかは分かるかな…。
follow_redirect!
って何だろうと思ったけど、リダイレクトを追うコードらしい。
リダイレクトを追ってって、リダイレクト後のページをテストするのか。
test/integration/microposts_interface_test.rb
require 'test_helper' class MicropostsInterfaceTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "micropost interface" do log_in_as(@user) get root_path assert_select 'div.pagination' # 無効な送信 assert_no_difference 'Micropost.count' do post microposts_path, micropost: { content: "" } end assert_select 'div#error_explanation' # 有効な送信 content = "This micropost really ties the room together" assert_difference 'Micropost.count', 1 do post microposts_path, micropost: { content: content } end assert_redirected_to root_url follow_redirect! assert_match content, response.body # 投稿を削除する assert_select 'a', text: 'delete' first_micropost = @user.microposts.paginate(page: 1).first assert_difference 'Micropost.count', -1 do delete micropost_path(first_micropost) end # 違うユーザーのプロフィールにアクセスする get user_path(users(:archer)) assert_select 'a', text: 'delete', count: 0 end end
既にアプリケーション側のコードは実装してあるので、このテストは成功するはずです。
成功しますね。
$ bundle exec rake test 59 tests, 306 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【19分】。
次は「11.4 マイクロポストの画像投稿」から。
【156日目】【1日20分のRailsチュートリアル】【第11章】マイクロポストを削除する
今日は「11.3.4 マイクロポストを削除する」から。
11.3.4 マイクロポストを削除する
最後の機能として、マイクロポストリソースにポストを削除する機能を追加します。これはユーザー削除と同様に(9.4.2)、"delete" リンクで実現します (図11.16)。
(中略)
カレントユーザーが作成したマイクロポストに対してのみ削除リンクが動作するようにします。
自分が投稿したマイクロポストしか消せないようにしないとね。
最初のステップとして、マイクロポストのパーシャル (リスト11.21) に削除リンクを追加します。
マイクロポストの投稿ユーザーとカレントユーザーが一致するときは削除リンクを表示する。
app/views/microposts/_micropost.html.erb
: <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %> <% end %> :
次に、Micropostsコントローラのdestroyアクションを定義しましょう。
(中略)
大きな違いは、admin_userフィルターで@user変数を使うのではなく、関連付けを使ってマイクロポストを見つけるようにしている点です。
マイクロポストのidは一意なので、カレントユーザーのマイクロポストからfindしたときに見つからない→カレントユーザーのマイクロポストじゃない→削除できない、ってできるってことかな。
削除リンクも表示させないけど、destroyアクション呼ばれたときにもチェックしてあるユーザーが他のユーザーのマイクロポストを削除しようとするのを防ぐのかー。
app/controllers/microposts_controller.rb
: before_action :correct_user, only: :destroy : def destroy @micropost.destroy flash[:success] = "Micropost deleted" redirect_to request.referrer || root_url end private : def correct_user @micropost = current_user.microposts.find_by(id: params[:id]) redirect_to root_url if @micropost.nil? end end
マイクロポストがHomeページから削除された場合でもProfileページから削除された場合でも、request.referrerを使うことでDELETEリクエストが発行されたページに戻すことができるので、非常に便利です。
URLって保存されてるんだなぁ…。
これらのコードにより、上から2番目のマイクロポストを削除すると、図 11.17のようにうまく動くはずです。
動作見てみよう。
サーバーを起動して
$ rails server -b $IP -p $PORT
上から2番目のマイクロポストを削除してみる。
確認のポップアップも表示されて
マイクロポストも削除できました。
今日の学習時間は【24分】。
次は「11.3.5 フィード画面におけるマイクロポストのテスト」から。
【155日目】【1日20分のRailsチュートリアル】【第11章】お試しフィードを追加する
今日は「11.3.3 フィードの原型」から。
11.3.3 フィードの原型
マイクロポスト投稿フォームが動くようになりましたが、今の段階では投稿した内容をすぐに見ることができません。というのも、Homeページにまだマイクロポストを表示する部分が実装されていないからです。
ホーム画面でマイクロポスト投稿してからプロフィール画面に行けば見れるけど、めんどいよね!ってことか。そうだね、微妙に手間だね。
すべてのユーザーがフィードを持つので、feedメソッドはUserモデルで作るのが自然です。フィードの原型では、まずは現在ログインしているユーザーのマイクロポストをすべて取得してきます。(リスト11.44)
whereメソッドを使って現在ログインしているユーザーのマイクロポストを全て取得。
app/models/user.rb
: # 試作feedの定義 # 完全な実装は第12章「ユーザーをフォローする」を参照してください。 def feed Micropost.where("user_id = ?", id) end :
上の疑問符があることで、SQLクエリにインクルードされる前にidが適切にエスケープされることを保証してくれるため、SQLインジェクションと呼ばれる深刻なセキュリティホールを避けることができます。
へー。SQLクエリ使うときは気をつけなきゃいけないのか。。。
サンプルアプリケーションでフィードを使うために、カレントユーザーのページ分割されたフィードに@feed_itemsインスタンス変数を追加し (リスト11.45)、次にフィード用のパーシャル (リスト11.46) をHomeページに追加します。
homeアクションに@feed_items
インスタンス変数を追加。
app/controllers/static_pages_controller.rb
def home if logged_in? @micropost = current_user.microposts.build @feed_items = current_user.feed.paginate(page: params[:page]) end end
フィード用のパーシャルはまずファイルを生成して
$ touch app/views/shared/_feed.html.erb
下記内容で更新。
app/views/shared/_feed.html.erb
<% if @feed_items.any? %> <ol class="microposts"> <%= render @feed_items %> </ol> <%= will_paginate @feed_items %> <% end %>
このとき、@feed_itemsの各要素がMicropostクラスを持っていたため、RailsはMicropostのパーシャルの呼び出すことができました。
Micropostクラスを持っていたら@feed_items
で「1つのマイクロポストを表示するパーシャル」を呼び出すことができるんだ…!なんか不思議。
後は、いつものようにフィードパーシャルを表示すればHomeページにフィードを追加できます (リスト11.47)。
マイクロポストの投稿フォームタグの下にフィードを追加。
app/views/static_pages/home.html.erb
<% if logged_in? %> : <div class="col-md-8"> <h3>Micropost Feed</h3> <%= render 'shared/feed' %> </div> :
動作確認のためにサーバーを起動して
$ rails server -b $IP -p $PORT
ホーム画面にフィードが表示されました。
マイクロポスト投稿もOK!
ただしささいなことではありますが、マイクロポストの投稿が失敗すると、 Homeページは@feed_itemsインスタンス変数を期待しているため、現状では壊れてしまいます。
試しに空で投稿してみたらエラー画面が。。。
最も簡単な解決方法は、リスト11.48のように空の配列を渡しておくことです。
とりあえずcreateアクションで空の配列渡すようにしておきましょう。
app/controllers/microposts_controller.rb
: else @feed_items = [] render 'static_pages/home' end :
これするとどうなるのかなー、と思ったらフィードが表示されなくなるのか。。。まぁ空の配列渡してるもんね。
今日の学習時間は【28分】。
次は「11.3.4 マイクロポストを削除する」から。
【154日目】【1日20分のRailsチュートリアル】【第11章】マイクロポスト作成フォームを定義する
今日は「11.3.2 マイクロポストを作成する」のマイクロポスト作成フォームを定義するところから。
11.3.2 マイクロポストを作成する
次はマイクロポスト作成フォームを定義します (リスト11.37)。
これもファイルから作成します。
$ touch app/views/shared/_micropost_form.html.erb
できたファイルを下記内容で更新。マイクロポストを入力してPOSTで送信するフォームを定義。
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" %> <% end %>
1つは、(以前と同様) 関連付けを使用して次のように@micropostを定義することです。
homeアクションにマイクロポストのインスタンス変数を追加。
app/controllers/static_pages_controller.rb
def home @micropost = current_user.microposts.build if logged_in? end
リスト11.37を動かすためのもう1つの変更は、エラーメッセージのパーシャルを再定義することです。
(中略)
object: f.objectはerror_messagesパーシャルの中でobjectという変数名を作成してくれるので、この変数を使ってエラーメッセージを更新すればよいということです (リスト11.39)。
うーん、なんか難しい…。
error_messagesパーシャルを汎用的に使えるようにするためのあれこれってことだよね…。
@user
としてたところをobject
に変更。
app/views/shared/_error_messages.html.erb
<% if object.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(object.errors.count, "error") %>. </div> <ul> <% object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>
この時点でテストを走らせてみてください。テストが失敗したままになっています。
$ bundle exec rake test ERROR["test_password_resets", PasswordResetsTest, 2017-06-08 11:12:25 +0000] : 57 tests, 286 assertions, 0 failures, 1 errors, 0 skips
失敗しますね。
このパーシャルは他の場所でも使われていたため、ユーザー登録 (リスト7.18)、パスワード再設定 (リスト10.50)、そしてユーザー編集 (リスト9.2) のそれぞれのビューを更新する必要があったのです。
パスワード再設定のやつしかログには出てないけど、いいのかな。
app/views/password_resets/edit.html.erb
: <%= render 'shared/error_messages', object: f.object %> :
試しにパスワード再設定だけ直してテスト実施してみる。
$ bundle exec rake test 57 tests, 294 assertions, 0 failures, 0 errors, 0 skips
…通っちゃったよ!!何故だ……
ユーザー登録のソースコード見てみると、「9.6 演習」のnewフォームとeditフォームをパーシャル化するの対応がmasterに入っちゃってるっぽい。
あれー、演習の内容マージしたっけな…。エラー出てないしこっちは直さなくてもいいのかな。
パーシャルの方でobject: @user
って指定してるから問題ないのかも。でも一応f.object
に統一しておくか。
app/views/users/_form.html.erb
: <%= render 'shared/error_messages', object: f.object %> :
修正してもテストが通ることを確認。
$ bundle exec rake test 57 tests, 294 assertions, 0 failures, 0 errors, 0 skips
さらに、この章で作成したすべてのHTMLが適切に表示されるようになったはずです。
動作確認してみましょう。
サーバーを起動して
$ rails server -b $IP -p $PORT
ホーム画面にアクセスしてみる。
おぉ、マイクロポスト投稿フォームができてる!
空のまま「Post」をクリックしてエラーメッセージが出ることも確認。
今日の学習時間は【35分】。
次は「11.3.3 フィードの原型」から。