おぴよの気まぐれ日記

おぴよの気まぐれ日記

岡山やプログラミング、ファッションのこと、子育てや人生、生き方についての備忘録。

Ruby on Rails チュートリアルで30歳までに人生を変える(第3章)

こんちには、opiyoです。

人生の生き残りをかけて始めた「Railsブートキャンプ」ですが、今日はRailsチュートリアル第3章をやっていこうと思います。

railsチュートリアル 3章の流れ

静的なページのセットアップ

繰り返しの復習は大事です。人間はすぐに忘れてしまう動物なので。

改めてrails newして最低限のところまでを作ります!

https://railstutorial.jp/chapters/static_pages?version=5.0#sec-sample_app_setup

  • $ rails _5.0.0.1_ new sample_app
  • Gemfileをいじります
source 'https://rubygems.org'

gem 'rails',        '5.0.0.1'
gem 'puma',         '3.4.0'
gem 'sass-rails',   '5.0.6'
gem 'uglifier',     '3.0.0'
gem 'coffee-rails', '4.2.1'
gem 'jquery-rails', '4.1.1'
gem 'turbolinks',   '5.0.1'
gem 'jbuilder',     '2.4.1'

group :development, :test do
  gem 'sqlite3', '1.3.11'
  gem 'byebug',  '9.0.0', platform: :mri
end

group :development do
  gem 'web-console',           '3.1.1'
  gem 'listen',                '3.0.8'
  gem 'spring',                '1.7.2'
  gem 'spring-watcher-listen', '2.0.0'
end

group :test do
  gem 'rails-controller-testing', '0.1.1'
  gem 'minitest-reporters',       '1.1.9'
  gem 'guard',                    '2.13.0'
  gem 'guard-minitest',           '2.4.4'
end

group :production do
  gem 'pg', '0.18.4'
end

# Windows環境ではtzinfo-dataというgemを含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
  • $ bundle install --without production
    • エラーが出たら $ bundle update する
  • gitを使ってsample_appをバージョン管理下にする
  • トップページ(hogehoge.com/)にhello worldを表示されるようにコードを修正する
  • herokuの設定を行いpushします
  • heroku openしてhello worldが表示されればOK

githerokuの設定はチュートリアルを見ずにやってみることで、覚えているかの復習になりますね!

コントローラーの作り方

  • 表記はキャメルケース = 単語の先頭を大文字になる(StaticPages)
  • アクション名は全て小文字にする
  • 出来上がるコントローラーファイル名はスネークケース = 単語をアンダーバー_でつなぎ合わせた名前になる
$ rails g controller StaticPages home help # コントローラー名 アクション名ですね
      create  app/controllers/static_pages_controller.rb # 出来上がったコントローラーのファイル
       route  get 'static_pages/help'
       route  get 'static_pages/home'
      invoke  erb
      create    app/views/static_pages
      create    app/views/static_pages/home.html.erb
      create    app/views/static_pages/help.html.erb
      invoke  test_unit
      create    test/controllers/static_pages_controller_test.rb
      invoke  helper
      create    app/helpers/static_pages_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/static_pages.coffee
      invoke    scss
      create      app/assets/stylesheets/static_pages.scss
  • routes.rbが自動的に更新される
Rails.application.routes.draw do
  get 'static_pages/home' # 自動的に追加されたよ
  get 'static_pages/help' # 自動的に追加されたよ
  root 'application#hello'
end

このgetは、/static_pages/home/というURLでアクセスされたら、static_pagesコントローラーのhomeアクションと結びつく設定になります。

テスト駆動開発 (TDD)

考え方

テストの手法のひとつで、最初に「正しいコードがないと失敗するテスト」を書き、次に本編のコードを書いてそのテストがパスするようにする考え、やり方かな。

テストをするメリットはチュートリアルのコラムの中で書かれてますが、引用します。

  1. テストが揃っていれば、機能停止に陥るような回帰バグ (Regression Bug: 以前のバグが再発したり機能の追加/変更に副作用が生じたりすること) を防止できる。
  2. テストが揃っていれば、コードを安全にリファクタリング (機能を変更せずにコードを改善すること) ができる。
  3. テストコードは、アプリケーションコードから見ればクライアントとして動作するので、アプリケーションの設計やシステムの他の部分とのインターフェイスを決めるときにも役に立つ。

さらに詳細までみたいな場合は、こちらからどうぞ(コラム3-3.です)

https://railstutorial.jp/chapters/static_pages?version=5.0#sec-custom_static_pages

当たり前ですが、何か問題があるとお客様に迷惑をかけます。それは本当に申し訳無いところなのですが、お客様以外のところで凄く面倒なことになります。

だからテストを書くのです。もーこれだけ。

テストコードの書き方

テストコードはrails g controllerを実行した時点で作成されます。

require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest
  test "should get home" do
    get static_pages_home_url
    assert_response :success
  end
end

これを日本語で表すと、「Homeページのテスト。GETリクエストをhomeアクションに対して発行 (=送信) せよ。そうすれば、リクエストに対するレスポンスは[成功]になるはず。」となります。

