ぞえの技術めも

Ruby on Rails勉強中

【160日目】【1日20分のRailsチュートリアル】【第11章】画像のバリデーションを定義する

Ruby on Railsチュートリアル(第3版)

今日は「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章】基本的な画像アップロード機能を実装する

Ruby on Railsチュートリアル(第3版)

今日は「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

まずホーム画面にアクセス。

f:id:kt_zoe:20170703123527p:plain

画像をアップロードするフォーム?追加されてる!

試しにWindowsに元々入ってたサンプル ピクチャをアップロードしてみる。

f:id:kt_zoe:20170703123547p:plain

ひえっ!画像がデカすぎてレイアウトが残念な感じに…!!!
この辺はおいおい対応していくんでしょう。とりあえず画像のアップロードはできた、ということで。

今日の学習時間は【25分】

次は「11.4.2 画像の検証」から。

【158日目】【1日20分のRailsチュートリアル】【第11章】画像アップロードの準備

Ruby on Railsチュートリアル(第3版)

今日は「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モデルにアップローダーを追加した結果をリスト11.56に示します。

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章】フィード画面におけるマイクロポストのテストを追加する

Ruby on Railsチュートリアル(第3版)

今日は「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章】マイクロポストを削除する

Ruby on Railsチュートリアル(第3版)

今日は「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番目のマイクロポストを削除してみる。

確認のポップアップも表示されて

f:id:kt_zoe:20170626123549p:plain

マイクロポストも削除できました。

f:id:kt_zoe:20170626123606p:plain

今日の学習時間は【24分】

次は「11.3.5 フィード画面におけるマイクロポストのテスト」から。

【155日目】【1日20分のRailsチュートリアル】【第11章】お試しフィードを追加する

Ruby on Railsチュートリアル(第3版)

今日は「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

ホーム画面にフィードが表示されました。

f:id:kt_zoe:20170623124901p:plain

マイクロポスト投稿もOK!

f:id:kt_zoe:20170623124925p:plain

ただしささいなことではありますが、マイクロポストの投稿が失敗すると、 Homeページは@feed_itemsインスタンス変数を期待しているため、現状では壊れてしまいます。

試しに空で投稿してみたらエラー画面が。。。

f:id:kt_zoe:20170623124950p:plain

最も簡単な解決方法は、リスト11.48のように空の配列を渡しておくことです。

とりあえずcreateアクションで空の配列渡すようにしておきましょう。

app/controllers/microposts_controller.rb

    :
    else
      @feed_items = []
      render 'static_pages/home'
    end
    :

これするとどうなるのかなー、と思ったらフィードが表示されなくなるのか。。。まぁ空の配列渡してるもんね。

f:id:kt_zoe:20170623125008p:plain

今日の学習時間は【28分】

次は「11.3.4 マイクロポストを削除する」から。

【154日目】【1日20分のRailsチュートリアル】【第11章】マイクロポスト作成フォームを定義する

Ruby on Railsチュートリアル(第3版)

今日は「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

ホーム画面にアクセスしてみる。

おぉ、マイクロポスト投稿フォームができてる!

f:id:kt_zoe:20170621123854p:plain

空のまま「Post」をクリックしてエラーメッセージが出ることも確認。

f:id:kt_zoe:20170621123911p:plain

今日の学習時間は【35分】

次は「11.3.3 フィードの原型」から。