💎

【第3章】PHP歴8年・Ruby歴0秒の僕のRailsチュートリアル日記

2020/03/22に公開

こんにちは、たつきちです。

現在PHP歴8年ちょいで、4年ほど前には副著で 技術書を執筆 したりもした、PHPチョットデキル人です←

今更ながらRuby on Railsを学んでみようと思い立ったので、Rails入門者の登竜門 Railsチュートリアル に取り組んでみた様子をブログに連載していきます💪

今回は、全14章中の第3章です。

この連載の目次

  • 第1章 ゼロからデプロイまで
  • 第2章 Toyアプリケーション
  • 第3章 ほぼ静的なページの作成
  • 第4章 Rails風味のRuby(準備中)
  • 第5章 レイアウトを作成する(準備中)
  • 第6章 ユーザーのモデルを作成する(準備中)
  • 第7章 ユーザー登録(準備中)
  • 第8章 基本的なログイン機構(準備中)
  • 第9章 発展的なログイン機構(準備中)
  • 第10章 ユーザーの更新・表示・削除(準備中)
  • 第11章 アカウントの有効化(準備中)
  • 第12章 パスワードの再設定(準備中)
  • 第13章 ユーザーのマイクロポスト(準備中)
  • 第14章 ユーザーをフォローする(準備中)

第3章 ほぼ静的なページの作成

今回から、全章を通して作り込んでいく本格的なサンプルアプリの開発に着手するようです。

まずはその中でも静的なページを作るところから始めつつ、あわせて自動テスト化の雰囲気を掴みましょうとのこと。

楽しみです。

3.1 セットアップ

まずは作業ベースを準備します。1つのGitHubリポジトリを全章に渡って使い続けていくために、章ごとにブランチを切って、それぞれのブランチが1つのHerokuアプリに対応するようにしてあります。

今回も、第3章用に 03 ブランチを切って、Herokuのパイプラインにアプリを追加して、 03 ブランチを自動デプロイするようにします。

また、ローカルのリポジトリで heroku コマンドを使う場合に備えて、 heroku コマンドがデフォルトで使うアプリも切り替えておきます。

$ heroku git:remote -a {第3章用のアプリ名}
set git remote heroku to https://git.heroku.com/{第3章用のアプリ名}.git

では、準備が整ったので始めていきましょう💪

一旦新しいアプリを作り直すようです。

$ rm -rf * .bundle .gitignore
$ rails _5.1.6_ new .

一度プロジェクトを空にして(コミット)、カレントディレクトリをターゲットに rails new しました(コミット)。