テストの実行方法は

$ rails test
# Running:

..
Finished in 1.467940s, 1.3625 runs/s, 1.3625 assertions/s.
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

0 errorsとなっているので、成功(GREEN)したことが分かりますね。

もしエラーになる場合は、こちらに回避方法書いたので参考にしてください。 opiyotan.hatenablog.com

このように、テストと一緒にコードを書いていくことが最近は推奨されています。

3.3.1では、TDDの考えに基づき失敗するエラーを書いて成功させリファクタリングするという流れでテストする方法が乗っていますので是非チャレンジしてみてください。

動的にタイトルを変更する

先ずは各ページのタイトルをチェックするテストを書きます。 もちろん、viewtitleタグは何も変更していないのでエラー(RED)になります。

require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest
  test "should get home" do
    get static_pages_home_url
    assert_response :success
    assert_select "title", "Home | Ruby on Rails Tutorial Sample App" # ここ追加しました。"title"タグに書かれた文字列をチェックするテストです
  end
end

では、次にviewtitleタグを修正していきます。始めにapp/views/static_pages/home.html.erbファイルから。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Home | Ruby on Rails Tutorial Sample App</title> # この部分ですね
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
    </p>
  </body>
</html>

この状態でテストを実行するとhomeアクションについては修正したのでエラーじゃなくなりますが他の2つのアクションがエラーになります

$ rails test
# Running:

Failure:
StaticPagesControllerTest#test_should_get_about [/Users/taku/rails/railstutorial/sample_app/test/controllers/static_pages_controller_test.rb:19]:
<About | Ruby on Rails Tutorial Sample App> expected but was
<SampleApp>..
Expected 0 to be >= 1.

bin/rails test test/controllers/static_pages_controller_test.rb:16

Failure:
StaticPagesControllerTest#test_should_get_help [/Users/taku/rails/railstutorial/sample_app/test/controllers/static_pages_controller_test.rb:13]:
<Help | Ruby on Rails Tutorial Sample App> expected but was
<SampleApp>..
Expected 0 to be >= 1.

bin/rails test test/controllers/static_pages_controller_test.rb:10

Finished in 0.649278s, 4.6205 runs/s, 9.2410 assertions/s.
3 runs, 6 assertions, 2 failures, 0 errors, 0 skips

なので、エラー(RED)にならないようhelpaboutも修正し成功(GREEN)させます。

リファクタリング

今までは全てのviewファイルに直接titleタグを書き込んでいました。なので当然表示されるタイトルも変わってきます。

これらを今テストでやったように共通にできるところは共通化し、違う部分だけを設定するように修正していきます。

このように同じコードを繰り返し書かないようにする考え方を「DRY(Don’t Repeat Yourself: 繰り返すべからず) 」と言います。

では早速やってみます。先ずは各ページに動的に変更する部分を書いていきます。

app/views/static_pages/home.html.erb

<% provide(:title, "Home") %> # 各ビュー毎に文字列をセットする
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title> # 設定した文字列をyield関数を使って反映させる
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

次に共通のビューになるapplication.html.erbtitleyield関数を使って表示されるようにします

<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title> # ここですねー
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                                              'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %> # ここで各ページを読み込んでいるから、各ビューでは`head`タグとかがいらないんですね
  </body>
</html>

それぞれのページでは違う処理だけ書く。共通部分はapplication.html.erbで定義する!!

<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
  sample application.
</p>

railsチュートリアル 3章の演習問題

3.1 演習

問題1

BitbucketがMarkdown記法のREADME (リスト 3.3) をHTMLとして正しく描画しているか、確認してみてください。

回答1

ぼくは、githubで作業しているのですが問題なく描画されていますね。

  • h1は大きな文字
  • h2は少し大きな文字
  • リンクは青色で
  • コード部分は灰色になる

https://github.com/nakanoTaku/railstutorial_3

問題2

本番環境 (Heroku) のルートURLにアクセスして、デプロイが成功したかどうか確かめてみてください。

回答2

hello worldが表示されているのでOKですね! heroku logsでログを見ることも出来ます。

3.2.1 演習

問題1

Fooというコントローラを生成し、その中にbarとbazアクションを追加してみてください。

回答1

$ rails g controller Foo bar baz

class FooController < ApplicationController
  def bar
  end
  def baz
  end
end

問題2

コラム 3.1で紹介したテクニックを駆使して、Fooコントローラとそれに関連するアクションを削除してみてください。

回答2

$ rails destroy controller Foo bar baz

3.4.2 演習

テストを一部リファクタリングします。

3つのアクションで同じように使われている文字列「Ruby on Rails Tutorial Sample App」を変数に代入し、使い回すようにします。

require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest
  def setup
    @base_title = "Ruby on Rails Tutorial Sample App" # setupというテストが実行される前に処理されるメソッド内で文字列を変数にセットしておきます。
  end

  test "should get home" do
    get static_pages_home_url
    assert_response :success
    assert_select "title", "Home | #{@base_title}" # 変数を参照するように修正します。
  end
