Ruby on Rails チュートリアル

プロダクト開発の0→1を学ぼう

第7版 目次

第2章Toyアプリケーション

この章では、Railsの強力な機能をいくつか紹介するためのToyアプリケーションを作成します。大量の機能を自動的に生成するscaffoldジェネレータというスクリプトを使ってアプリケーションをすばやく生成し、それを元に高度なRailsプログラミングとWebプログラミングの概要を学びます。コラム 2.1でも説明しますが、3以降では基本的にこの逆のアプローチを取り、少しずつアプリケーションを作りながら各段階と概念を説明する予定です。とは言えscaffoldはRailsアプリケーションの概要を素早くつかむには最適なので、この章でのみあえて使うことにします。生成されたToyアプリケーションはブラウザのアドレスバーにURLを入力すれば動かせます。これを使って、Railsアプリの構造とRailsで推奨されているRESTアーキテクチャーについて考察することにします。

Toyアプリケーションは、後に作成するサンプルアプリケーションと同様、ユーザーと、それに関連しているマイクロポストから成り立っています。このToyアプリケーションはもちろん動きますが完成品とは言えません。多くの手順が「魔法」のように思えるかもしれませんが、3以降で作成するサンプルアプリケーションでは同等の機能を1つ1つ手動で作成しますので、ご安心ください。その分時間がかかることになりますが、どうか最後まで本書にお付き合いいただければと思います。本書の目的は、scaffoldを使った手軽なアプローチではなく、そこを突破してRailsを深いレベルまで理解することにあります。

コラム 2.1. お手軽すぎるScaffoldの甘い誘惑

Railsの作者David Heinemer Hansson氏による有名な動画「15分で作るブログ(英語)」の印象がとても強かったおかげで、Railsは立ち上げ当初から一気に盛り上がりました。この後にも続々同じような動画が作られていますが、いずれもRailsの能力の一端を垣間見るにはぴったりなので、ぜひ一度ご覧ください。この動画では「15分でブログを作る」ためにScaffold(足場)という手軽な生成機能を使っています。Railsの魔法のようなgenerate scaffoldコマンドで自動生成したコードがあるからこそ、このような早業が可能なのです。

実際、筆者はRuby on Railsのチュートリアルを書きながら、あまりにも手軽にコードを生成できるscaffoldの機能を使って教えたい誘惑にかられることが何度もありました。しかし、自動生成されたコードは量が多く複雑で、Rails初心者には向いていません。たとえ運よく動いたとしても、正常に動いている理由を解明するのはおそらく難しいでしょう。これではRailsに関する実践的な知識はほとんど身に付きません。

Railsチュートリアルでは、より実践的な知識を身につけるために、Scaffoldとほぼ逆のアプローチで開発を進めていきます。具体的には、2で作成する簡単なデモアプリではscaffoldを使いますが、このチュートリアルの中核である3以降のサンプルアプリケーションからは、scaffoldを一切使わずに開発を進めていきます。scaffold を使わない代わりに、開発の各ステップで、手頃なサイズのコードを書いてもらいます。この手頃なサイズのコードは、無理なく理解できる程度にシンプルで、かつ、ある程度の手ごたえとやりがいを得られるように配慮してあります。各ステップで理解する必要のあるコードの量はわずかですが、こうした理解を積み重ねていくことで、最終的にRailsの知識を高いレベルで身につけられるように構成されています。このようにして得た深い知識は柔軟性が高く、どのようなWebアプリを作成する時にも応用が効きます。

2.1 アプリケーションの計画

はじめに、Toyアプリケーションをどのようなものにするのか、計画を立てましょう。Codespacesを使用する場合、1.2.1の手順を参考にRailsチュートリアル用のテンプレートページから新しくtoy_appリポジトリを作成します。そこからCodespacesを開くと、この後の説明はスキップして2.1.1から学習できます。

それ以外の環境では1.3で説明したように、rails newコマンドでRailsのバージョン番号を指定して、アプリケーションの骨組みを生成するところから始めましょう。

$ cd ~/environment
$ rails _7.0.4.3_ new toy_app
$ cd toy_app/

次に、Bundlerで扱うGemfileをエディタで編集します。リスト 2.1を参考にRailsチュートリアル用のテンプレートページと同じ内容に内容に書き換えてください。

電子書籍版を読んでいる方へ】本書のGemfileで固定した各gemのバージョンは公式リポジトリと一致している必要があります。現在の章に合わせてご参照ください。
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.2.6"

gem "rails",           "7.0.4.3"
gem "sassc-rails",     "2.1.2"
gem "sprockets-rails", "3.4.2"
gem "importmap-rails", "1.1.5"
gem "turbo-rails",     "1.4.0"
gem "stimulus-rails",  "1.2.1"
gem "jbuilder",        "2.11.5"
gem "puma",            "5.6.8"
gem "bootsnap",        "1.16.0", require: false
gem "sqlite3",         "1.6.1"

group :development, :test do
  gem 'reline', '0.5.10'
  gem "debug",   "1.7.1", platforms: %i[ mri mingw x64_mingw ]
end

group :development do
  gem "web-console",         "4.2.0"
  gem "solargraph",          "0.50.0"
  gem "irb",                 "1.10.0"
  gem "repl_type_completor", "0.1.2"
end

group :test do
  gem "capybara",                 "3.38.0"
  gem "selenium-webdriver",       "4.8.3"
  gem "webdrivers",               "5.2.0"
  gem "rails-controller-testing", "1.0.5"
  gem "minitest",                 "5.18.0"
  gem "minitest-reporters",       "1.6.0"
  gem "guard",                    "2.18.0"
  gem "guard-minitest",           "2.4.6"
end

# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります
#gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]

続いて、bundle installを実行してgemをインストールします。リスト 1.8と同じく、一部のシステムで必要となるLinuxデプロイプラットフォーム向けの行も含めています。

$ bundle install
Fetching source index for https://rubygems.org/
.
.
.
$ bundle lock --add-platform x86_64-linux

1.3.1でも説明したように、もしうまく動かなかったらbundle updateを実行してみてください (コラム 1.2)。「The dependency tzinfo-data」で始まる警告メッセージが表示されることがありますが、この警告は無視しても大丈夫です。

