このガイドでは、Ruby on Rails(以下 Rails)を初めて設定して実行するまでを解説します。
このガイドの内容:
本ガイドは、Railsアプリケーションを構築したいと考えているRails初心者を対象にしています。読者にRailsの経験があることは前提としていません。
Railsとは、プログラミング言語「Ruby」の上で動作するWebアプリケーションフレームワークです。ただしプログラミング経験がまったくない人がいきなりRailsを学ぼうとすると、かなり大変な作業になるでしょう。オンラインで学べる洗練されたコンテンツはたくさんあるので、その中から以下をご紹介します。
いずれもよくできていますが中には古いものもあり、たとえば通常のRails開発で見かけるような最新の構文がカバーされていない可能性もあります。
Railsとは、プログラミング言語「Ruby」で書かれたWebアプリケーションフレームワークです。Railsは、あらゆる開発者がWebアプリケーション開発で必要となる作業やリソースを事前に想定することで、Webアプリケーションをより手軽に開発できるように設計されています。他の多くのWebアプリケーションフレームワークと比較して、アプリケーションを開発する際のコード量がより少なくて済むにもかかわらず、より多くの機能を実現できます。ベテラン開発者の多くが「RailsのおかげでWebアプリケーション開発がとても楽しくなった」と述べています。
Railsは「最善の開発方法は1つである」という、ある意味大胆な判断に基いて設計されています。何かを行うための最善の方法を1つ仮定して、それに沿った開発を全面的に支援します。言い換えれば、Railsで仮定されていない別の開発手法は行いにくくなります。この「Rails Way」、すなわち「Railsというレールに乗って開発する」手法を学んだ人は、開発の生産性が驚くほど向上することに気付くでしょう。逆に、レールに乗らずに従来の開発手法にこだわると、開発の楽しさが減ってしまうかもしれません。
Railsの哲学には、以下の2つの主要な基本理念があります。
本ガイドを最大限に活用するには、以下の手順を1つずつすべて実行するのがベストです。どの手順もサンプルアプリケーションを動かすのに必要なものであり、それ以外のコードや手順は不要です。
本ガイドの手順に沿って作業すれば、blogという名前の非常にシンプルなブログのRailsプロジェクトを作成できます。Railsアプリケーションを構築する前に、Rails本体をインストールしておいてください。
以下の例では、Unix系OSのプロンプトとして$記号が使われていますが、プロンプトはカスタマイズ可能なので環境によって異なることもあります。Windowsではc:\source_code>のように表示されます。
Railsをインストールする前に、必要な要件が自分のシステムで満たされているかどうかをチェックしましょう。少なくとも以下のソフトウェアが必要です。
訳注:GitHubが提供するクラウド開発環境『Codespaces』には、公式のRuby on Railsテンプレートが用意されています。Use this template ボタンから、ワンクリックでRailsを動かせるクラウド開発環境が手に入ります。(参考: GitHub Codespaces を利用する - Rails Girls)
ターミナル(コマンドプロンプトとも言います)ウィンドウを開いてください。macOSの場合、ターミナル(Terminal.app)という名前のアプリケーションを実行します。Windowsの場合は[スタート]メニューから[ファイル名を指定して実行]をクリックして'cmd.exe'と入力します。$で始まる記述はコマンド行なので、これらをコマンドラインに入力して実行します。次に以下を実行して、現在インストールされているRubyが最新バージョンであることを確認しましょう。
$ ruby -v ruby 3.0.1
RailsではRubyバージョン2.7.0以降が必須です。これより低いバージョン(2.3.7や1.8.7など)が表示された場合は、新たにRubyをインストールする必要があります。
RailsをWindowsにインストールする場合は、最初にRuby Installerをインストールしておく必要があります。
OS環境ごとのインストール方法について詳しくは、ruby-lang.orgを参照してください。
SQLite3データベースのインストールも必要です。 多くのUnix系OSには実用的なバージョンのSQLite3が同梱されています。Windowsの場合は、上述のRails InstalerでRailsをインストールするとSQLite3もインストールされます。その他の環境についてはSQLite3のインストール方法を参照してください。
$ sqlite3 --version
上を実行することでSQLite3のバージョンを確認できます。
Railsをインストールするには、gem installコマンドを実行します。このコマンドはRubyGemsによって提供されます。
$ gem install rails
以下のコマンドを実行することで、すべて正常にインストールできたかどうかを確認できます。
$ rails --version
"Rails 7.0.1"などのバージョンが表示されたら、次に進みましょう。
Railsには、ジェネレータというスクリプトが多数付属していて、特定のタスクを開始するために必要なものを自動的に生成してくれるので、楽に開発できます。その中から、新規アプリケーション作成用のジェネレータを使ってみましょう。ジェネレータを実行すればRailsアプリケーションの基本的なパーツが提供されるので、開発者が自分でこれらを作成する必要はありません。
ジェネレータを実行するには、ターミナルを開き、Railsファイルを作成したいディレクトリに移動して、以下を入力します。
$ rails new blog
これにより、Blogという名前のRails アプリケーションがblogディレクトリに作成され、Gemfileというファイルで指定されているgemファイルがbundle installコマンドによってインストールされます。
rails new --helpを実行すると、Railsアプリケーションビルダで使えるすべてのコマンドラインオプションを表示できます。
ブログアプリケーションを作成したら、そのフォルダ内に移動します。
$ cd blog
blogディレクトリの下には多数のファイルやフォルダが生成されており、これらがRailsアプリケーションを構成しています。このガイドではほとんどの作業をappディレクトリで行いますが、Railsが生成したファイルとフォルダについてここで簡単に説明しておきます。
| ファイル/フォルダ | 目的 |
|---|---|
| app/ | このディレクトリには、アプリケーションのコントローラ、モデル、ビュー、ヘルパー、メーラー、チャンネル、ジョブ、そしてアセットが置かれます。以後、本ガイドでは基本的にこのディレクトリを中心に説明を行います。 |
| bin/ | このディレクトリには、アプリケーションを起動するrailsスクリプトが置かれます。セットアップ・アップデート・デプロイに使うスクリプトファイルもここに置けます。 |
| config/ | このディレクトリには、アプリケーションの各種設定ファイル(ルーティング、データベースなど)が置かれます。詳しくはRails アプリケーションの設定項目 を参照してください。 |
| config.ru | アプリケーションの起動に使われるRackベースのサーバー用のRack設定ファイルです。Rackについて詳しくは、RackのWebサイトを参照してください。 |
| db/ | このディレクトリには、現在のデータベーススキーマと、データベースマイグレーションファイルが置かれます。 |
| Gemfile Gemfile.lock |
これらのファイルは、Railsアプリケーションで必要となるgemの依存関係を記述します。この2つのファイルはBundler gemで使われます。Bundlerについて詳しくはBundlerのWebサイトを参照してください。 |
| lib/ | このディレクトリには、アプリケーションで使う拡張モジュールが置かれます。 |
| log/ | このディレクトリには、アプリケーションのログファイルが置かれます。 |
| public/ | 静的なファイルやコンパイル済みアセットはここに置きます。このディレクトリにあるファイルは、外部(インターネット)にそのまま公開されます。 |
| Rakefile | このファイルは、コマンドラインから実行できるタスクを探索して読み込みます。このタスク定義は、Rails全体のコンポーネントに対して定義されます。独自のRakeタスクを定義したい場合は、Rakefileに直接書くと権限が強すぎるので、なるべくlib/tasksフォルダの下にRake用のファイルを追加してください。 |
| README.md | アプリケーションの概要を簡潔に説明するマニュアルをここに記入します。このファイルにはアプリケーションの設定方法などを記入し、これさえ読めば誰でもアプリケーションを構築できるようにしておきましょう。 |
| storage/ | このディレクトリには、Disk Serviceで用いるActive Storageファイルが置かれます。詳しくはActive Storageの概要を参照してください。 |
| test/ | このディレクトリには、単体テストやフィクスチャなどのテスト関連ファイルを置きます。テストについて詳しくはRailsアプリケーションをテストするを参照してください。 |
| tmp/ | このディレクトリには、キャッシュやpidなどの一時ファイルが置かれます。 |
| vendor/ | サードパーティ製コードはすべてこのディレクトリに置きます。通常のRailsアプリケーションの場合、外部のgemファイルがここに置かれます。 |
| .gitattributes | このファイルは、gitリポジトリ内の特定のパスについてメタデータを定義します。このメタデータは、gitや他のツールで振る舞いを拡張できます。詳しくはgitattributesドキュメントを参照してください。 |
| .gitignore | Gitに登録しないファイル(またはパターン)をこのファイルで指定します。Gitにファイルを登録しない方法について詳しくはGitHub - Ignoring filesを参照してください。 |
| .ruby-version | このファイルには、デフォルトのRubyバージョンが記述されています。 |
手始めに、画面に何かテキストを表示してみましょう。そのためには、Railsアプリケーションサーバーを起動しなくてはなりません。
先ほど作成したRailsアプリケーションは、既に実行可能な状態になっています。Webアプリケーションを開発用のPCで実際に動かしてこのことを確かめてみましょう。blogディレクトリに移動し、以下のコマンドを実行します。
$ bin/rails server
Windowsの場合は、binフォルダの下にあるスクリプトをRubyインタープリタに直接渡さなければなりません(例: ruby bin\rails server)
JavaScriptアセットの圧縮にはJavaScriptランタイムが必要です。JavaScriptランタイムが環境にない場合は、起動時にexecjsエラーが発生します。macOSやWindowsにはJavaScriptランタイムが同梱されています。therubyrhinoはJRubyユーザー向けに推奨されているランタイムであり、JRuby環境下ではデフォルトでアプリケーションのGemfileに追加されます。サポートされているランタイムについて詳しくはExecJSを参照してください。
Railsで起動されるWebサーバーは、Railsにデフォルトで付属しているPumaです。Webアプリケーションが実際に動作しているところを確認するには、ブラウザを開いて http://localhost:3000 を表示してください。以下のようなRailsのデフォルト情報ページが表示されます。