end

3.4.4 演習

問題1

リスト 3.41にrootルーティングを追加したことで、root_urlというRailsヘルパーが使えるようになりました (以前、static_pages_home_urlが使えるようになったときと同じです)。リスト 3.42のFILL_INと記された部分を置き換えて、rootルーティングのテストを書いてみてください。

回答1

  test "should get root" do
    get root_url
    assert_response :success
  end

問題2

実はリスト 3.41のコードを書いていたので、先ほどの課題のテストは既に green になっているはずです。このような場合、テストを変更する前から成功していたのか、変更した後に成功するようになったのか、判断が難しいです。リスト 3.41のコードがテスト結果に影響を与えていることを確認するため、リスト 3.43のようにrootルーティングをコメントアウトして見て、 red になるかどうか確かめてみましょう (なおRubyのコメント機能については4.2.1で説明します)。最後に、コメントアウトした箇所を元に戻し (すなわちリスト 3.41に戻し)、テストが green になることを確認してみましょう。

回答2

ちゃんとREDになるし、GREEMにもなる

railsチュートリアル 3章で学んだこと

テストをパワーアップする

minitest reporters

テストの成功(RED)失敗(GREEN)の見栄えをよくしてくれます。

準備するのはこれだけ、是非試してみてください!

# ファイル名:test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require "minitest/reporters"
Minitest::Reporters.use!

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
end

f:id:opiyotan:20170620173015p:plain

Guard

ファイルの変更を検出して必要なテストだけを自動実行してくれるそうです。

例えば、「home.html.erbファイルが変更されたらstatic_pages_controller_test.rbを自動的に実行する」といったことをGuardで設定することができます。

これヤバいですね。今はまだ変更するファイルも少ないですし何をしたか全部記憶できるくらいしかないですが絶対面倒になって忘れますよね。

詳細な設定方法はRailsチュートリアルを参照してください。

https://railstutorial.jp/chapters/static_pages?version=5.0#sec-guard

手順だけ簡単にまとめておきます。

  • 初期化
$ bundle exec guard init

初期化を実行すると、「/Guardfile」が作成されます。

  • Guardfileを編集する
  • .gitignoreにSpringを追加する
  • Guardの実行
$ bundle exec guard

なんか実行すると「Ctrl + D」か「テストが成功」するまで終わらないのだけど、これは正しい動きなのか?

ずっとバックグラウンドで動かしておいて、常に監視させておくってことなのかな...

あーすごい便利かなーと思ったけど、これは使わないかもしれない。邪魔っちー

httpメソッド

HTTP (HyperText Transfer Protocol) には4つの基本的な操作があり、それぞれGET、POST、PATCH、DELETEという4つの動詞に対応づけられています。クライアント (例えばFirefoxSafariなどのWebブラウザ) とサーバー (ApacheやNginxなどのWebサーバー) は、上で述べた4つの基本操作を互いに認識できるようになっています

  • get

主にWeb上のデータを読み取る (get) ときに使われるリクエストです。

  • post

ページ上のフォームに入力した値を、ブラウザから送信する時に送られるリクエストです。

gitでaddとcommitを一回でやる!

$ git commit -am "Improve the README"

herokuへpushした時に何かエラー

$ heroku logs

新しいブランチの作り方

$ git checkout -b hogehoge

今チェックアウトしているブランチは?

$ git branch
  master
* static-pages

ブランチを指定してpushするには?

$ git push origin hogehoge

rails g をやり直したい!

$ rails destroy  controller StaticPages home help
$ rails destroy model User

Unixプロセスの確認

$ ps aux
$ ps aux | grep spring
$ ps aux | grep spring
taku              7781   0.0  0.2  2518224  19900   ??  S     5:23PM   0:00.42 spring server | sample_app | started 31 mins ago
taku              8561   0.0  0.0  2423376    216 s003  R+    5:55PM   0:00.00 grep spring
taku              7782   0.0  0.9  2577420  73224   ??  Ss    5:23PM   0:02.75 spring app    | sample_app | started 31 mins ago | test mode

$ spring stop
$ ps aux | grep spring
taku              8604   0.0  0.0  2434840    804 s003  S+    5:57PM   0:00.00 grep spring # 「7781」「7782」が無くなった!

railsチュートリアル 3章のまとめ

3章ではテスト駆動開発 (TDD)に沿って、開発を進めてきました。 簡単にまとめるこんな感じでしょうか?

  • 何事も繰り返し行うことで覚えることができる
    • rails new
    • git init ~ git pushまでの流れ
  • rails g controller ControllerName アクション名でコントローラーが作れる
  • RailsのビューはRubyのコードが使える
  • テスト駆動開発 (TDD)は、「RED」「GREEN」「REFACTOR」のサイクルを繰り返して開発をすることだ


では、最後にぼくが一番尊敬している先輩エンジニアからの名言でお別れしましょー

テストコードが仕様書だ