ローカルのWebサーバーをターミナルの別タブで実行しておくことで、アプリが実行されていることを確認しながら作業できます。

7
図 2.1: Codespacesで新しいターミナルを開く

新しいターミナルを開いたら、1.3.2で学んだ方法を使ってRailsサーバーを起動しましょう。

$ rails server

これで、1.3.2で説明した通りにローカルWebサーバーにアクセスできるようになるはずです。ブラウザで/(スラッシュと読みます)というルートURLを開くと、Codespaces以外の方はRailsのデフォルトページが表示されるでしょう。

アプリが動くことを確認できたので、GitでこのToyアプリケーションをバージョン管理下に置きます。

$ git init
$ git add -A
$ git commit -m "Add hello"

Codespaces 以外の環境では、この時点でGitHub上に新しいリポジトリを作成します(このときリポジトリを図 2.2のようにprivateにしておくことをお忘れなく)。続いて、生成したファイルをこの新しいリモートリポジトリにプッシュします。

$ git remote add origin https://github.com/<あなたのGitHubアカウント名>/toy_app.git
$ git push -u origin main
7
図 2.2: GitHubにtoyアプリのリポジトリを作成する

これでアプリケーション開発の下準備が整いました。

2.1.1 ユーザーのモデル設計

Webアプリケーションを作る際、アプリケーションの構造を表すデータモデルを最初に設計しておくと手戻りが防げて便利なので、まずは設計から始めます。本章の題材となる“toy_app”は、ユーザーと短いマイクロポスト(旧Twitterにおけるツイート)のみを取り扱う小さな練習用アプリケーションです。本来はパスワード(password)の取り扱い方なども設計する必要がありますが、今回は練習なので一旦考えずに進めていきます。本格的なパスワードの設計および実装については6で説明します。

それでは“toy_app”の開発に向けて、Userモデル(2.1.1)とMicropostモデル(2.1.2)のそれぞれについて考えていきましょう。

Webでのユーザー登録の方法が多岐にわたることからもわかるように、ユーザーという概念をデータモデルで表す方法はたくさんありますが、ここではあえて最小限の表現方法を使います。

各ユーザーには、重複のない一意のキーとなるinteger型のID番号(idと呼びます)を割り当て、このIDに加えて一般公開されるstring型の名前(name)、そして同じくstring型のメールアドレス(email)を持たせます。メールアドレスはユーザー名としても使われます。ユーザーのデータモデルの概要を図 2.3に示します。

7
図 2.3: ユーザーのデータモデル

詳しくは6.1.1から解説しますが、図 2.3のユーザー(users)はデータベースのテーブル(table)に相当します。また、 idnameemail の属性はそれぞれテーブルのカラム(column: 列)に相当します。

2.1.2 マイクロポストのモデル設計

マイクロポストは比較的短い投稿であると最初に説明したことを思い出しましょう。マイクロポストはTwitterの用語では「ツイート」と呼ばれますが、本質的には一般用語です(「micro」というプレフィックスを使っているのは、初期のTwitterが用いていた「micro-blog」という説明に従った結果です)。

マイクロポストのデータモデルの中核部分はユーザーよりもさらにシンプルです。idとマイクロポストのテキスト内容を格納するtext型のcontentだけで構成されています1

しかし実際には、マイクロポストをユーザーと関連付ける(associate) 必要があります。そのため、マイクロポストの投稿者を記録するためのuser_idも追加します。これにより、データモデルは図 2.4のようになります。

7
図 2.4: マイクロポストのデータモデル

2.3.3では、user_idという属性を使って、1人のユーザーに複数のマイクロポストが関連付けられるという構造を簡潔に説明します。詳細は13で完全に説明します。

2.2 Usersリソース

ここでは、2.1.1で説明したユーザー用のデータモデルを、そのモデルを表示するためのWebインターフェイスに従って実装します。このデータモデルとWebインターフェイスは、組み合わさってUsersリソースとなり、ユーザーというものを、HTTPプロトコル経由で自由に作成/取得/更新/削除できるオブジェクトとみなすことができるようになります。「はじめに」で約束したとおり、このUsersリソースはすべてのRailsプロジェクトに標準装備されているscaffoldジェネレータで生成します。scaffoldで生成された膨大なコードを今詳細に読む必要はありません。今の段階ではおそらく混乱するだけでしょう。

Railsのscaffoldは、rails generateスクリプトにscaffoldコマンドを渡すことで生成されます。scaffoldコマンドの引数には、リソース名を単数形にしたもの(この場合はUser)を使い、必要に応じてデータモデルの属性をオプションとしてパラメータに追加します2

$ rails generate scaffold User name:string email:string
      invoke  active_record
      create    db/migrate/<タイムスタンプ>_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      invoke  resource_route
       route    resources :users
      invoke  scaffold_controller
      create    app/controllers/users_controller.rb
      invoke    erb
      create      app/views/users
      create      app/views/users/index.html.erb
      create      app/views/users/edit.html.erb
      create      app/views/users/show.html.erb
      create      app/views/users/new.html.erb
      create      app/views/users/_form.html.erb
      invoke    test_unit
      create      test/controllers/users_controller_test.rb
      create      test/system/users_test.rb
      invoke    helper
      create      app/helpers/users_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/users/index.json.jbuilder
      create      app/views/users/show.json.jbuilder
      create      app/views/users/_user.json.jbuilder

name:stringemail:stringオプションを追加することで、Userモデルの内容が図 2.3の表のとおりになるようにします。なお、idパラメータはRailsによって自動的に主キーとしてデータベースに追加されるため、追加不要です。

続いてToyアプリケーションの開発を進めるには、次のようにrails db:migrateを実行してデータベースをマイグレート(migrate)する必要があります(リスト 2.2)。

リスト 2.2: データベースをマイグレートする
  $ rails db:migrate
  == CreateUsers: migrating ======================================
  -- create_table(:users)
     -> 0.0027s
  == CreateUsers: migrated (0.0036s) =============================

リスト 2.2のコマンドは、単にデータベースを更新し、usersデータモデルを作成するためのものです(データベースのマイグレーションの詳細については6.1.1以降で説明します)。