Webサーバーを停止するには、実行されているターミナルのウィンドウでCtrl + Cキーを押します。なお、development環境ではファイルに変更を加えればサーバーが自動的に変更を反映するので、サーバーの再起動は通常は不要です。
Railsの起動ページは、新しいRailsアプリケーションの「スモークテスト」として使えます。このページが表示されれば、サーバーが正常に動作していることが確認できます。
Railsで「Hello」と表示するには、少なくとも「ルーティング」「コントローラ」「ビュー」が必要です。ルーティングは、リクエストをどのコントローラに振り分けるかを決定します。コントローラは、アプリケーションに対する特定のリクエストを受け取って処理します。コントローラの アクション は、リクエストを扱うのに必要な処理を実行します。ビューは、データを好みの書式で表示します。
実装の面から見れば、ルーティングはRubyのDSL(Domain-Specific Language) で記述されたルールです。コントローラはRubyのクラスで、そのクラスのpublicメソッドがアクションです。ビュー はテンプレートで、多くの場合HTMLの中にRubyコードも含んでいます。
それではルーティングを1個追加してみましょう。config/routes.rbを開き、Rails.application.routes.drawブロックの冒頭に以下を書きます。
Rails.application.routes.draw do get "/articles", to: "articles#index" # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # ... end
上で宣言したルーティングは、GET /articlesリクエストをArticlesControllerのindexアクションに対応付けます。
ArticlesControllerとindexアクションを作成するには、コントローラ用のジェネレータを実行します(上で既に適切なルーティングを追加したので、ここでは--skip-routesオプションでルーティングの追加をスキップします)。
$ bin/rails generate controller Articles index --skip-routes
これで、必要なファイルをRailsが生成します。
create app/controllers/articles_controller.rb
invoke erb
create app/views/articles
create app/views/articles/index.html.erb
invoke test_unit
create test/controllers/articles_controller_test.rb
invoke helper
create app/helpers/articles_helper.rb
invoke test_unit
この中で最も重要なのは、app/controllers/articles_controller.rbというコントローラファイルです。このファイルを見てみましょう。
class ArticlesController < ApplicationController def index end end
indexアクションは空です。あるアクションがビューを明示的にレンダリングしない場合(またはHTTPレスポンスをトリガーしない場合)、Railsはその「コントローラ名」と「アクション名」にマッチするビューを自動的にレンダリングします。これは「設定より規約」の例です。ビューはapp/viewsの下に配置されるので、indexアクションはデフォルトでapp/views/articles/index.html.erbをレンダリングします。
それではapp/views/articles/index.html.erbを開き、中身を以下に置き換えましょう。
<h1>Hello, Rails!</h1>
コントローラ用のジェネレータを実行するためにWebサーバーを停止していた場合は、再びbin/rails serverを実行します。ブラウザでhttp://localhost:3000/articlesを開くと、「Hello, Rails!」というテキストが表示されます。
この時点ではトップページhttp://localhost:3000にまだRailsのデフォルト起動画面が表示されているので、http://localhost:3000を開いたときにも「Hello, Rails!」が表示されるようにしてみましょう。これを行うには、アプリケーションのrootパスをこのコントローラとアクションに対応付けます。
それではconfig/routes.rbを開き、Rails.application.routes.drawブロックを以下のように書き換えてみましょう。
Rails.application.routes.draw do root "articles#index" get "/articles", to: "articles#index" end
ブラウザでhttp://localhost:3000を開くと、「Hello, Rails!」テキストが表示されるはずです。これで、rootルーティングがArticlesControllerのindexアクションに対応付けられたことを確認できました。
ルーティングについて詳しくはRailsのルーティングを参照してください。
Railsアプリケーションでは、アプリケーションコードを読み込むのにrequireを書く必要はありません。
おそらくお気づきかもしれませんが、ArticlesControllerはApplicationControllerを継承しているにもかかわらず、app/controllers/articles_controller.rbには以下のような記述がどこにもありません。
require "application_controller" # 実際には書いてはいけません
Railsでは、アプリケーションのクラスやモジュールはどこでも利用できるようになっているので、上のようにrequireを書く必要はありませんし、app/ディレクトリの下で何かを読み込むためにrequireを書いてはいけません。この機能は「オートロード(autoloading: 自動読み込み)」と呼ばれています。詳しくはガイドの『定数の自動読み込みと再読み込み』を参照してください。
requireを書く必要があるのは、以下の2つの場合だけです。
lib/ディレクトリの下にあるファイルを読み込む場合Gemfileでrequire: falseが指定されているgem依存を読み込む場合これまでに、「ルーティング」「コントローラ」「アクション」「ビュー」について解説しました。これらはMVC(Model-View-Controller)と呼ばれるパターンに沿ったWebアプリケーションの典型的な構成要素です。MVCは、アプリケーションの責務を分割して理解しやすくするデザインパターンです。Railsは、このデザインパターンに従う規約になっています。
コントローラと、それに対応して動作するビューを作成したので、次の構成要素である「モデル」を生成しましょう。
モデル(model)とは、データを表現するためのRubyクラスです。モデルは、Active Recordと呼ばれるRailsの機能を介して、アプリケーションのデータベースとやりとりできます。
モデルを定義するには、以下のようにモデル用のジェネレータを実行します。
$ bin/rails generate model Article title:string body:text
モデル名は常に英語の「単数形」で表記してください。理由は、インスタンス化されたモデルは1件のデータレコードを表すからです。この規約を覚えるために、モデルのコンストラクタを呼び出すときにArticle.new(...)と単数形で書くことはあっても、複数形のArticles.new(...)は書かないことを考えてみるとよいでしょう。
ジェネレータを実行すると、以下のようにいくつものファイルが作成されます。
invoke active_record create db/migrate/<タイムスタンプ>_create_articles.rb create app/models/article.rb invoke test_unit create test/models/article_test.rb create test/fixtures/articles.yml
生成されたファイルのうち、マイグレーションファイル(db/migrate/<タイムスタンプ>_create_articles.rb)とモデルファイル(app/models/article.rb)の2つを中心に解説します。
マイグレーション(migration)は、アプリケーションのデータベース構造を変更するときに使われる機能です。RailsアプリケーションのマイグレーションはRubyコードで記述するので、データベースの種類を問わずにマイグレーションを実行できます。
db/migrate/ディレクトリの下に生成されたマイグレーションファイルを開いてみましょう。
class CreateArticles < ActiveRecord::Migration[7.0] def change create_table :articles do |t| t.string :title t.text :body t.timestamps end end end
create_tableメソッド呼び出しは、articlesテーブルの構成方法を指定します。create_tableメソッドは、デフォルトでidカラムを「オートインクリメントの主キー」として追加します。つまり、テーブルで最初のレコードのidは1、次のレコードのidは2、というように自動的に増加します。
create_tableのブロック内には、titleとbodyという2つのカラムが定義されています。これらのカラムは、先ほど実行したbin/rails generate model Article title:string body:textコマンドで指定したので、ジェネレータによって自動的に追加されました。
ブロックの末尾行はt.timestampsメソッドを呼び出しています。これはcreated_atとupdated_atという2つのカラムを追加で定義します。後述するように、これらのカラムはRailsによって自動で管理されるので、モデルオブジェクトを作成・更新すると、これらのカラムに値が自動で設定されます。
それでは以下のコマンドでマイグレーションを実行しましょう。
$ bin/rails db:migrate
マイグレーションコマンドを実行すると、データベース上にテーブルが作成されます。
== CreateArticles: migrating =================================== -- create_table(:articles) -> 0.0018s == CreateArticles: migrated (0.0018s) ==========================
マイグレーションについて詳しくは、Active Recordマイグレーションを参照してください。
これで、モデルを介してテーブルとやりとりできるようになりました。
Railsのコンソール機能を使って、モデルで少し遊んでみましょう。Railsコンソールは、Rubyのirbと同様の対話的コーディング環境ですが、irbと違うのは、Railsとアプリケーションコードも自動的に読み込まれる点です。
以下を実行してRailsコンソールを起動しましょう。
$ bin/rails console
以下のようなirbプロンプトが表示されるはずです。
Loading development environment (Rails 7.0.1) irb(main):001:0>
このプロンプトで、先ほど作成したArticleオブジェクトを以下のように初期化できます。
irb> article = Article.new(title: "Hello Rails", body: "I am on Rails!")
ここで重要なのは、このオブジェクトは単に初期化されただけの状態であり、まだデータベースに保存されていないことです。つまり、このオブジェクトはこのコンソールでしか利用できません(コンソールを終了すると消えてしまいます)。オブジェクトをデータベースに保存するには、saveメソッドを呼び出さなくてはなりません。
irb> article.save
(0.1ms) begin transaction
Article Create (0.4ms) INSERT INTO "articles" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "Hello Rails"], ["body", "I am on Rails!"], ["created_at", "2020-01-18 23:47:30.734416"], ["updated_at", "2020-01-18 23:47:30.734416"]]
(0.9ms) commit transaction
=> true
上の出力には、INSERT INTO "Article" ...というデータベースクエリも表示されています。これは、その記事がテーブルにINSERT(挿入)されたことを示しています。そして、articleオブジェクトをもう一度表示すると、先ほどと何かが変わっていることがわかります。
irb> article => #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">
オブジェクトにid、created_at、updated_atという属性(attribute)が設定されています。先ほどオブジェクトをsaveしたときにRailsが追加してくれたのです。
この記事をデータベースから取り出したければ、そのモデルでfindメソッドを呼び出し、その記事のidを引数として渡します。
irb> Article.find(1) => #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">
データベースに保存されているすべての記事を取り出すには、そのモデルでallメソッドを呼び出します。
irb> Article.all => #<ActiveRecord::Relation [#<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">]>
このメソッドが返すActiveRecord::Relationオブジェクトは、一種の超強力な配列と考えるとよいでしょう。
モデルについて詳しくは、Active Record の基礎とActive Record クエリインターフェイスを参照してください。
モデルは、MVCというパズルの最後のピースです。次は、これらのピースをつなぎ合わせてみましょう。
app/controllers/articles_controller.rbコントローラを再度開いて、データベースからすべての記事を取り出せるようindexアクションを変更します。
class ArticlesController < ApplicationController def index @articles = Article.all end end
コントローラ内のインスタンス変数(@で始まる変数)は、ビューからも参照できます。つまり、app/views/articles/index.html.erbで@articlesと書くと、このインスタンス変数を参照できるということです。このファイルを開いて、以下のように書き換えます。
<h1>Articles</h1> <ul> <% @articles.each do |article| %> <li> <%= article.title %> </li> <% end %> </ul>
上記のコードでは、HTMLの中にERB(Embedded Ruby)も書かれています。ERBとは、ドキュメントに埋め込まれたRubyコードを評価するテンプレートシステムのことです。
ここでは、<% %>と<%= %>という2種類のERBタグが使われています。<% %>タグは「この中のRubyコードを評価する」という意味です。<%= %>タグは「この中のRubyコードを評価し、返された値を出力する」という意味です。
これらのERBタグの中には、通常のRubyプログラムで書けるコードなら何でも書けますが、読みやすさのため、ERBタグにはなるべく短いコードを書く方がよいでしょう。
上のコードでは、@articles.eachが返す値は画面に出力したくないので<% %> で囲んでいますが、(各記事の)article.title が返す値は画面に出力したいので<%= %> で囲んでいます。
ブラウザでhttp://localhost:3000を開くと最終的な結果を確認できます(bin/rails serverを実行しておくことをお忘れなく)。このときの動作は以下のようになります。
GET http://localhost:3000というリクエストをサーバーに送信する。ArticlesControllerのindexアクションに割り当てる。indexアクションは、Articleモデルを用いてデータベースからすべての記事を取り出す。app/views/articles/index.html.erbビューをレンダリングする。これでMVCのピースがすべてつながり、コントローラに最初のアクションができました。このまま次のアクションを作ってみましょう。
ほぼすべてのWebアプリケーションは、何らかの形でCRUD(Create、Read、Update、Delete)操作を行います。Webアプリケーションで行われる処理の大半はCRUDです。Railsフレームワークはこの点を認識しており、CRUDを行うコードをシンプルに書ける機能を多数備えています。
それでは、アプリケーションに機能を追加してこれらの機能を探ってみましょう。
現在のビューは、データベースにある記事をすべて表示します。今度は、1件の記事のタイトルと本文を表示するビューを追加してみましょう。
手始めに、コントローラの新しいアクションに対応付けられる新しいルーティングを1個追加します(アクションはこの後で追加します)。config/routes.rbを開き、ルーティングの末尾に以下のように追加します。
Rails.application.routes.draw do root "articles#index" get "/articles", to: "articles#index" get "/articles/:id", to: "articles#show" end
追加したルーティングもgetルーティングですが、パスの末尾に:idが追加されている点が異なります。これはルーティングのパラメータ(parameter)を指定します。ルーティングパラメータは、リクエストのパスに含まれる特定の値をキャプチャして、その値をparamsというハッシュに保存します。paramsはコントローラのアクションでもアクセスできます。たとえばGET http://localhost:3000/articles/1というリクエストを扱う場合、:idで1がキャプチャされ、ArticlesControllerのshowアクションでparams[:id]と書くとアクセスできます。
それでは、showアクションをapp/controllers/articles_controller.rbのindexアクションの下に追加しましょう。
class ArticlesController < ApplicationController def index @articles = Article.all end def show @article = Article.find(params[:id]) end end
このshowアクションでは、前述のArticle.findメソッドを呼び出すときに、ルーティングパラメータでキャプチャしたidを渡しています。返された記事は@articleインスタンス変数に保存されているので、ビューから参照できます。showアクションは、デフォルトでapp/views/articles/show.html.erbをレンダリングします。
今度はapp/views/articles/show.html.erbを作成し、以下のコードを書きます。
<h1><%= @article.title %></h1> <p><%= @article.body %></p>
これで、http://localhost:3000/articles/1を開くと記事が1件表示されるようになりました。
仕上げとして、記事ページを開くときによく使われる方法を追加しましょう。app/views/articles/index.html.erbにリスト表示される記事タイトルに、その記事へのリンクを追加します。
<h1>Articles</h1> <ul> <% @articles.each do |article| %> <li> <a href="/articles/<%= article.id %>"> <%= article.title %> </a> </li> <% end %> </ul>
ここまでにCRUDのR(Read)をやってみました。最終的にCRUDのC(Create)、U(Update)、D(Delete)も行います。既にお気づきかと思いますが、CRUDを追加するときは「ルーティングを追加する」「コントローラにアクションを追加する」「ビューを追加する」という3つの作業を行います。「ルーティング」「コントローラのアクション」「ビュー」がどんな組み合わせになっても、エンティティに対するCRUD操作に落とし込まれます。こうしたエンティティはリソース(resource)と呼ばれます。たとえば、このアプリケーションの場合は「1件の記事」が1個のリソースに該当します。
Railsはresourcesというルーティングメソッドを提供しており、メソッド名が複数形であることからわかるように、リソースのコレクション(collection: 集まり)を対応付けるのによく使われるルーティングをすべて対応付けてくれます。C(Create)、U(Update)、D(Delete)に進む前に、config/routes.rbでこれまでgetメソッドで書かれていたルーティングをresourcesで書き換えましょう。
Rails.application.routes.draw do root "articles#index" resources :articles end
ルーティングがどのように対応付けられているかを表示するには、bin/rails routesコマンドが使えます(訳注: Rails 7では以下のルーティングの下にTurboやAction MailboxやActive Storageなどのルーティングも表示されますが、ここでは無視して構いません)。
$ bin/rails routes Prefix Verb URI Pattern Controller#Action root GET / articles#index articles GET /articles(.:format) articles#index new_article GET /articles/new(.:format) articles#new article GET /articles/:id(.:format) articles#show POST /articles(.:format) articles#create edit_article GET /articles/:id/edit(.:format) articles#edit PATCH /articles/:id(.:format) articles#update DELETE /articles/:id(.:format) articles#destroy
resourcesメソッドは、_urlで終わる「URL」ヘルパーメソッドと、_pathで終わる「パス」ヘルパーメソッドも自動的に設定します。パスヘルパーを使うことで、コードが特定のルーティング設定に依存することを避けられます。Prefixカラムの値の末尾には、パスヘルパーによって_urlや_pathといったサフィックスが追加されます。たとえば、記事を1件渡されると、article_pathヘルパーは"/articles/#{article.id}"を返します。このパスヘルパーを用いると、app/views/articles/index.html.erbのリンクを簡潔な形に書き直せます。
<h1>Articles</h1> <ul> <% @articles.each do |article| %> <li> <a href="<%= article_path(article) %>"> <%= article.title %> </a> </li> <% end %> </ul>
しかし、link_toヘルパーを用いると<a>タグが不要になるので、さらに便利です。link_toヘルパーの第1引数はリンクテキスト、第2引数はリンク先です。第2引数にモデルオブジェクトを渡すと、link_toが適切なパスヘルパーを呼び出してオブジェクトをパスに変換します。たとえば、link_toにarticleを渡すとarticle_pathというパスヘルパーが呼び出されます。これを用いると、app/views/articles/index.html.erbは以下のように書き換えられます。
<h1>Articles</h1> <ul> <% @articles.each do |article| %> <li> <%= link_to article.title, article %> </li> <% end %> </ul>
すっきりしましたね!
ルーティングについて詳しくはRailsのルーティングを参照してください。
次はCRUDのC(Create)です。典型的なWebアプリケーションでは、リソースを1個作成するのに複数のステップを要します。最初にユーザーがフォーム画面をリクエストします。次にユーザーがそのフォームに入力して送信します。エラーが発生しなかった場合はリソースが作成され、リソース作成に成功したことを何らかの形で表示します。エラーが発生した場合はフォーム画面をエラーメッセージ付きで再表示し、フォーム送信の手順を繰り返します。
Railsアプリケーションでは、通常これらのステップを実現するときにnewアクションとcreateアクションを組み合わせて扱います。それでは2つのアクションをapp/controllers/articles_controller.rbのshowアクションの下に典型的な実装として追加してみましょう。
class ArticlesController < ApplicationController def index @articles = Article.all end def show @article = Article.find(params[:id]) end def new @article = Article.new end def create @article = Article.new(title: "...", body: "...") if @article.save redirect_to @article else render :new, status: :unprocessable_entity end end end
newアクションは、新しい記事を1件インスタンス化しますが、データベースには保存しません。インスタンス化された記事は、ビューでフォームをビルドするときに使われます。newアクションを実行すると、app/views/articles/new.html.erb(この後作成します)がレンダリングされます。
createアクションは、タイトルと本文を持つ新しい記事をインスタンス化し、データベースへの保存を試みます。記事の保存に成功すると、その記事のページ("http://localhost:3000/articles/#{@article.id}")にリダイレクトします。記事の保存に失敗した場合は、app/views/articles/new.html.erbに戻ってフォームを再表示し、Turboが正常に動作するようにステータスコード422 Unprocessable Entityを返します(unprocessable_entity)。なお、このときの記事タイトルと本文にはダミーの値が使われます。これらはフォームが作成された後でユーザーが変更することになります。
redirect_toメソッドを使うとブラウザで新しいリクエストが発生しますが、renderメソッドは指定のビューを現在のリクエストとしてレンダリングします。ここで重要なのは、redirect_toメソッドはデータベースやアプリケーションのステートが変更された「後で」呼び出すべきであるという点です。ステートが変更される前にredirect_toを呼び出すと、ユーザーがブラウザをリロードしたときに同じリクエストが再送信され、変更が重複してしまいます。
ここではRailsのフォームビルダー(form builder)という機能を使います。フォームビルダーを使えば、最小限のコードを書くだけで設定がすべてできあがったフォームを表示でき、かつRailsの規約に沿うことができます。
それではapp/views/articles/new.html.erbを作成して以下のコードを書き込みましょう。
<h1>New Article</h1> <%= form_with model: @article do |form| %> <div> <%= form.label :title %><br> <%= form.text_field :title %> </div> <div> <%= form.label :body %><br> <%= form.text_area :body %> </div> <div> <%= form.submit %> </div> <% end %>
form_withヘルパーメソッドは、フォームビルダー(ここではform)をインスタンス化します。form_withのブロック内でフォームビルダーのlabelやtext_fieldといったメソッドを呼び出すと、適切なフォーム要素が出力されます。
form_withを呼び出したときの出力結果は以下のようになります。
<form action="/articles" accept-charset="UTF-8" method="post"> <input type="hidden" name="authenticity_token" value="..."> <div> <label for="article_title">Title</label><br> <input type="text" name="article[title]" id="article_title"> </div> <div> <label for="article_body">Body</label><br> <textarea name="article[body]" id="article_body"></textarea> </div> <div> <input type="submit" name="commit" value="Create Article" data-disable-with="Create Article"> </div> </form>
フォームビルダーについて詳しくは、Action View フォームヘルパーを参照してください。
送信されたフォームのデータはparamsハッシュに保存され、ルーティングパラメータも同様にキャプチャされます。つまりcreateアクションでは、params[:article][:title]を用いると送信された記事タイトルにアクセスでき、params[:article][:body]を用いると送信された記事本文にアクセスできます。こうした値を個別にArticle.newに渡すことも一応可能ですが、値の数が増えれば増えるほどコードが煩雑になり、コーディング中のミスも増えます。
そこで、さまざまな値を個別に渡すのではなく、それらの値を含む1個のハッシュを渡します。しかしその場合も、ハッシュ内でどのような値が許されているかを厳密に指定しなければなりません。これを怠ると、悪意のあるユーザーがブラウザ側でフィールドをこっそり追加して、機密データを上書きする可能性が生じるので危険です。ただし実際には、params[:article]をフィルタなしでArticle.newに直接渡すと、RailsがForbiddenAttributesErrorエラーを出してこの問題を警告するようになっています。そこで、RailsのStrong Parametersという機能を用いてparamsをフィルタすることにします。ここで言うstrongとは、paramsを強く型付けする(strong typing)とお考えください。
それでは、app/controllers/articles_controller.rbの末尾に article_paramsというprivateメソッドを追加し、paramsをフィルタしましょう。さらに、createアクションでこのメソッドを使うように変更します。
class ArticlesController < ApplicationController def index @articles = Article.all end def show @article = Article.find(params[:id]) end def new @article = Article.new end def create @article = Article.new(article_params) if @article.save redirect_to @article else render :new, status: :unprocessable_entity end end private def article_params params.require(:article).permit(:title, :body) end end
Strong Parametersについて詳しくは、Action Controller の概要 § Strong Parametersを参照してください。
これまで見てきたように、リソースの作成は単独のステップではなく、複数のステップで構成されています。その中には、無効なユーザー入力を適切に処理することも含まれます。Railsには、無効なユーザー入力を処理するためにバリデーション(validation: 検証)という機能が用意されています。バリデーションとは、モデルオブジェクトを保存する前に自動的にチェックするルールのことです。チェックに失敗した場合は保存を中止し、モデルオブジェクトの errors 属性に適切なエラーメッセージが追加されます。
それでは、app/models/article.rbモデルにバリデーションをいくつか追加してみましょう。
class Article < ApplicationRecord validates :title, presence: true validates :body, presence: true, length: { minimum: 10 } end
1個目のバリデーションは、「titleの値が必ず存在しなければならない」ことを宣言しています。titleは文字列なので、titleにはホワイトスペース(スペース文字、改行、Tabなど)以外の文字が1個以上含まれていなければならないという意味になります。
2個目のバリデーションも、「bodyの値が必ず存在しなければならない」ことを宣言しています。さらに、bodyの値は10文字以上でなければならないことも宣言しています。
title属性やbody属性がどこで定義されているかが気になる方へ: Active Recordは、テーブルのあらゆるカラムごとにモデル属性を自動的に定義するので、モデルファイル内でこれらの属性を宣言する必要はありません。
バリデーションを追加したので、今度はapp/views/articles/new.html.erbを変更してtitleやbodyのエラーメッセージが表示されるようにしましょう。
<h1>New Article</h1> <%= form_with model: @article do |form| %> <div> <%= form.label :title %><br> <%= form.text_field :title %> <% @article.errors.full_messages_for(:title).each do |message| %> <div><%= message %></div> <% end %> </div> <div> <%= form.label :body %><br> <%= form.text_area :body %><br> <% @article.errors.full_messages_for(:body).each do |message| %> <div><%= message %></div> <% end %> </div> <div> <%= form.submit %> </div> <% end %>
full_messages_forメソッドは、指定の属性に対応するわかりやすいエラーメッセージを含む配列を1個返します。その属性でエラーが発生していない場合、配列は空になります。
以上の追加がバリデーションでどのように動くかを理解するために、コントローラのnewアクションとcreateアクションをもう一度見てみましょう。
def new @article = Article.new end def create @article = Article.new(article_params) if @article.save redirect_to @article else render :new, status: :unprocessable_entity end end
http://localhost:3000/articles/newをブラウザで表示すると、GET /articles/newリクエストはnewアクションに対応付けられます。newアクションは@articleを保存しないので、この時点ではバリデーションは実行されず、エラーメッセージも表示されません。
このフォームを送信すると、POST /articlesリクエストはcreateアクションに対応付けられます。createアクションは@articleを保存しようとするので、バリデーションが実行されます。バリデーションのいずれかが失敗すると、@articleは保存されず、レンダリングされたapp/views/articles/new.html.erbにエラーメッセージが表示されます。
バリデーションについて詳しくは、Active Record バリデーションを参照してください。バリデーションのエラーメッセージについてはActive Record バリデーション § バリデーションエラーに対応するを参照してください。
これで、ブラウザで http://localhost:3000/articles/new を表示すると記事を1件作成できるようになりました。仕上げに、app/views/articles/index.html.erbページの末尾からこの作成ページへのリンクを追加しましょう。
<h1>Articles</h1> <ul> <% @articles.each do |article| %> <li> <%= link_to article.title, article %> </li> <% end %> </ul> <%= link_to "New Article", new_article_path %>
ここまでで、CRUDのうちCとRを実現しました。今度はUの部分、つまり更新を実装してみましょう。リソースの更新は、ステップが複数あるという点でリソースの作成と非常に似ています。最初に、ユーザーはデータを編集するフォームをリクエストします。次に、ユーザーがフォームにデータを入力して送信します。エラーが発生しなければ、リソースは更新されます。エラーが発生した場合はフォームをエラーメッセージ付きで再表示し、同じことを繰り返します。
更新のステップは、コントローラのeditアクションとupdateアクションで扱うのが慣例です。それでは、app/controllers/articles_controller.rbのcreateアクションの下にこれらのアクションの典型的な実装を追加してみましょう。
class ArticlesController < ApplicationController def index @articles = Article.all end def show @article = Article.find(params[:id]) end def new @article = Article.new end def create @article = Article.new(article_params) if @article.save redirect_to @article else render :new, status: :unprocessable_entity end end def edit @article = Article.find(params[:id]) end def update @article = Article.find(params[:id]) if @article.update(article_params) redirect_to @article else render :edit, status: :unprocessable_entity end end private def article_params params.require(:article).permit(:title, :body) end end
更新に用いるeditアクションおよびupdateアクションが、作成に用いるnewアクションおよびcreateとほとんど同じである点にご注目ください。
editアクションはデータベースから記事を取得して@articleに保存し、フォームを作成するときに使えるようにします。editアクションは、デフォルトでapp/views/articles/edit.html.erbをレンダリングします。
updateアクションはデータベースから記事を(再)取得し、
article_paramsでフィルタリングされた送信済みのフォームデータで更新を試みます。
バリデーションが失敗せずに更新が成功した場合、ブラウザを更新後の記事ページにリダイレクトします。更新に失敗した場合はapp/views/articles/edit.html.erbをレンダリングし、同じフォームをエラーメッセージ付きで再表示します。
editで使うフォームの表示は、newで使うフォームの表示と同じに見えます。さらに、Railsのフォームビルダーとリソースフルルーティングのおかげで、コードも同じになっています。フォームビルダーは、モデルオブジェクトが既に保存されている場合はedit用のフォームを、モデルオブジェクトが保存されていない場合はnew用のフォームを自動的に構成するので、状況に応じて適切なリクエストを行えます。
どちらのフォームにも同じコードが使われているので、パーシャル(partial: 部分テンプレートとも呼ばれます)と呼ばれる共有ビューにまとめることにします。以下の内容で app/views/articles/_form.html.erb を作成してみましょう。
<%= form_with model: article do |form| %> <div> <%= form.label :title %><br> <%= form.text_field :title %> <% article.errors.full_messages_for(:title).each do |message| %> <div><%= message %></div> <% end %> </div> <div> <%= form.label :body %><br> <%= form.text_area :body %><br> <% article.errors.full_messages_for(:body).each do |message| %> <div><%= message %></div> <% end %> </div> <div> <%= form.submit %> </div> <% end %>
上記のコードはapp/views/articles/new.html.erbのフォームと同じですが、すべての@articleをarticleに置き換えてある点にご注目ください。パーシャルのコードは共有されるので、特定のインスタンス変数に依存しないようにするのがベストプラクティスです(コントローラのアクションで設定されるインスタンス変数に依存すると、他で使い回すときに不都合が生じます)。代わりに、記事をローカル変数としてパーシャルに渡します。
render でパーシャルを使うために、app/views/articles/new.html.erbを以下の内容で置き換えてみましょう。
<h1>New Article</h1> <%= render "form", article: @article %>
パーシャルのファイル名は冒頭にアンダースコア_を必ず付けなければなりません(例: _form.html.erb)。ただし、レンダリングでパーシャルを参照するときはアンダースコアを付けません(例: render "form")。
続いて、同じ要領で以下の内容のapp/views/articles/edit.html.erbも作ってみましょう。
<h1>Edit Article</h1> <%= render "form", article: @article %>
パーシャルについて詳しくは、レイアウトとレンダリング § パーシャルを使うを参照してください。
これで、記事のeditページ(http://localhost:3000/articles/1/editなど)にアクセスして記事を更新できるようになりました。最後に、app/views/articles/show.html.erb の末尾に以下のようなeditページへのリンクを追加してみましょう。
<h1><%= @article.title %></h1> <p><%= @article.body %></p> <ul> <li><%= link_to "Edit", edit_article_path(@article) %></li> </ul>
いよいよCRUDのDまで到達しました。リソースの削除はリソースの作成や更新よりもシンプルなので、必要なのは削除用のルーティングとコントローラのアクションだけです。削除用のルーティングは、DELETE /articles/:idリクエストをArticlesControllerの destroyアクションに対応付けます。
それでは、app/controllers/articles_controller.rbのupdateアクションの下に典型的なdestroyアクションを追加してみましょう。
class ArticlesController < ApplicationController def index @articles = Article.all end def show @article = Article.find(params[:id]) end def new @article = Article.new end def create @article = Article.new(article_params) if @article.save redirect_to @article else render :new, status: :unprocessable_entity end end def edit @article = Article.find(params[:id]) end def update @article = Article.find(params[:id]) if @article.update(article_params) redirect_to @article else render :edit, status: :unprocessable_entity end end def destroy @article = Article.find(params[:id]) @article.destroy redirect_to root_path, status: :see_other end private def article_params params.require(:article).permit(:title, :body) end end
destroyアクションは、データベースから記事を取得してdestroyメソッドを呼び出しています。次にブラウザをステータスコード303 See Otherでrootパスにリダイレクトします。
rootパスにリダイレクトすることに決めたのは、そこが記事へのメインのアクセスポイントだからです。しかし状況によっては、たとえばarticles_pathにリダイレクトすることもあります。
それでは、app/views/articles/show.html.erb の下部に削除用ボタンを追加して、ページの記事を削除できるようにしましょう。
<h1><%= @article.title %></h1> <p><%= @article.body %></p> <ul> <li><%= link_to "Edit", edit_article_path(@article) %></li> <li><%= link_to "Destroy", article_path(@article), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %></li> </ul>
上のコードでは、dataオプションを使って"Destroy"リンクのHTML属性data-turbo-methodとdata-turbo-confirmを設定しています。どちらの属性も、新しいRailsアプリケーションにデフォルトで含まれているTurboにフックします。
data-turbo-method="delete"を指定すると、GETリクエストではなくDELETEリクエストが送信されます。
data-turbo-confirm="Are you sure?" を指定すると、リンクをクリックしたときに「Are you sure?」ダイアログが表示され、ユーザーが「キャンセル」をクリックするとリクエストを中止します。
以上でできあがりです!記事のリスト表示も、作成も、更新も思いのままです。CRUDバンザイ!
今度はアプリケーションに第2のモデルを追加しましょう。第2のモデルは記事へのコメントを扱います。
先ほどArticleモデルの作成に使ったのと同じジェネレーターを見てみましょう。今回は、Articleモデルへの参照を保持するComment モデルを作成します。ターミナルで以下のコマンドを実行します。
$ bin/rails generate model Comment commenter:string body:text article:references
コマンドを実行すると、以下の4つのファイルが作成されます。
| ファイル | 目的 |
|---|---|
| db/migrate/20140120201010_create_comments.rb | データベースにコメント用のテーブルを作成するためのマイグレーションファイル(ファイル名のタイムスタンプはこれとは異なります) |
| app/models/comment.rb | Commentモデル |
| test/models/comment_test.rb | Commentモデルをテストするためのハーネス |
| test/fixtures/comments.yml | テストで使うサンプルコメント |
手始めにapp/models/comment.rbを開いてみましょう。
class Comment < ApplicationRecord belongs_to :article end
Commentモデルの内容は、これまでに見たArticleモデルと非常によく似ています。違いは、Active Recordの関連付け(アソシエーション: association)を設定するbelongs_to :articleという行がある点です。関連付けについて詳しくは、本ガイドの次のセクションで解説します。
シェルコマンドで使われている:referencesキーワードは、モデルの特殊なデータ型を表し
、指定されたモデル名の後ろに_idを追加した名前を持つ新しいカラムをデータベーステーブルに作成します。マイグレーションの実行後にdb/schema.rbファイルを調べてみると理解しやすいでしょう。
モデルファイルの他に、以下のようなマイグレーションファイルも生成されています。マイグレーションファイルは、モデルに対応するデータベーステーブルを生成するのに使います。
class CreateComments < ActiveRecord::Migration[7.0] def change create_table :comments do |t| t.string :commenter t.text :body t.references :article, null: false, foreign_key: true t.timestamps end end end
t.referencesという行は、article_idという名前のinteger型カラムとそのインデックス、そしてarticlesのidカラムを指す外部キー制約を設定します。それではマイグレーションを実行しましょう。
$ bin/rails db:migrate
Railsは、これまで実行されていないマイグレーションだけを適切に判定して実行するので、以下のようなメッセージだけが表示されるはずです。
== CreateComments: migrating ================================================= -- create_table(:comments) -> 0.0115s == CreateComments: migrated (0.0119s) ========================================
Active Recordの関連付け機能により、2つのモデルの間にリレーションシップを簡単に宣言することができます。今回の記事とコメントというモデルの場合、以下のいずれかの方法で関連付けを設定できます。
そして上の方法(における英語の記述)は、Railsで関連付けを宣言するときの文法と非常に似ています。Commentモデル(app/models/comment.rb)内のコードに既に書かれていたように、各コメントは1つの記事に属しています。
class Comment < ApplicationRecord belongs_to :article end
今度は、Articleモデル(app/models/article.rb)を編集して、関連付けの他方のモデルをここに追加する必要があります。
class Article < ApplicationRecord has_many :comments validates :title, presence: true validates :body, presence: true, length: { minimum: 10 } end
2つのモデルで行われているこれらの宣言によって、さまざまな動作が自動化されます。たとえば、@articleというインスタンス変数に記事が1件含まれていれば、@article.commentsと書くだけでその記事に関連付けられているコメントをすべて取得できます。
Active Recordの関連付けについて詳しくは、Active Recordの関連付けガイドを参照してください。
articlesコントローラで行ったときと同様、commentsを参照するためにRailsが認識すべきルーティングを追加する必要があります。再びconfig/routes.rbファイルを開き、以下のように変更してください。
Rails.application.routes.draw do root "articles#index" resources :articles do resources :comments end end
この設定により、articlesの内側にネストしたリソース(nested resouce)としてcommentsが作成されます。これは、モデルの記述とは別の視点から、記事とコメントの間のリレーションシップを階層的に捉えたものです。
ルーティングについて詳しくはRailsのルーティングガイドを参照してください。
モデルを手作りしたので、モデルに合うコントローラも作ってみたくなります。それでは、再びこれまでと同様にジェネレータを使ってみましょう。
$ bin/rails generate controller Comments
上のコマンドを実行すると、以下の3つのファイルと1つの空ディレクトリが作成されます。
| ファイル/ディレクトリ | 目的 |
|---|---|
| app/controllers/comments_controller.rb | コメント用コントローラ |
| app/views/comments/ | このコントローラのビューはここに置かれる |
| test/controllers/comments_controller_test.rb | このコントローラのテスト用ファイル |
| app/helpers/comments_helper.rb | ビューヘルパー |
一般的なブログと同様、このブログの記事を読んだ人はそこに直接コメントを追加したくなるでしょう。そしてコメントを追加後に元の記事表示ページに戻り、コメントがそこに反映されていることを確認したいはずです。そこで、CommentsControllerを用いてコメントを作成したり、スパムコメントが書き込まれたら削除できるようにしたいと思います。
そこで最初に、Articleのshowテンプレート(app/views/articles/show.html.erb)を改造して新規コメントを作成できるようにしましょう。
<h1><%= @article.title %></h1> <p><%= @article.body %></p> <ul> <li><%= link_to "Edit", edit_article_path(@article) %></li> <li><%= link_to "Destroy", article_path(@article), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %></li> </ul> <h2>Add a comment:</h2> <%= form_with model: [ @article, @article.comments.build ] do |form| %> <p> <%= form.label :commenter %><br> <%= form.text_field :commenter %> </p> <p> <%= form.label :body %><br> <%= form.text_area :body %> </p> <p> <%= form.submit %> </p> <% end %>
上のコードでは、Articleのshowページにフォームが1つ追加されています。このフォームはCommentsControllerのcreateアクションを呼び出すことでコメントを新規作成します。form_with呼び出しでは配列を1つ渡しています。これは/articles/1/commentsのような「ネストしたルーティング(nested route)」を生成します。
今度はapp/controllers/comments_controller.rbのcreateアクションを改造しましょう。
class CommentsController < ApplicationController def create @article = Article.find(params[:article_id]) @comment = @article.comments.create(comment_params) redirect_to article_path(@article) end private def comment_params params.require(:comment).permit(:commenter, :body) end end
上のコードは、Articleコントローラのコードを書いていたときよりも少しだけ複雑に見えます。これはネストを使ったことによるものです。コメント関連のリクエストでは、コメントがどの記事に追加されるのかを追えるようにしておく必要があります。そこで、Articleモデルのfindメソッドを最初に呼び出し、リクエストで言及されている記事(のオブジェクト)を取得して@articleに保存しています。
さらにこのコードでは、関連付けによって有効になったメソッドをいくつも利用しています。@article.commentsに対してcreateメソッドを実行することで、コメントの作成と保存を同時に行っています(訳注: buildメソッドにすれば作成のみで保存は行いません)。この方法でコメントを作成すると、コメントと記事が自動的にリンクされ、指定された記事に対してコメントが従属するようになります。
新しいコメントの作成が完了したら、article_path(@article)ヘルパーを用いて元の記事の画面に戻ります。既に説明したように、このヘルパーを呼び出すとArticlesControllerのshowアクションが呼び出され、show.html.erbテンプレートがレンダリングされます。この画面にコメントを表示したいので、app/views/articles/show.html.erbに以下のコードを追加しましょう。
<h1><%= @article.title %></h1> <p><%= @article.body %></p> <ul> <li><%= link_to "Edit", edit_article_path(@article) %></li> <li><%= link_to "Destroy", article_path(@article), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %></li> </ul> <h2>Comments</h2> <% @article.comments.each do |comment| %> <p> <strong>Commenter:</strong> <%= comment.commenter %> </p> <p> <strong>Comment:</strong> <%= comment.body %> </p> <% end %> <h2>Add a comment:</h2> <%= form_with model: [ @article, @article.comments.build ] do |form| %> <p> <%= form.label :commenter %><br> <%= form.text_field :commenter %> </p> <p> <%= form.label :body %><br> <%= form.text_area :body %> </p> <p> <%= form.submit %> </p> <% end %>
これで、ブログに記事やコメントを自由に追加して、それらを正しい場所に表示できるようになりました。

さて、ブログの記事とコメントが動作するようになったので、ここでapp/views/articles/show.html.erbテンプレートを見てみましょう。何やらコードがたくさん書かれていて読みにくくなっています。ここでもパーシャルを使ってコードをきれいにしましょう。
最初に、特定記事のコメントをすべて表示する部分を切り出してコメントパーシャルを作成しましょう。app/views/comments/_comment.html.erbというファイルを作成し、以下のコードを入力します。
<p> <strong>Commenter:</strong> <%= comment.commenter %> </p> <p> <strong>Comment:</strong> <%= comment.body %> </p>
続いて、app/views/articles/show.html.erbの内容を以下に置き換えます。
<h1><%= @article.title %></h1> <p><%= @article.body %></p> <ul> <li><%= link_to "Edit", edit_article_path(@article) %></li> <li><%= link_to "Destroy", article_path(@article), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %></li> </ul> <h2>Comments</h2> <%= render @article.comments %> <h2>Add a comment:</h2> <%= form_with model: [ @article, @article.comments.build ] do |form| %> <p> <%= form.label :commenter %><br> <%= form.text_field :commenter %> </p> <p> <%= form.label :body %><br> <%= form.text_area :body %> </p> <p> <%= form.submit %> </p> <% end %>
これで、app/views/comments/_comment.html.erbパーシャルが、@article.commentsコレクションに含まれているコメントをすべてレンダリングするようになりました。renderメソッドが@article.commentsコレクションに含まれる要素を1つずつ列挙するときに、各コメントをパーシャルと同じ名前のローカル変数に自動的に代入します。この場合はcommentというローカル変数が使われるので、これをパーシャルの表示に利用できます。
今度はコメント作成部分もパーシャルに追い出してみましょう。app/views/comments/_form.html.erbファイルを作成し、以下のように入力します。
<%= form_with model: [ @article, @article.comments.build ] do |form| %> <p> <%= form.label :commenter %><br> <%= form.text_field :commenter %> </p> <p> <%= form.label :body %><br> <%= form.text_area :body %> </p> <p> <%= form.submit %> </p> <% end %>
続いてapp/views/articles/show.html.erbの内容を以下で置き換えます。
<h1><%= @article.title %></h1> <p><%= @article.body %></p> <ul> <li><%= link_to "Edit", edit_article_path(@article) %></li> <li><%= link_to "Destroy", article_path(@article), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %></li> </ul> <h2>Comments</h2> <%= render @article.comments %> <h2>Add a comment:</h2> <%= render 'comments/form' %>
2番目のrenderは、レンダリングするcomments/formパーシャルテンプレートを定義しているだけです。comments/formと書くだけで、Railsは区切りのスラッシュ文字を認識し、app/views/commentsディレクトリの_form.html.erbパーシャルをレンダリングすればよいということを理解し、実行してくれます。app/views/comments/_form.html.erbなどと書く必要はありません。
@articleオブジェクトはインスタンス変数なので、ビューでレンダリングされるどのパーシャルからもアクセスできます。
Railsの「concern(関心事)」とは、大規模なコントローラやモデルの理解や管理を楽にする手法の1つです。複数のモデル(またはコントローラ)が同じ関心を共有していれば、concernを介して再利用できるというメリットもあります。concernはRubyの「モジュール」で実装され、モデルやコントローラが担当する機能のうち明確に定義された部分を表すメソッドをそのモジュールに含めます。なおモジュールは他の言語では「ミックスイン」と呼ばれることもよくあります。
concernは、コントローラやモデルで普通のモジュールと同じように使えます。rails new blog でアプリを作成すると、app/内に以下の2つのconcernsフォルダも作成されます。
app/controllers/concerns app/models/concerns
以下の例ではブログの新機能を実装しますが、この機能にはconcernを利用するのが有益です。そこで、この機能を利用するためにconcernを作成し、コードをリファクタリングすることで、コードをよりDRYでメンテナンスしやすくします。
1件のブログ記事はさまざまなステータスを持つ可能性があります。たとえば記事の可視性について「誰でも見てよい(public)」「著者だけに見せる(private)」というステータスを持つかもしれませんし、「復旧可能な形で記事を非表示にする(archived)」ことも考えられます。コメントについても同様に可視性やアーカイブを設定することもあるでしょう。こうしたステータスを表す方法の1つとして、モデルごとにstatusカラムを持たせるとしましょう。
以下のマイグレーション生成コマンドを実行してstatusカラムを追加した後で、ArticleモデルとCommentsモデルにstatusカラムを追加します。
$ bin/rails generate migration AddStatusToArticles status:string $ bin/rails generate migration AddStatusToComments status:string
続いて以下を実行し、生成されたマイグレーションでデータベースを更新します。
$ bin/rails db:migrate
マイグレーションについて詳しくは、Active Record マイグレーションガイドを参照してください。
次に、app/controllers/articles_controller.rbのStrong Parametersを以下のように更新して:statusキーも許可しておかなければなりません。
private def article_params params.require(:article).permit(:title, :body, :status) end
app/controllers/comments_controller.rbでも同様に:statusキーを許可します。
private def comment_params params.require(:comment).permit(:commenter, :body, :status) end
bin/rails db:migrateマイグレーションを実行してstatusカラムを追加したら、Articleモデルを以下で置き換えます。
class Article < ApplicationRecord has_many :comments validates :title, presence: true validates :body, presence: true, length: { minimum: 10 } VALID_STATUSES = ['public', 'private', 'archived'] validates :status, inclusion: { in: VALID_STATUSES } def archived? status == 'archived' end end
Commentモデルも以下で置き換えます。
class Comment < ApplicationRecord belongs_to :article VALID_STATUSES = ['public', 'private', 'archived'] validates :status, inclusion: { in: VALID_STATUSES } def archived? status == 'archived' end end
次にindexアクションに対応するapp/views/articles/index.html.erbテンプレートで以下のようにarchived?メソッドを追加し、アーカイブ済みの記事を表示しないようにします。
<h1>Articles</h1> <ul> <% @articles.each do |article| %> <% unless article.archived? %> <li> <%= link_to article.title, article %> </li> <% end %> <% end %> </ul> <%= link_to "New Article", new_article_path %>
同様に、コメントのパーシャルビュー(app/views/comments/_comment.html.erb)にも、アーカイブ済みのコメントが表示されないようarchived?メソッドを書きます。
<% unless comment.archived? %> <p> <strong>Commenter:</strong> <%= comment.commenter %> </p> <p> <strong>Comment:</strong> <%= comment.body %> </p> <% end %>
しかし、2つのモデルのコードを見返してみると、ロジックが重複していることがわかります。このままでは、今後ブログにプライベートメッセージ機能などを追加するとロジックがまた重複してしまうでしょう。concernは、このような重複を避けるのに便利です。
1つのconcernは、モデルの責務の「一部」についてのみ責任を負います。この例の場合、「関心(concern)」の対象となるメソッドはすべてモデルの可視性に関連しているので。新しいconcern(すなわちモジュール)をVisibleと呼ぶことにしましょう。app/models/concernsディレクトリの下にvisible.rbという新しいファイルを作成し、複数のモデルで重複していたステータス関連のすべてのメソッドをそこに移動します。
app/models/concerns/visible.rb
module Visible def archived? status == 'archived' end end
ステータスを検証するメソッドもconcernにまとめられますが、バリデーションメソッドはクラスレベルで呼び出されるので、より複雑になります。APIドキュメントのActiveSupport::Concernには、以下のようにバリデーションをシンプルにincludedする方法が紹介されています。
module Visible extend ActiveSupport::Concern VALID_STATUSES = ['public', 'private', 'archived'] included do validates :status, inclusion: { in: VALID_STATUSES } end def archived? status == 'archived' end end
これで各モデルで重複しているロジックを取り除けるようになったので、新たにVisibleモジュールをincludeしましょう。
app/models/article.rbを以下のように変更します。
class Article < ApplicationRecord include Visible has_many :comments validates :title, presence: true validates :body, presence: true, length: { minimum: 10 } end
app/models/comment.rbも以下のように変更します。
class Comment < ApplicationRecord include Visible belongs_to :article end
concernにはクラスメソッドも追加できます。たとえば、ステータスがpublicの記事(またはコメント)の件数をメインページに表示したい場合は、Visibleモジュールに以下の要領でクラスメソッドを追加します。
module Visible extend ActiveSupport::Concern VALID_STATUSES = ['public', 'private', 'archived'] included do validates :status, inclusion: { in: VALID_STATUSES } end class_methods do def public_count where(status: 'public').count end end def archived? status == 'archived' end end
これで、以下のようにindexビューで任意のクラスメソッドを呼べるようになります。
<h1>Articles</h1> Our blog has <%= Article.public_count %> articles and counting! <ul> <% @articles.each do |article| %> <% unless article.archived? %> <li> <%= link_to article.title, article %> </li> <% end %> <% end %> </ul> <%= link_to "New Article", new_article_path %>
仕上げとして、フォームにセレクトボックスを追加して、ユーザーが記事を作成したりコメントを投稿したりするときにステータスを選択できるようにします。デフォルトのステータスをpublicと指定することもできます。app/views/articles/_form.html.erbに以下を追加します。
<div> <%= form.label :status %><br> <%= form.select :status, ['public', 'private', 'archived'], selected: 'public' %> </div>
app/views/comments/_form.html.erbにも以下を追加します。
<p> <%= form.label :status %><br> <%= form.select :status, ['public', 'private', 'archived'], selected: 'public' %> </p>
スパムコメントを削除できるようにするのも、このブログでは重要な機能です。そのためのビューを作成し、CommentsControllerにdestroyアクションを作成する必要があります。
最初にapp/views/comments/_comment.html.erbパーシャルに削除用のボタンを追加しましょう。
<% unless comment.archived? %> <p> <strong>Commenter:</strong> <%= comment.commenter %> </p> <p> <strong>Comment:</strong> <%= comment.body %> </p> <p> <%= link_to "Destroy Comment", [comment.article, comment], data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %> </p> <% end %>
この新しい「Destroy Comment」リンクをクリックすると、DELETE /articles/:article_id/comments/:idというリクエストがCommentsControllerに送信されます。コントローラはそれを受け取って、どのコメントを削除すべきかを検索することになります。それではコントローラ(app/controllers/comments_controller.rb)にdestroyアクションを追加しましょう。
class CommentsController < ApplicationController def create @article = Article.find(params[:article_id]) @comment = @article.comments.create(comment_params) redirect_to article_path(@article) end def destroy @article = Article.find(params[:article_id]) @comment = @article.comments.find(params[:id]) @comment.destroy redirect_to article_path(@article), status: :see_other end private def comment_params params.require(:comment).permit(:commenter, :body, :status) end end
このdestroyアクションは、まずどの記事が対象であるかを検索して@articleに保存し、続いて@article.commentsコレクションの中のどのコメントが対象であるかを特定して@commentに保存します。そしてそのコメントをデータベースから削除し、終わったら記事のshowアクションに戻ります。
ある記事を削除したら、その記事に関連付けられているコメントも一緒に削除する必要があります(そうしないと、コメントがいつまでもデータベース上に残ってしまいます)。Railsでは関連付けにdependentオプションを指定することでこれを実現しています。Articleモデルapp/models/article.rbを以下のように変更しましょう。
class Article < ApplicationRecord include Visible has_many :comments, dependent: :destroy validates :title, presence: true validates :body, presence: true, length: { minimum: 10 } end
このブログアプリケーションをオンラインで公開すると、このままでは誰でも記事を追加/編集/削除したり、コメントを削除したりできてしまいます。
Railsではこのような場合に便利な、非常にシンプルなHTTP認証システムが用意されています。
ArticlesControllerでは、認証されていない人物がアクションにアクセスできないようにブロックする必要があります。そこで、Railsのhttp_basic_authenticate_withメソッドを使うことで、このメソッドが許可する場合に限って、リクエストされたアクションにアクセスできるようにすることができます。
この認証システムを使うには、ArticlesControllerコントローラの冒頭部分で指定します。今回は、indexアクションとshowアクションは自由にアクセスできるようにし、それ以外のアクションには認証を要求するようにしたいと思います。そこで、app/controllers/articles_controller.rbに次の記述を追加します。
class ArticlesController < ApplicationController http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] def index @articles = Article.all end #(以下省略)
コメントの削除も認証済みユーザーにだけ許可したいので、CommentsController(app/controllers/comments_controller.rb)に以下のように追記します。
class CommentsController < ApplicationController http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy def create @article = Article.find(params[:article_id]) # ... end #(以下省略)
これで、記事を新規作成しようとすると、以下のようなBASIC http認証ダイアログが表示されます。