さらに、前回までと同じように Gemfile もチュートリアルの指定の内容に変更します。Gemfile を変更したら、 bundle install して Gemfile.lock も更新します。(コミット

$ bundle update
$ bundle install --without production

READMEも実際にアプリの使い方や開発手順が分かる内容にしておきましょうということで、チュートリアルをコピペします。(コミット

デプロイが上手くいっていることを確認できるよう、今回もトップページをhello worldにします。(コミット

無事にデプロイできています👍

3.2 静的ページ

静的ページ用のコントローラを生成します。前回のように rails generate scaffold する代わりに rails generate controller とすればコントローラだけを生成できるようです。

$ rails generate controller StaticPages home help

一度目、実行してみるも何の応答もなし。10分ぐらい放置してみたけど何も変化なかったので、PCを再起動したら動きました。なんだったのか。(コミット

rails generate controller でコントローラを作ると、ルーティングと対応するビューも生成されるようです。

# config/routes.rb

Rails.application.routes.draw do
  get 'static_pages/home'

  get 'static_pages/help'

  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root 'application#hello'
end
<!-- app/views/static_pages/home.html.erb -->

<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>

http://localhost:3000/static_pages/home にアクセスすると、以下のような画面が表示されます。

チュートリアルの説明によると

get 'static_pages/home'
このルールは、/static_pages/homeというURLに対するリクエストを、StaticPagesコントローラの home アクションと結びつけています。今回は get と書かれているため、GETリクエストを受け取ったときに対応するアクションを結びつけています。

とのことです。 static_pages/home 自体はURLを表しているように思えますが、コントローラやビューのファイル名が命名規則に従っているために、URLとHTTPメソッドだけを宣言しておけばいい感じに繋がってくれるということなのでしょう。(とか想像しながら書いてましたが、少し先を読み進めていくとそのような説明が書いてありました笑)

最後に、「今作られている静的ページのビューは単なる静的なHTMLなので、HTMLの知識だけで試しに修正してみましょう」とのことで、チュートリアルのとおりに修正してみます。(コミット

画面を表示するとちゃんと変更されているのが分かります。

3.3 テストから始める

Home Help に加えて About という静的ページを加えるようですが、その際にテスト駆動でやってみましょうということです。

rails generate controller したときに、コントローラのテストは 雛形がすでに作成されている とのことです。

内容的には、( static_pages_home_url のような不思議なショートハンドが使われていますが)リクエストして200が返ってくることを確認するだけの最低限の機能テストになっていますね。

テストの実行は rails test で行うようです。

$ rails test
#
# なんかいっぱい警告が出るけどとりあえず無視
#
Finished in 1.072504s, 1.8648 runs/s, 1.8648 assertions/s.

2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

Aboutページのテストを追加してテストがREDになる(失敗する)ことを確認しましょうとのことです。(コミット

$ rails test

Finished in 0.838271s, 3.5788 runs/s, 2.3859 assertions/s.

  1) Error:
StaticPagesControllerTest#test_should_get_about:
NameError: undefined local variable or method `static_pages_about_url' for #<StaticPagesControllerTest:0x00007fef67731b40>
    test/controllers/static_pages_controller_test.rb:15:in `block in <class:StaticPagesControllerTest>'

3 runs, 2 assertions, 0 failures, 1 errors, 0 skips

static_pages_about_url が未定義だと怒られていますね。期待どおりの結果です。

このテストをパスさせるべく、ルーティングを追加します。(コミット

テストを実行してみると

$ rails test

Finished in 0.461982s, 6.4938 runs/s, 4.3292 assertions/s.

  1) Error:
StaticPagesControllerTest#test_should_get_about:
AbstractController::ActionNotFound: The action 'about' could not be found for StaticPagesController
    test/controllers/static_pages_controller_test.rb:15:in `block in <class:StaticPagesControllerTest>'

3 runs, 2 assertions, 0 failures, 1 errors, 0 skips

エラーの内容が変わりました。 The action 'about' could not be found for StaticPagesController ということで、コントローラにアクションがないよと言われています。

コントローラにアクションを追加します。(コミット

$ rails test

Finished in 1.119905s, 2.6788 runs/s, 1.7859 assertions/s.

  1) Error:
StaticPagesControllerTest#test_should_get_about:
ActionController::UnknownFormat: StaticPagesController#about is missing a template for this request format and variant.

request.formats: ["text/html"]
request.variant: []

NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
    test/controllers/static_pages_controller_test.rb:15:in `block in <class:StaticPagesControllerTest>'

3 runs, 2 assertions, 0 failures, 1 errors, 0 skips

今度は StaticPagesController#about is missing a template for this request format and variant. ということでビューのテンプレートがないというエラーですね。

テンプレートを作成します。(コミット

$ rails test

Finished in 0.505127s, 5.9391 runs/s, 5.9391 assertions/s.

3 runs, 3 assertions, 0 failures, 0 errors, 0 skips

やっとテストがGREENになりました👍

3.4 少しだけ動的なページ

次は、先ほど作った静的ページたちを少しだけ動的にします。

具体的には <title> タグの内容が、ページごとに <ページ名> | Ruby on Rails Tutorial Sample App になるようにするそうです。

各ページのテンプレートに別々にコードを書いたのでは動的にする意味が何もないので、レイアウトファイルにコードを集約して、1つのコードでページごとにタイトルを出し分けられるようにするようです。

レイアウトファイルは rails new したときに自動で作られている( app/views/layouts/application.html.erb )のですが、学習のために一旦このファイルは使わず、別の場所に退避しておきます。(コミット

$ mv app/views/layouts/application.html.erb layout_file

準備ができたところで、まずはREDになるテストを書きます。(コミット

$ rails test
3 tests, 6 assertions, 3 failures, 0 errors, 0 skips

次に、まずはレイアウトを使わずに各静的ページにタイトルを適切に付加します。(コミット

これによりひとまずテストはGREENになります。

$ rails test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips

ここで、テストコードの重複をなくすために setup メソッドを使ってリファクタします。(コミット

$ rails test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips

テストは壊れていません👍

ではいよいよ、レイアウトを使ってプロダクションコードのほうをリファクタします。

まずは第1ステップとして、テンプレート内に埋め込みRubyなるものを使ってタイトルを変数化します。(コミット

  • provide() はRailsが提供するメソッド(テンプレート用のメソッド?)
  • <% %> は実行するのみ、 <%= %> は実行結果を出力する

だそうです。

第2ステップで、各テンプレートの共通部分をレイアウトファイルに移動させます。

まずは、学習のために一旦退避してあったレイアウトファイルを戻します。(コミット

$ mv layout_file app/views/layouts/application.html.erb

続いて、各静的ページの共通するコードをレイアウトファイルに任せる形でリファクタします。(コミット

$ rails test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips

テストは壊れていません👍

演習でContactページを追加しましょうということなので、これまでの手順に従って以下のとおり実施します。

  • テストを追加(RED)
  • ルーティングを追加
  • コントローラにアクションを追加
  • テンプレートを追加(GREENに)

今回は1コミットで一気に書きました。(コミット

$ rails test
4 runs, 8 assertions, 0 failures, 0 errors, 0 skips

テストも通っています👍

最後に、ルートのルーティングをHomeに切り替えます。

static_pages_home_url などと同じように、 root_url というRailヘルパーもあって、ルートのURLを取得できるとのことで、これを使ってルートページの機能テストを追加しましょうという演習が設定されているので、ここまで一気に対応します。(コミット

$ rails test
5 runs, 9 assertions, 0 failures, 0 errors, 0 skips

テストも通っています👍

3.5 最後に

はい。

3.6 高度なセットアップ

今後のテスト駆動開発を便利にするために、

  • minitest-reportersを有効にしてCLIでのテスト結果を見やすくする
  • Guard を導入してファイルの変更検知から自動で差分テストが実行されるようにする

の2つをセットアップしましょうということです。

minitest-reportersの導入は問題なかった(コミット)のですが、Guardのほうがエラーで起動できません・・・😓

チュートリアルのとおりにGuardfileを用意して(コミット)起動してみると、

$ bundle exec guard

/Users/xxx/.gem/gems/guard-2.13.0/lib/guard/jobs/pry_wrapper.rb:131:in `_setup': undefined method `file=' for #<Pry::History:0x00007fc712029a78>
Did you mean?  filter (NoMethodError)
        from /Users/xxx/.gem/gems/guard-2.13.0/lib/guard/jobs/pry_wrapper.rb:68:in `initialize'
        from /Users/xxx/.gem/gems/guard-2.13.0/lib/guard/interactor.rb:15:in `new'
        from /Users/xxx/.gem/gems/guard-2.13.0/lib/guard/interactor.rb:15:in `initialize'
        from /Users/xxx/.gem/gems/guard-2.13.0/lib/guard.rb:67:in `new'
        from /Users/xxx/.gem/gems/guard-2.13.0/lib/guard.rb:67:in `setup'
        from /Users/xxx/.gem/gems/guard-2.13.0/lib/guard/commander.rb:32:in `start'
        from /Users/xxx/.gem/gems/guard-2.13.0/lib/guard/cli/environments/valid.rb:16:in `start_guard'
        from /Users/xxx/.gem/gems/guard-2.13.0/lib/guard/cli.rb:122:in `start'
        from /Users/xxx/.gem/gems/thor-1.0.1/lib/thor/command.rb:27:in `run'
        from /Users/xxx/.gem/gems/thor-1.0.1/lib/thor/invocation.rb:127:in `invoke_command'
        from /Users/xxx/.gem/gems/thor-1.0.1/lib/thor.rb:392:in `dispatch'
        from /Users/xxx/.gem/gems/thor-1.0.1/lib/thor/base.rb:485:in `start'
        from /Users/xxx/.gem/gems/guard-2.13.0/lib/guard/aruba_adapter.rb:32:in `execute'
        from /Users/xxx/.gem/gems/guard-2.13.0/lib/guard/aruba_adapter.rb:19:in `execute!'
        from /Users/xxx/.gem/gems/guard-2.13.0/bin/_guard-core:11:in `<main>'

Guardのソースコード内で undefined method とか言われてしまいます・・・

ソースを見てみると、この部分

Pry.config.history.file = File.expand_path(history_file_path)

で、なぜか「 file= なんていうメソッドないよ」と言われているようです。まったくの謎です。。。

ひとしきりググってみるも解決の糸口も何も見えず。。。チュートリアルの本筋ではないので一旦諦めて無視することにします。

頑張って第3章やってきたのに、最後の最後に気持ち悪い結果になりました😭

今回のまとめ

  • コントローラの雛形作成は rails g controller
  • テストの実行は rails test
  • RailsのビューはERB(Embedded RuBy)形式なので、埋め込みRubyが使える( <% %> は実行のみ、 <%= %> は実行結果を出力)
    • PHPっぽい
  • レイアウトファイルの <%= yield %> の部分に各ページのコンテンツが出力される
  • Guardが動いてくれないけど原因がまったく分からないので一旦無視・・・
  • 次回も頑張ります!
GitHubで編集を提案

Discussion