2.2.1 ユーザーページを探検する

Usersリソースをscaffoldで生成したことで(2.2)、ユーザー管理用のページが多数Railsに追加されます。たとえば、/usersをブラウザで表示すればすべてのユーザーの一覧が表示されますし、/users/newを表示すれば新規ユーザー作成ページが表示されます。このセクションでは以後、ユーザーに関連するページについて手短に説明します。その際、表 2.1に記載されている、ページとURLの関係を参照するとわかりやすいと思います。

URL アクション 用途
/users index すべてのユーザーを一覧するページ
/users/1 show id=1のユーザーを表示するページ
/users/new new 新規ユーザーを作成するページ
/users/1/edit edit id=1のユーザーを編集するページ
表 2.1: Usersリソースにおける、ページとURLの関係。

まずはユーザーの一覧を表示するindexアクションのURL(/users)を見てみましょう。このページに移動するには、ブラウザのアドレスバーをクリックし、ルートURLの末尾に「/users」を追加します(図 2.5)。

7
図 2.5: ルートURLに/usersを追加する

もちろん、この時点ではまだユーザーは登録されていません(図 2.6)。

7
図 2.6: Usersリソース(/users)ページの最初の状態

ユーザーを新規作成するには、[New user]をクリックしてnewページを表示します(図 2.7)。なお、7ではこのページがユーザー登録ページに変わります。

7
図 2.7: 新規ユーザー作成ページ(/users/new)

テキストフィールドに名前とメールアドレスを入力して[Create User]ボタンを押してください。図 2.8のようにshowページが表示されます(緑色のウェルカムメッセージは、7.4.2で解説するflashという機能を使って表示しています)。ここで、URLが/users/1と表示されていることに注目してください。ご想像のとおり、この1という数字は図 2.3のid属性そのものです。7.1では、このページをユーザーのプロフィールページに作り変える予定です。

7
図 2.8: ユーザー表示用のページ(/users/1)

今度はユーザーの情報を変更するため、[Edit this user]をクリックして/users/1/editにあるeditページを表示してみましょう(図 2.9)。この編集ページ上でユーザーに関する情報を変更し、[Update User]ボタンを押せば、Toyアプリケーション内のユーザー情報が変更されます(図 2.10)。(詳細は6で説明しますが、このユーザー情報は、Webアプリケーションの背後にあるデータベースに保存されています)。サンプルアプリケーションでも、ユーザーを編集または更新する機能を10.1で実装します。

7
図 2.9: ユーザー編集用のページ(/users/1/edit)
7
図 2.10: 情報が更新されたユーザー

ここで/users/newにあるnewページに戻り、ユーザーをもう1人作成してみましょう。indexページを表示してみると、図 2.11のようにユーザーが追加されています。7.1ではもっと本格的なユーザー一覧ページを作成する予定です。

7
図 2.11: 2人目のユーザーが追加された一覧ページ(/users)

ユーザーの作成、表示、編集方法について説明しましたので、今度はユーザーを削除してみましょう(図 2.12)。図 2.8の[Destroy this user]をクリックするとユーザーが削除され、indexページのユーザーは1人だけになります(もしこのとおりにならない場合は、ブラウザのJavaScriptが有効になっているかどうかを確認してください。Railsでは、ユーザーを削除するリクエストを発行するときにJavaScriptを使っています)。なお、この後の 10.4ではサンプルアプリケーションにユーザーを削除する機能を実装し、管理権限(admin)を持つユーザー以外は削除を実行できないように制限をかけます。

7
図 2.12: ユーザーを削除する

演習

  1. emailを入力せず、名前だけを入力しようとした場合、どうなるでしょうか?
  2. 「@example.com」のような間違ったメールアドレスを入力して更新しようとした場合、どうなるでしょうか?
  3. 上記の演習で作成したユーザーを削除してみてください。ユーザーを削除したとき、Railsはどんなメッセージを表示するでしょうか?

2.2.2 MVCの挙動

これでUsersリソースの概略についての説明が終わりましたが、ここで1.3.3で紹介した MVC(Model-View-Controller = モデル-ビュー-コントローラ)パターンの観点からこのリソースを考察してみましょう。具体的には、「/users にあるindexページをブラウザで開く」という操作をしたとき、内部では何が起こっているかについてMVC(図 2.13)で説明します。

7
図 2.13: RailsにおけるMVC

図 2.13で行われている手順の概要を以下に示します。

  1. ブラウザから「/users」というURLのリクエストをRailsサーバーに送信する。
  2. /users」リクエストは、Railsのルーティング機構(ルーター)によってUsersコントローラ内のindexアクションに割り当てられる。
  3. indexアクションが実行され、そこからUserモデルに、「すべてのユーザーを取り出せ」(User.all)と問い合わせる。
  4. Userモデルは問い合わせを受け、すべてのユーザーをデータベースから取り出す。
  5. データベースから取り出したユーザーの一覧をUserモデルからコントローラに返す。
  6. Usersコントローラは、ユーザーの一覧を@users変数(@はRubyのインスタンス変数を表す)に保存し、indexビューに渡す。
  7. indexビューが起動し、ERB(Embedded RuBy: ビューのHTMLに埋め込まれているRubyコード)を実行して HTMLを生成(レンダリング)する。
  8. コントローラは、ビューで生成されたHTMLを受け取り、ブラウザに返す3

上の流れをもう少し詳しく見てみることにします。最初にブラウザからのリクエストを見てみましょう。このリクエストは、アドレスバーにURLを入力したりリンクをクリックした時に発生します(図 2.13の①)。 リクエストはRailsルーティングに到達し(②)、ここでURL(とリクエストの種類: コラム 3.2参照)に基づいて適切なコントローラのアクションに割り当てられます(ディスパッチ)。

ユーザーからリクエストされたURLを、Usersリソースで使うコントローラのアクションに割り当てるためのコードは、リスト 2.3のようになります。 このようなマッピングするコードはRailsのルーティング設定ファイル(config/routes.rb)に書きます。Railsではconfig/routes.rbで、URLとアクションの組み合わせ(表 2.1)を効率よく設定していきます(:usersという一見奇妙な記法は、Ruby言語特有の「シンボル」と呼ばれるものです。詳細については4.3.3で説明します)。