正しいユーザー名とパスワードを入力すると、別のユーザー名とパスワードが要求されるか、ブラウザが閉じられるまで、認証された状態が続きます。
もちろん、Railsでは他の認証方法も使えます。Railsにはさまざまな認証システムがありますが、その中で人気が高い認証システムはDeviseとAuthlogic gemの2つです。
セキュリティ、それもWebアプリケーションのセキュリティは非常に幅広く、かつ詳細に渡っています。Railsアプリケーションのセキュリティについて詳しくは、本ガイドのRailsセキュリティガイドを参照してください。
以上で、Railsアプリケーションを初めて作るという試みは終わりです。この後は自由に更新したり実験を重ねたりできます。
もちろん、何の助けもなしにWebアプリケーションを作らなければならないなどということはないということを忘れてはなりません。RailsでWebアプリを立ち上げたり実行したりするうえで助けが必要になったら、以下のサポート用リソースを自由に参照できます。
Railsでの無用なトラブルを避けるための最も初歩的なコツは、外部データを常にUTF-8エンコーディングで保存しておくことです。そうしておかないと、RubyライブラリやRailsがネイティブデータをたびたびUTF-8に変換しなければならず、しかも場合によっては失敗します。外部データのエンコーディングは常にUTF-8で統一することをおすすめします。
外部データのエンコードが統一されていないと、たとえば画面に黒い菱型◆や疑問符?が表示されたり、"ü"という文字のはずが"ü"という文字に化けたりするといった症状がよく発生します。Railsではこうした問題を緩和するため、問題の原因を自動的に検出して修正するために内部で多くの手順を行っています。しかし、UTF-8で保存されていない外部データがあると、Railsによる自動検出・修正が効かないデータで文字化けが発生することがあります。
UTF-8でないデータの主な原因は以下の2つです。
◆?のようにブラウザ表示が文字化けすることがあります。これはi18n(国際化)用の翻訳ファイルで発生することもあります。DreamweaverのようにUTF-8保存がデフォルトでないエディタであっても、デフォルトをUTF-8に変更する方法は用意されているはずです。エンコードをUTF-8に変更してください。Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。