【第3章】PHP歴8年・Ruby歴0秒の僕のRailsチュートリアル日記
こんにちは、たつきちです。
現在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が動いてくれないけど原因がまったく分からないので一旦無視・・・
- 次回も頑張ります!
Discussion