リスト 2.3: Railsルートで使うUsersリソース用のルール config/routes.rb
Rails.application.routes.draw do
  resources :users
  root 'hello#index'
end

それでは、このルーティングファイルを変更してみましょう。サーバーのルートURLにアクセスしたら、デフォルトのページの代わりにユーザー一覧を表示するようにします。つまり、「/」(スラッシュ)にアクセスしたら/usersを開くようにします。現在のルートは次のとおりです。

root 'hello#index'

これにより、ルートにアクセスするとhelloコントローラ内のindexアクションにルーティングされています。今回の場合は、Usersコントローラのindexアクションを使いたいので、リスト 2.4のコードを元に書き換えてみましょう。

リスト 2.4: ルートからusersへのルーティングを追加する config/routes.rb
Rails.application.routes.draw do
  resources :users
  root 'users#index'
end

2.2.1以降で紹介した各ページは、Usersコントローラ内のアクションにそれぞれ対応しています。リスト 2.5は、scaffoldで生成したコントローラの骨格です。class UsersController < ApplicationControllerという記法では、Rubyのクラス継承の文法をそのまま使っていることにご注目ください(継承の概略については2.3.4、詳細については4.4で説明します)。

リスト 2.5: Usersコントローラの骨格 app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def index
    .
    .
    .
  end

  def show
    .
    .
    .
  end

  def new
    .
    .
    .
  end

  def edit
    .
    .
    .
  end

  def create
    .
    .
    .
  end

  def update
    .
    .
    .
  end

  def destroy
    .
    .
    .
  end
end

ページの数よりもアクションの数の方が多いことにお気付きでしょうか。indexshowneweditアクションはいずれも2.2.1のページに対応していますが、それ以外にもcreateupdatedestroyアクションがあります。通常、これらのアクションは、ページを出力せずにデータベース上のユーザー情報を操作します(もちろんページを出力しようと思えばできますが)。

表 2.2は、RailsにおけるRESTアーキテクチャー(コラム 2.2)を構成するすべてのアクションの一覧です。RESTは、コンピュータ科学者Roy Fieldingによって提唱された「REpresentational State Transfer」という概念に基づいています4表 2.2のURLには重複しているものがあることにご注目ください。例えば、showアクションと updateアクションは、どちらも/users/1というURLに対応しています。これらのアクション同士の違いは、それらのアクションに対応するHTTP requestメソッドの違いでもあります。HTTP requestメソッドの詳細については3.3で説明します。

HTTPリクエストメソッド URL アクション 用途
GET /users index すべてのユーザーを一覧するページ
GET /users/1 show id=1のユーザーを表示するページ
GET /users/new new 新規ユーザーを作成するページ
POST /users create ユーザーを作成するアクション
GET /users/1/edit edit id=1のユーザーを編集するページ
PATCH /users/1 update id=1のユーザーを更新するアクション
DELETE /users/1 destroy id=1のユーザーを削除するアクション
表 2.2: リスト 2.3のUsersリソースが提供するRESTfulなルート
コラム 2.2. REpresentational State Transfer(REST)をチョット理解する

Rails関連の書籍を読んでいると “REST” という略語をよく見かけます。RESTはREpresentational State Transferの略で、Railsを筆頭に多くのフレームワーク(例: LaravelDjangoなど)でも意識されている、Webアプリケーションのよくあるアーキテクチャー(つまり先人達の知恵によって「良い」とされている手法)の1つです。

元々のRESTの学術論文はやや抽象的に見えるかもしれませんが、RailsにおけるRESTは具体的です。例えばToyアプリケーションを構成する「ユーザー」や「マイクロポスト」などの情報を「リソース」というパターンに当てはめていた部分がこれに相当します。具体的には、データベースにおける4つの基本的な操作「作成 (Create)・取得 (Read)・更新 (Update)・削除 (Delete)」と、HTTPにおける4つの基本的な操作「作成 (POST)・取得 (GET)・更新 (PATCH)・削除 (DELETE)」をそれぞれ繋げることで、ユーザー情報の作成(=ユーザー登録)や、マイクロポスト情報の削除(=投稿の削除)などをシンプルに実現していた部分を指します。(今は理解できなくても大丈夫です。詳細は次章以降にあるコラム 3.2解説動画などで少しずつ説明していきます。)

RESTfulなスタイルに沿って作っていくことで、「いつコントローラを作るべきか?」「どんなアクションを作るべきか?」などで悩む場面が少なくなり、より素早いプロダクト開発が可能になります。とはいえ、実世界の複雑な問題を4つの基本的な操作でどう解決するかについては開発者の腕の見せ所の1つとなります。本章のユーザーやマイクロポストではRESTfulなスタイルをそのまま適用して「リソース」にできましたが、そうではない場面もあるでしょう。最後の14「ユーザーをフォローする」では、RESTfulなスタイルがそのまま適用できなさそうに見える場面の考え方(=モデリング)についても解説します。

UsersコントローラとUserモデルの関係をさらに考察するために、リスト 2.6indexアクションを整理してみました(コラム 1.2でも紹介しましたが、たとえ完全に理解できなくても、コードの読み方について学ぶことは非常に重要です)。

リスト 2.6: Toyアプリケーションの簡潔なユーザーindexアクション app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def index
    @users = User.all
  end
  .
  .
  .
end

indexアクションに@users = User.allという行があります(図 2.13の③に相当)。これによって、Userモデルからすべてのユーザーの一覧を取り出し(④)、@usersという変数に保存します(⑤)。なお、@usersは「あっと ゆーざーず」と発音します。

Userモデルの内容はリスト 2.7にあります。驚くほどシンプルな内容ですが、継承(2.3.4および4.4)によって多くの機能が備わっています。特に、Active RecordというRubyライブラリのおかげで、リスト 2.7のUserモデルはUser.allというリクエストに対して、DB上のすべてのユーザーを返すことができます。

リスト 2.7: ToyアプリケーションのUserモデル app/models/user.rb
class User < ApplicationRecord
end

@users変数にユーザー一覧が保存されると、コントローラはリスト 2.8ビューを呼び出します(⑥)。 @記号で始まる変数をRubyではインスタンス変数と呼び、Railsのコントローラ内で宣言したインスタンス変数はビューでも使えるようになります。この場合、リスト 2.8index.html.erbビューは@usersの一覧を並べ、1行ごとにHTMLの行として出力します(今はこのコードの意味がわからなくても問題ありません。これはあくまで説明のためのものです)。

リスト 2.8: usersのindexアクションに対応しているビュー app/views/users/index.html.erb
<p style="color: green"><%= notice %></p>

<h1>Users</h1>

<div id="users">
  <% @users.each do |user| %>
    <%= render user %>
    <p>
      <%= link_to "Show this user", user %>
    </p>
  <% end %>
</div>

<%= link_to "New user", new_user_path %>

ビューはその内容をHTMLに変換し(⑦)、コントローラがブラウザにHTMLを送信して、ブラウザでHTMLが表示されます(⑧)。

演習

  1. 図 2.13を参考にしながら、/users/1/editというURLにアクセスしたときの振る舞いについて図を書いてみてください。
  2. 図示した振る舞いを見ながら、Scaffoldで生成されたコードの中でデータベースからユーザー情報を取得しているコードを探してみてください。(ヒント: set_userという特殊な場所の中にあります。)
  3. ユーザーの情報を編集するページのファイル名は何でしょうか?

2.2.3 Usersリソースの欠点

scaffoldで作成したUsersリソースは、Railsの概要を手っ取り早く説明するには良いのですが、次のような様々な問題点を抱えています。

  • データの検証が行われていない。 このままでは、ユーザー名が空欄の場合や、でたらめなメールアドレスを入力した場合でも登録されてしまいます。
  • ユーザー認証が行われていない。 ログイン、ログアウトが行われていないので、誰でも無制限に操作できてしまいます。
  • テストが十分に書かれていない。 Scaffoldが生成したコードにはテストも含まれていますが、あくまでScaffold(土台)と言える部分のみです。
  • レイアウトやスタイルが整っていない。 生成するのはあくまで骨組みのみで、どうデザインするかは開発者の裁量に委ねられています。
  • 理解が困難。 Scaffoldは素早く開発するための機能で、学習用では無いです。もしScafolldだけで理解できたら、あなたは既に「熟練」の域に達していると言えるでしょう。(コラム 1.2

2.3 Micropostsリソース

Usersリソースを生成して内容を理解しましたので、今度はMicropostsリソースで同じことをやってみましょう。なお、この節全体について、Micropostsリソースを理解する際には2.2のuser要素と比較しながら進めることをオススメします。実際、これらの2つのリソースは様々な面で似通っています。RailsのRESTful構造を身体に叩きこむには、繰り返し学ぶのが一番です。UsersリソースとMicropostsリソースの構造の類似点を理解することが、この章の主要な目的です。

2.3.1 マイクロポストを探検する

Usersリソースの場合と同様に、Micropostsリソースもscaffoldでコードを生成してみましょう。rails generate scaffoldコマンドを使って、図 2.4のデータモデルを実装してみます5

$ rails generate scaffold Micropost content:text user_id:integer
      invoke  active_record
      create    db/migrate/<タイムスタンプ>_create_microposts.rb
      create    app/models/micropost.rb
      invoke    test_unit
      create      test/models/micropost_test.rb
      create      test/fixtures/microposts.yml
      invoke  resource_route
       route    resources :microposts
      invoke  scaffold_controller
      create    app/controllers/microposts_controller.rb
      invoke    erb
      create      app/views/microposts
      create      app/views/microposts/index.html.erb
      create      app/views/microposts/edit.html.erb
      create      app/views/microposts/show.html.erb
      create      app/views/microposts/new.html.erb
      create      app/views/microposts/_form.html.erb
      invoke    test_unit
      create      test/controllers/microposts_controller_test.rb
      create      test/system/microposts_test.rb
      invoke    helper
      create      app/helpers/microposts_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/microposts/index.json.jbuilder
      create      app/views/microposts/show.json.jbuilder
      create      app/views/microposts/_micropost.json.jbuilder

新しいデータモデルでデータベースを更新するには、2.2のときと同様にマイグレーションを実行します。

$ rails db:migrate
==  CreateMicroposts: migrating ===============================================
-- create_table(:microposts)
   -> 0.0023s
==  CreateMicroposts: migrated (0.0026s) ======================================

これでMicropostsを作成する準備ができました。作成方法は2.2.1と同じです。既にお気づきかもしれませんが、scaffoldジェネレータによってRailsのroutesファイルが更新され、リスト 2.9のようにMicropostsリソース用のルールが追加されました6。ユーザーの場合と同様に、resources :micropostsというルーティングルールは、表 2.3で示すようにマイクロポスト用のURIをMicropostsコントローラ内のアクションに割り当てます。

リスト 2.9: Railsルートで使うMicropostsリソース用のルール config/routes.rb
Rails.application.routes.draw do
  resources :microposts
  resources :users
  root 'users#index'
end
HTTPリクエストメソッド URL アクション 用途
GET /microposts index すべてのマイクロポストを表示するページ
GET /microposts/1 show id=1のマイクロポストを表示するページ
GET /microposts/new new マイクロポストを新規作成するページ
POST /microposts create マイクロポストを新規作成するアクション
GET /microposts/1/edit edit id=1のマイクロポストを編集するページ
PATCH /microposts/1 update id=1のマイクロポストを更新するアクション
DELETE /microposts/1 destroy id1のマイクロポストを削除する
表 2.3: Micropostsリソースが提供するリスト 2.9のRESTfulルート

Micropostsコントローラ自体の構造をリスト 2.10に示します。リスト 2.10の内容は、UsersControllerMicropostsControllerに置き換わっていること以外はリスト 2.5完全に同一である点に注目してください。これは、RESTアーキテクチャーが2つのリソースに同じように反映されていることを示しています。

リスト 2.10: Micropostsコントローラの骨格 app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  .
  .
  .
  def index
    .
    .
    .
  end

  def show
    .
    .
    .
  end

  def new
    .
    .
    .
  end

  def edit
    .
    .
    .
  end

  def create
    .
    .
    .
  end

  def update
    .
    .
    .
  end

  def destroy
    .
    .
    .
  end
end

マイクロポストのインデックスページで[New Micropost]をクリックして/microposts/newページをブラウザで開き(図 2.14)、新しいマイクロポストの情報を入力してマイクロポストを実際にいくつか作成してみましょう(図 2.15)。

7
図 2.14: マイクロポストのインデックスページ(/microposts)
7
図 2.15: 新しいマイクロポストの作成ページ(/microposts/new)

ここではひとまずマイクロポストを1つか2つ作成し、少なくとも片方のuser_id1になるようにして、2.2.1で作成した最初のユーザーのidと同じにします。結果は図 2.16のようになるはずです。

7
図 2.16: マイクロポストのindexページに数件投稿がある状態

演習

  1. マイクロポストの作成画面で、ContentもUserも空のまま作成してみるとと、どうなるでしょうか?
  2. 141文字以上の文字列をContentに入力した状態で、マイクロポストを作成しようとするとどうなるでしょうか?(ヒント: WikipediaのRubyの記事にある設計思想の引用文が140文字を超えているので、これをコピペしてみましょう。)
  3. 上記の演習で作成したマイクロポストを削除してみましょう。

2.3.2 マイクロポストをマイクロにする

マイクロポストのマイクロという名前にふさわしくなるように、何らかの方法で文字数制限を与えてみましょう。Railsではこのようなとき、バリデーション(validation)を使って簡単に制限を加えることができます。例えばTwitterの元の設計に倣って140文字制限を加えたいときは、lengthを使うだけです。エディタかIDEを使ってapp/models/micropost.rbを開き、リスト 2.11の内容を書き加えてみてください。

リスト 2.11: マイクロポストの最大文字数を140文字に制限する。 app/models/micropost.rb
class Micropost < ApplicationRecord
  validates :content, length: { maximum: 140 }
end

リスト 2.11のコードは、これで本当に動作するのだろうかと思えるかもしれませんが、ちゃんと動作します(検証機能については6.2でさらに詳しく説明します)。141文字以上の新規マイクロポストを投稿してみることで、動作していることを確かめられます。図 2.17に示したとおり、マイクロポストの内容が長すぎるというエラーメッセージがRailsによって表示されます(エラーメッセージの詳細については7.3.3で説明します)。

7
図 2.17: マイクロポストの作成に失敗した場合のエラーメッセージ

演習

  1. 先ほど2.3.1.1の演習でやったように、もう一度Contentに141文字以上を入力してみましょう。どのように振る舞いが変わったでしょうか?

2.3.3 ユーザーはたくさんマイクロポストを持っている

異なるデータモデル同士の関連付けは、Railsの強力な機能です。ここでは、1人のユーザーに対し複数のマイクロポストがあるとしましょう。UserモデルとMicropostモデルをそれぞれリスト 2.12リスト 2.13のように更新することでこの関連付けを表現できます。

リスト 2.12: 1人のユーザーに複数のマイクロポストがある。 app/models/user.rb
class User < ApplicationRecord
  has_many :microposts
end
リスト 2.13: 1つのマイクロポストは1人のユーザーにのみ属する。 app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  validates :content, length: { maximum: 140 }
end

この関連付けを図で表したものが図 2.18です。micropostsテーブルにはuser_idカラムを作成してあったので、それによってRailsとActive Recordがマイクロポストとユーザーを関連付けることが可能になっています。

7
図 2.18: マイクロポストとユーザーの関連付け

1314では、関連付けられたユーザーとマイクロポストを同時に表示し、Twitterのようなマイクロポストのフィードを作成する予定です。ここでは、Railsのコンソールを使って、ユーザーとマイクロポストの関連付けを確認するにとどめます。Railsのコンソールは、Railsアプリケーションを対話的に操作することができる便利なツールです。

まずはターミナルでrails consoleコマンドを入力します。続いてUser.firstと入力してデータベースから1人目のユーザー情報を取り出し、first_user変数に保存します(リスト 2.147ここではコンソールの終了まで実演するため、末尾にexitコマンドも追加してあります。exitコマンドの代わりに、CtrlキーとDキーを同時に押してコンソールを終了することもできます。8

リスト 2.14: Railsコンソールでアプリケーションの状態を調べてみる
$ rails console
>> first_user = User.first
User Load (0.1ms)  SELECT "users".* FROM "users" ORDER BY "users"."id"
ASC LIMIT ?  [["LIMIT", 1]]
 =>

>> first_user
 =>
#<User:0x00007ffbf1331658
 id: 1,
 name: "Michael Hartl",
 email: "michael@example.org ",
 created_at: Thu, 10 Mar 2022 00:23:31.441663000 UTC +00:00,
 updated_at: Thu, 10 Mar 2022 00:25:04.172206000 UTC +00:00>

#<User:0x00007ffbf1331658

>> first_user.microposts
  Micropost Load (0.2ms)  SELECT "microposts".* FROM "microposts"
  WHERE "microposts"."user_id" = ?  [["user_id", 1]]
 =>
[#<Micropost:0x00007ffbf16807a0
  id: 1,
  content: "First micropost!",
  user_id: 1,
  created_at: Thu, 10 Mar 2022 00:46:02.263125000 UTC +00:00,
  updated_at: Thu, 10 Mar 2022 00:46:02.263125000 UTC +00:00>,
 #<Micropost:0x00007ffbf1653a70
  id: 2,
  content: "Second micropost",
  user_id: 1,
  created_at: Thu, 10 Mar 2022 00:46:14.079131000 UTC +00:00,
  updated_at: Thu, 10 Mar 2022 00:46:14.079131000 UTC +00:00>]

>> micropost = first_user.microposts.first
 =>
#<Micropost:0x00007ffbf16807a0
...
>> micropost
 =>
#<Micropost:0x00007ffbf16807a0
 id: 1,
 content: "First micropost!",
 user_id: 1,
 created_at: Thu, 10 Mar 2022 00:46:02.263125000 UTC +00:00,
 updated_at: Thu, 10 Mar 2022 00:46:02.263125000 UTC +00:00>

>> micropost.user
 =>
#<User:0x00007ffbf1331658
 id: 1,post:0
 name: "Michael Hartl",
 email: "michael@example.org ",
 created_at: Thu, 10 Mar 2022 00:23:31.441663000 UTC +00:00,
 updated_at: Thu, 10 Mar 2022 00:25:04.172206000 UTC +00:00>

>> exit

リスト 2.14では多くのログが表示されていますが、こういったログを詳しく読み取ろうとする姿勢は「熟練」を身に付けるために欠かせない要素です(コラム 1.2)。ログには実行したSQL(Structured Query Language)や、結果として取得したRubyオブジェクトの情報が含まれています。少しだけ説明しましょう。

冒頭のfirst_user = User.firstを実行した後、リスト 2.14には次の2つが示されています。

(1)first_user.micropostsを実行することで、最初のユーザーが投稿したすべてのマイクロポストを取得します。つまりuser_idfirst_userのid(ここでは 1)と一致するマイクロポストを自動的にすべて出力しています。

(2)micropost.userを実行することで、ある特定の投稿を行ったユーザーを返します。

リスト 2.14に含まれるRubyの文法については4で、ユーザーとマイクロポストの関連付けは1314でそれぞれ詳しく説明するので、今は雰囲気を掴むだけで十分です。

演習

  1. ユーザーのshowページを編集し、ユーザーの最初のマイクロポストを表示してみましょう。同ファイル内の他のコードから文法を推測してみてください(コラム 1.2で紹介した技術の出番です)。うまく表示できたかどうか、/users/1にアクセスして確認してみましょう。
  2. リスト 2.15は、マイクロポストのContentが存在しているかどうかを検証するバリデーションです。マイクロポストが空でないことを検証できているかどうか、実際に試してみましょう(図 2.19のようになっていると成功です)。
  3. リスト 2.16(コードを書き込む)となっている箇所を書き換えて、Userモデルのnameとemailが存在していることを検証してみてください(図 2.20)。
リスト 2.15: マイクロポストのコンテンツが存在しているかどうかの確認 app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  validates :content, length: { maximum: 140 },
                      presence: true
end
7
図 2.19: Micropostモデルの存在確認バリデーションの結果
リスト 2.16: Userモデルに存在性のバリデーションを追加する app/models/user.rb
class User < ApplicationRecord
  has_many :microposts
  validates (コードを書き込む), presence: true    # (コードを書き込む)の中身を書き換えてください
  validates (コードを書き込む), presence: true    # (コードを書き込む)の中身を書き換えてください
end
7
図 2.20: Userモデルの存在性がうまく検証できた場合の結果

2.3.4 継承の階層

最後に、Toyアプリケーションで使っているRailsのコントローラとモデルのクラス階層について簡単に解説します。この節を理解するには、多少なりともオブジェクト指向プログラミング(OOP)の経験が必要です(特にクラスを理解している必要があります)。「まだクラスを知らない」という方も心配しないでください。4.4でクラスの概念についてじっくり解説するので、ここでの説明が理解できなくても問題ありません。

最初に、モデルの継承構造について説明します。リスト 2.17リスト 2.18を比較してみると、UserモデルとMicropostモデルはいずれも、ApplicationRecordというクラスを継承しています(Rubyでは継承関係を<記号で表現します)。また、ApplicationRecordクラスは、Active Recordが提供する基本クラス ActiveRecord::Base を継承しています。図 2.21は、このクラス間の関係をまとめたものです。このActiveRecord::Baseという基本クラスを継承したことによって、作成したモデルオブジェクトはデータベースにアクセスできるようになり、データベースのカラムをあたかもRubyの属性のように扱えるようになります。

リスト 2.17: Userクラスにおける継承 app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
end
リスト 2.18: Micropostクラスにおける継承 app/models/micropost.rb
class Micropost < ApplicationRecord
  .
  .
  .
end
7
図 2.21: UserモデルとMicropostモデルの継承階層

コントローラの継承構造も、モデルの継承構造と本質的には同じです。リスト 2.19リスト 2.20を比較してみると、UsersコントローラとMicropostsコントローラはいずれもApplicationControllerを継承しています。リスト 2.21をみると気づくように、ApplicationControllerActionController::Baseというクラスを継承していることがわかります。このクラスは、RailsのAction Packというライブラリが提供しているコントローラの基本クラスです。これらのクラス同士の関係を図 2.22に示します。

リスト 2.19: UsersControllerクラスにおける継承 app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
end
リスト 2.20: MicropostsControllerクラスにおける継承 app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  .
  .
  .
end
リスト 2.21: ApplicationControllerクラスにおける継承 app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  .
  .
  .
end
7
図 2.22: UsersコントローラとMicropostsコントローラにおける継承関係

モデルの継承関係と同様に、UsersコントローラもMicropostsコントローラも最終的にはActionController::Baseという基本クラスを継承しています。このため、どちらのコントローラもモデルオブジェクトの操作や、送られてくるHTTP requestのフィルタリング、ビューをHTMLとして出力するなどの多彩な機能を実行できるようになっています。Railsのコントローラは必ずApplicationControllerを継承しているので、Applicationコントローラで定義したルールは、アプリケーションのすべてのアクションに反映されます。例えば9.1では、ログインとログアウト用のヘルパーメソッドをサンプルアプリケーションのすべてのコントローラで利用できるようにしています。

これでMicropostsリソースの説明は終わりましたので、ここでリポジトリをGitHubに登録しましょう。

$ git status    #追加の前にこうやって状態を確認するのはよい習慣です
$ git add -A
$ git commit -m "Finish toy app"
$ git push

通常、Gitのコミットはなるべくこまめに行うようにし、更新をあまりためないことが望ましいのですが、この章の締めくくりとしてサイズの大きなコミットを1度だけ行うぐらいであれば問題ありません。

演習

  1. Applicationコントローラのファイルを開き、ApplicationControllerActionController::Baseを継承している部分のコードを探してみてください。
  2. ApplicationRecordActiveRecord::Baseを継承しているコードはどこにあるでしょうか? 先ほどの演習を参考に、探してみてください。(ヒント: コントローラと本質的には同じ仕組みなので、app/modelsディレクトリ内にあるファイルを調べてみましょう。)

2.4 最後に

非常に簡単ではありますが、ついにRailsアプリケーションを最後まで完成させました。この章で作成したToyアプリケーションには良い点もたくさんありますが、様々な弱点もあります。

長所

  • Rails全体を高度なレベルで概観できた
  • MVCモデルを紹介できた
  • RESTアーキテクチャーに初めて触れた
  • データモデルの作成を初めて行った
  • データベースを背後に持つWebアプリケーションを本番環境で動かした

短所

  • レイアウトもスタイルも設定されていない
  • “Home” や “About” といった静的なページがない
  • ユーザーがパスワードを設定できない
  • ユーザーが画像を投稿できない
  • ログインの仕組みがない
  • セキュリティのための仕組みがまったくない
  • ユーザーとマイクロポストの自動関連付けが行われていない
  • 「フォロワー(following)機能」や「フォロー中(followed)機能」がない
  • マイクロポストをフィードできない
  • 上記機能に対するテストがまだ無い

次の章以降では、本章で学んだ良い点を保ちつつ、弱点を1つ1つ克服していきます。

2.4.1 本章のまとめ

  • Scaffoldでコードを自動生成すると、Web上からデータモデルにアクセスし、やりとりできるようになる
  • Scaffoldは素早く開発するための機能で、学習用では無いため、ScafolldだけでRailsを理解するのは難しい
  • Railsでは、Webアプリケーションの構成にMVC(Model-View-Controller)というアークテクチャーを採用している
  • Railsでは、HTTP requestメソッドとその送信先(URL)に応じて、呼び出されるコントローラやアクションが決まる
  • Railsでは、データのバリデーション(validation)がサポートされており、データモデルの値に制限をかけられる
  • Railsには、データモデル同士を関連付けするための様々なメソッド(has_manyなど)が用意されている
  • Railsコンソールを使うと、コマンドラインからRailsアプリケーションを操作できる

Codespacesで無償プランの月の時間を使い切ってしまった場合、同様のIDEで進められるため、ヘルプページで紹介している「Visual Studio Code と Docker を使って開発する」方法がおすすめです。

2.5 Toyアプリケーションをデプロイする

本セクションでは、Toyアプリケーションをデプロイする方法について簡単に説明しますが、本番環境にデータベースをマイグレーションする方法は6で詳しく学ぶので、スキップしても構いません。雰囲気を掴んでみたい方は、以下の方法でデプロイを試すことができます。

まずは、設定ファイルを作ります。

$ touch bin/render-build.sh

このファイルに、リスト 2.22のビルドコマンドを設定します。

リスト 2.22: ビルドスクリプトを作成する bin/render-build.sh
#!/usr/bin/env bash
# exit on error
set -o errexit
bundle install
bundle exec rails assets:precompile
bundle exec rails assets:clean
bundle exec rails db:migrate

設定したらコミットしてGitHubにプッシュします。

$ git add -A
$ git commit -m "Add build script"
$ git push

ここで、1.5の時と同じように、Renderにtoy_app用のWeb Serviceを新たに作成します。2.1で作成したGitHubリポジトリと連携させたら、デプロイ時にRenderが先ほどの設定ファイルを読み込むように、Build Commandに./bin/render-build.shと入力します。環境変数RAILS_MASTER_KEYも忘れずに設定しておきましょう。

7
図 2.23: ビルドスクリプトを読み込ませる

最後に、“Create Web Service”をクリックすると、自動でデプロイが始まります9

1. マイクロポストは設計上短いのでstring型で十分だと思われるかもしれませんが、text型を使うことでより大きな柔軟性が得られます。実際のTwitterは、英語ツイートで140文字という制約を280文字にまで拡張しました。このことは、柔軟性がいかに重要であるかを示す見事な実例となっています。stringは255文字(\( 2^8-1 \))まで利用できるのが普通であり、これは140文字を収容するには十分すぎるほどですが、280文字を収容するには不足です。textを使えば、どちらの場合にも統一的に対応できるようになります。
2. scaffoldで指定する名前は、モデル名の命名の習慣に従って「単数形」にします。リソースやコントローラは「複数形」で表し、モデルは「単数形」で表します。したがって、ScaffoldではUsersではなくUserと指定します。
3. いくつかの文献では「ビューは(ApacheやNginxなどのWebサーバーを経由して)ブラウザにHTMLを直接返す」と説明しています。著者は、Railsの実際の実装とは無関係に、コントローラを軸にして情報の流れを捉えると分かりやすいと考えています。つまりビュー単体を切り離して理解しようとするのではなく、「情報の流れの中心にコントローラというハブがある」と考えると良いでしょう。
4. 論文の正式なタイトル: Fielding, Roy Thomas. Architectural Styles and the Design of Network-based Software Architectures. Doctoral dissertation, University of California, Irvine, 2000.
5. Userでscaffoldを実行した場合と同様に、scaffoldジェネレータではマイクロポストでもRailsモデルを単数形とする習慣に従います。実行したコマンドがgenerate Micropostと単数形になっていたのはこのためです。
6. scaffoldで生成した実際のコードにはリスト 2.9よりも多くの改行が追加されることがありますが、Rubyでは単なる改行は無視されるので問題ありません。
7. 実際のターミナル上では、Rubyのバージョンに応じてプロンプトが3.1.2 :001 >などと表示されることがありますが、Rubyのバージョンは今後変わるので、例では >>のようになっています。
8. このようなCtrlキーとDキーを同時に押す操作を「Ctrl-D」と表記します。またこの「D」は単に「Dキーを押す」という意味であり、大文字にする必要はありません。つまりCtrlキーを押すときに、Shiftキーも一緒に押す必要はありません。ほとんどのシステムでは、Ctrl-Dを押して終了することができます。
9. デプロイが失敗した場合、ヘルプページデプロイでつまづきやすいポイントセクションが参考になります。
前の章
第2章 Toyアプリケーション Rails 7 (第7版)
次の章