edge
詳細はrubyonrails.orgで: もっとRuby on Rails

Railsのはじめ方

このガイドでは、Ruby on Railsを使って始める方法について説明します。

このガイドを読み終えると、以下のことがわかるようになります:

1 ガイドの前提条件

このガイドは、Railsをゼロから作成して始めたい初心者を対象にしています。Railsの事前の経験は必要ありません。

RailsはRubyプログラミング言語上で動作するWebアプリケーションフレームワークです。 Rubyの事前の経験がない場合、Railsに直接取り組むと非常に学習コストがかかります。Rubyの学習には、いくつかのオンラインリソースのキュレーションされたリストがあります:

一部のリソースは、まだ優れているものの、古いバージョンのRubyをカバーしており、Railsの日常的な開発で見られる一部の構文が含まれていない場合があります。

2 Railsとは何ですか?

Railsは、Rubyプログラミング言語で書かれたWebアプリケーション開発フレームワークです。 Railsは、開発者が始めるために必要なものについての前提を立てることで、Webアプリケーションのプログラミングを容易にするように設計されています。他の言語やフレームワークよりも少ないコードで多くのことを実現できるようにします。 経験豊富なRails開発者は、Railsを使うことでWebアプリケーションの開発がより楽しくなると報告しています。

Railsは意見の強いソフトウェアです。Railsは、あることを行うための「最良の」方法があるという前提を立て、その方法を奨励するように設計されています。場合によっては、代替案を推奨しないこともあります。もし「Railsのやり方」を学ぶなら、生産性が非常に向上することに気づくでしょう。他の言語での古い習慣をRailsの開発に持ち込んだり、他で学んだパターンを使おうとすると、あまり良い経験にはならないかもしれません。

Railsの哲学には2つの主要な指針があります:

  • Don't Repeat Yourself(DRY): DRYはソフトウェア開発の原則であり、「システム内のすべての知識は、一つの明確で明確な表現を持つ必要がある」と述べています。同じ情報を何度も書かないことで、コードは保守性が高く、拡張性があり、バグが少なくなります。
  • Convention Over Configuration(設定より規約): Railsは、Webアプリケーションの多くのことを行うための最良の方法についての意見を持っており、設定ファイルを延々と指定する必要はありません。代わりに、一連の規約に従います。

3 新しいRailsプロジェクトの作成

このガイドを読む最良の方法は、ステップバイステップで進めることです。すべてのステップは、この例のアプリケーションを実行するために必要であり、追加のコードや手順は必要ありません。

このガイドに従って進めると、blogというRailsプロジェクト、非常にシンプルなウェブログを作成します。アプリケーションの構築を開始する前に、Rails自体がインストールされていることを確認する必要があります。

注意:以下の例では、UNIXのようなOSでは$をターミナルプロンプトを表すために使用していますが、カスタマイズされて異なる表示になる場合もあります。Windowsを使用している場合、プロンプトはC:\source_code>のように表示されます。

3.1 Railsのインストール

Railsをインストールする前に、システムに必要な前提条件がインストールされているか確認する必要があります。これには以下が含まれます:

  • Ruby
  • SQLite3

3.1.1 Rubyのインストール

コマンドラインプロンプトを開きます。macOSではTerminal.appを開き、Windowsではスタートメニューから「実行」を選択し、cmd.exeと入力します。$で始まるコマンドは、コマンドラインで実行する必要があります。現在のバージョンのRubyがインストールされていることを確認します:

$ ruby --version
ruby 2.7.0

RailsにはRubyバージョン2.7.0以降が必要です。最新のRubyバージョンを使用することが推奨されています。 返されるバージョン番号がその数値よりも小さい場合(2.3.7や1.8.7など)、新しいバージョンのRubyをインストールする必要があります。

WindowsでRailsをインストールするには、まずRuby Installerをインストールする必要があります。

ほとんどのオペレーティングシステムのインストール方法については、 ruby-lang.orgを参照してください。

3.1.2 SQLite3のインストール

SQLite3データベースのインストールも必要です。 多くの人気のあるUNIXのようなOSには、適切なバージョンのSQLite3が同梱されています。 その他のOSでは、SQLite3のウェブサイトのインストール手順を参照してください。 インストールが正しく行われ、PATH にロードされていることを確認してください。

$ sqlite3 --version

プログラムはバージョンを報告するはずです。

3.1.3 Rails のインストール

Rails をインストールするには、RubyGems が提供する gem install コマンドを使用します。

$ gem install rails

正しくインストールされているかを確認するために、新しいターミナルで次のコマンドを実行できるはずです。

$ rails --version

もし "Rails 7.0.0" のような表示がされたら、準備ができています。

3.2 ブログアプリケーションの作成

Rails には、特定のタスクを開始するために必要なすべてを作成するための便利なスクリプトであるジェネレータがいくつか用意されています。その中の1つが新しいアプリケーションジェネレータであり、自分で書く必要がないように新しい Rails アプリケーションの基盤を提供します。

このジェネレータを使用するには、ターミナルを開き、ファイルを作成する権限があるディレクトリに移動し、次のコマンドを実行します。

$ rails new blog

これにより、blog ディレクトリ内に Blog という名前の Rails アプリケーションが作成され、Gemfile で既に言及されているジェムの依存関係が bundle install を使用してインストールされます。

Rails アプリケーションジェネレータが受け入れるすべてのコマンドラインオプションを表示するには、rails new --help を実行します。

ブログアプリケーションを作成した後、そのフォルダに移動します。

$ cd blog

blog ディレクトリには、Rails アプリケーションの構造を構成する生成されたファイルとフォルダがいくつか含まれています。このチュートリアルでは、ほとんどの作業は app フォルダで行われますが、デフォルトで Rails が作成する各ファイルとフォルダの機能について基本的な説明を以下に示します。

ファイル/フォルダ 目的
app/ アプリケーションのコントローラ、モデル、ビュー、ヘルパー、メーラー、チャネル、ジョブ、およびアセットが含まれています。このガイドの残りの部分では、このフォルダに焦点を当てます。
bin/ アプリケーションを起動する rails スクリプトが含まれており、アプリケーションのセットアップ、更新、デプロイ、実行に使用する他のスクリプトを含めることもできます。
config/ アプリケーションのルート、データベースなどの設定が含まれています。これについての詳細は、Configuring Rails Applications を参照してください。
config.ru アプリケーションを起動するために使用される Rack ベースのサーバの Rack 設定です。Rack の詳細については、Rack website を参照してください。
db/ 現在のデータベーススキーマとデータベースマイグレーションが含まれています。
Gemfile
Gemfile.lock
これらのファイルを使用して、Rails アプリケーションに必要なジェムの依存関係を指定できます。これらのファイルは Bundler ジェムによって使用されます。Bundler の詳細については、Bundler website を参照してください。
lib/ アプリケーションの拡張モジュール。
log/ アプリケーションのログファイル。
public/ 静的ファイルとコンパイルされたアセットが含まれています。アプリケーションが実行されている間、このディレクトリはそのまま公開されます。
Rakefile コマンドラインから実行できるタスクを見つけて読み込むためのファイルです。タスクの定義は Rails の各コンポーネントによって定義されます。Rakefile を変更する代わりに、lib/tasks ディレクトリにファイルを追加することで独自のタスクを追加するべきです。
README.md アプリケーションの簡単な説明書です。他の人にアプリケーションの機能や設定方法などを伝えるために、このファイルを編集する必要があります。
storage/ ディスクサービスの Active Storage ファイル。Active Storage Overview を参照してください。
test/ ユニットテスト、フィクスチャ、およびその他のテスト用具。Testing Rails Applications を参照してください。
tmp/ キャッシュや PID ファイルなどの一時ファイル。
vendor/ サードパーティのコードを配置する場所。典型的な Rails アプリケーションでは、ベンダーのジェムが含まれます。
.gitattributes このファイルは、git リポジトリ内の特定のパスのメタデータを定義します。このメタデータは、git や他のツールが動作を向上させるために使用できます。詳細については、gitattributes documentation を参照してください。
.gitignore このファイルは、git が無視するべきファイル(またはパターン)を指定します。ファイルを無視する方法についての詳細は、GitHub - Ignoring files を参照してください。
.ruby-version このファイルにはデフォルトの Ruby バージョンが含まれています。

4 Hello, Rails!

まずは、画面にテキストを表示しましょう。これを行うには、Rails アプリケーションサーバを起動する必要があります。

4.1 Web サーバの起動

実際には、すでに機能する Rails アプリケーションがあります。それを表示するには、開発マシンで Web サーバを起動する必要があります。blog ディレクトリで次のコマンドを実行することでこれを行うことができます。

$ bin/rails server

ヒント:Windowsを使用している場合、スクリプトをRubyインタプリタに直接渡す必要があります。例:ruby bin\rails server

ヒント:JavaScriptのアセット圧縮には、システムにJavaScriptランタイムが必要です。ランタイムがない場合、アセットの圧縮中にexecjsエラーが表示されます。通常、macOSとWindowsにはJavaScriptランタイムがインストールされています。therubyrhinoはJRubyユーザーに推奨されるランタイムであり、JRubyで生成されたアプリのGemfileにデフォルトで追加されます。サポートされているすべてのランタイムについては、ExecJSを調べることができます。

これにより、デフォルトでRailsと一緒に配布されているWebサーバーであるPumaが起動します。アプリケーションを実行するには、ブラウザウィンドウを開き、http://localhost:3000に移動します。Railsのデフォルトの情報ページが表示されるはずです。

Railsの起動ページのスクリーンショット

Webサーバーを停止するには、実行中のターミナルウィンドウでCtrl+Cを押します。開発環境では、Railsは通常、サーバーを再起動する必要はありません。ファイルで行った変更は、サーバーによって自動的に反映されます。

Railsの起動ページは、新しいRailsアプリケーションの「スモークテスト」です。これにより、ソフトウェアが正しく設定され、ページを提供できるようになっているかどうかが確認されます。

4.2 Railsに「こんにちは」と言わせる

Railsに「こんにちは」と言わせるには、少なくともルートコントローラ、およびビューを作成する必要があります。ルートはリクエストをコントローラのアクションにマッピングします。コントローラのアクションは、リクエストを処理するために必要な作業を実行し、ビューのためのデータを準備します。ビューは、指定された形式でデータを表示します。

実装の観点からは、ルートはRubyのDSL(ドメイン固有言語)で書かれたルールです。コントローラはRubyのクラスであり、そのパブリックメソッドはアクションです。ビューはテンプレートであり、通常はHTMLとRubyの組み合わせで書かれます。

まず、config/routes.rbファイルのRails.application.routes.drawブロックの先頭に、ルートを追加します。

Rails.application.routes.draw do
  get "/articles", to: "articles#index"

  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

上記のルートは、GET /articlesリクエストがArticlesControllerindexアクションにマッピングされることを宣言しています。

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にアクセスして、テキストが表示されることを確認します。

4.3 アプリケーションのホームページを設定する

現時点では、http://localhost:3000はまだRuby on Railsのロゴが表示されるページです。同じくhttp://localhost:3000にも「Hello, Rails!」というテキストを表示しましょう。そのために、アプリケーションのルートパスを適切なコントローラとアクションにマッピングするルートを追加します。

config/routes.rbを開き、次のrootルートをRails.application.routes.drawブロックの先頭に追加します。

Rails.application.routes.draw do
  root "articles#index"

  get "/articles", to: "articles#index"
end

これで、http://localhost:3000を訪れると「Hello, Rails!」というテキストが表示されるようになりました。これにより、rootルートもArticlesControllerindexアクションにマッピングされていることが確認できます。

ヒント:ルーティングの詳細については、Rails Routing from the Outside Inを参照してください。

5 自動読み込み

Railsアプリケーションでは、アプリケーションコードを読み込むためにrequireを使用しません。

ArticlesControllerApplicationControllerを継承していることに気付いたかもしれませんが、app/controllers/articles_controller.rbには次のようなものはありません。

require "application_controller" # これは行わないでください。

アプリケーションのクラスやモジュールはどこでも利用できます。appの下にあるものをrequireで読み込む必要はありませんし、すべきではありません。この機能は「自動読み込み」と呼ばれ、Autoloading and Reloading Constantsで詳しく説明されています。 requireコールは2つのユースケースでのみ必要です:

  • libディレクトリのファイルをロードするために。
  • Gemfilerequire: falseと指定されているgemの依存関係をロードするために。

6 MVCとあなた

これまで、ルート、コントローラ、アクション、ビューについて説明してきました。これらはすべて、MVC(モデル-ビュー-コントローラ)パターンに従うウェブアプリケーションの典型的な要素です。 MVCは、アプリケーションの責任を分割して理解しやすくするための設計パターンです。Railsは、この設計パターンに従っています。

コントローラとビューがあるので、次の要素であるモデルを生成しましょう。

6.1 モデルの生成

モデルは、データを表すために使用されるRubyクラスです。さらに、モデルはRailsのActive Recordと呼ばれる機能を介してアプリケーションのデータベースと対話することができます。

モデルを定義するために、モデルジェネレータを使用します:

$ bin/rails generate model Article title:string body:text

注意:モデル名は単数形です。なぜなら、インスタンス化されたモデルは単一のデータレコードを表すからです。この規約を覚えるのに役立つために、モデルのコンストラクタを呼び出す方法を考えてみてください:Article.new(...)と書きたいのですが、Articles.new(...)とは書きたくありません。

これにより、いくつかのファイルが作成されます:

invoke  active_record
create    db/migrate/<timestamp>_create_articles.rb
create    app/models/article.rb
invoke    test_unit
create      test/models/article_test.rb
create      test/fixtures/articles.yml

焦点を当てるべき2つのファイルは、マイグレーションファイル(db/migrate/<timestamp>_create_articles.rb)とモデルファイル(app/models/article.rb)です。

6.2 データベースマイグレーション

マイグレーションは、アプリケーションのデータベースの構造を変更するために使用されます。Railsアプリケーションでは、マイグレーションはデータベースに依存しないようにRubyで書かれています。

新しいマイグレーションファイルの内容を見てみましょう:

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のブロック内では、titlebodyの2つの列が定義されています。これらは、私たちが生成コマンドに含めたため、ジェネレータによって追加されました(bin/rails generate model Article title:string body:text)。

ブロックの最後の行では、t.timestampsという呼び出しがあります。このメソッドは、created_atupdated_atという2つの追加の列を定義します。後で見るように、Railsはこれらを管理し、モデルオブジェクトを作成または更新するときに値を設定します。

次のコマンドでマイグレーションを実行しましょう:

$ bin/rails db:migrate

コマンドは、テーブルが作成されたことを示す出力を表示します:

==  CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.0018s
==  CreateArticles: migrated (0.0018s) ==========================

ヒント:マイグレーションについて詳しくは、Active Record Migrationsを参照してください。

これで、モデルを使用してテーブルと対話することができます。

6.3 データベースとの対話にモデルを使用する

モデルを少しいじってみるために、Railsの機能であるコンソールを使用します。コンソールは、irbと同様の対話型のコーディング環境ですが、Railsとアプリケーションのコードを自動的にロードすることもできます。

次のコマンドでコンソールを起動しましょう:

$ bin/rails console

次のようなirbプロンプトが表示されるはずです:

Loading development environment (Rails 7.0.0)
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 "articles" ...というデータベースクエリを示しています。これは、記事がテーブルに挿入されたことを示しています。そして、もう一度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">

オブジェクトのidcreated_at、およびupdated_at属性が設定されました。 オブジェクトを保存すると、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 BasicsActive Record Query Interfaceを参照してください。

モデルはMVCパズルの最後のピースです。次に、すべてのピースを組み合わせます。

6.4 記事の一覧表示

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の組み合わせです。ERBは、ドキュメントに埋め込まれたRubyコードを評価するテンプレートシステムです。ここでは、2種類のERBタグが見られます:<% %><%= %><% %>タグは「囲まれたRubyコードを評価する」という意味です。<%= %>タグは「囲まれたRubyコードを評価し、それが返す値を出力する」という意味です。通常、ERBタグの内容を読みやすくするために、ERBタグの内容を短く保つことが最善ですが、通常のRubyプログラムで書くことができるものはすべてこれらのERBタグの内部に入れることができます。

@articles.eachが返す値を出力したくないので、そのコードを<% %>で囲みました。しかし、各記事のarticle.titleが返す値を出力したいので、そのコードを<%= %>で囲みました。

http://localhost:3000を訪れることで最終結果を確認できます(bin/rails serverが実行されていることを忘れないでください)。以下は、それを行ったときの動作です。

  1. ブラウザがリクエストを行います:GET http://localhost:3000
  2. Railsアプリケーションがこのリクエストを受け取ります。
  3. RailsルータがルートルートをArticlesControllerindexアクションにマッピングします。
  4. indexアクションはArticleモデルを使用してデータベースからすべての記事を取得します。
  5. Railsは自動的にapp/views/articles/index.html.erbビューをレンダリングします。
  6. ビューのERBコードが評価されてHTMLが出力されます。
  7. サーバはHTMLを含むレスポンスをブラウザに送信します。

すべてのMVCのピースを組み合わせ、最初のコントローラアクションを持っています!次に、2番目のアクションに移りましょう。

7 CRUDit Where CRUDit Is Due

ほとんどのWebアプリケーションには、CRUD(作成、読み取り、更新、削除)の操作が含まれています。実際、アプリケーションのほとんどの作業がCRUDであることさえあるかもしれません。Railsはこれを認識し、CRUDを行うコードを簡素化するための多くの機能を提供しています。

この機能を探索するために、アプリケーションにさらなる機能を追加してみましょう。

7.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。これはルートパラメータを指定します。ルートパラメータは、リクエストのパスのセグメントをキャプチャし、その値をコントローラアクションでアクセス可能なparamsハッシュに入れます。たとえば、GET http://localhost:3000/articles/1のようなリクエストを処理する場合、1:idの値としてキャプチャされ、それはArticlesControllershowアクションでparams[:id]としてアクセスできるようになります。 showアクションをindexアクションの下に追加しましょう。app/controllers/articles_controller.rbに以下のコードを追加します。

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end
end

showアクションは、ルートパラメータでキャプチャされたIDを使用してArticle.find(前述の通り)を呼び出します。返された記事は@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にアクセスすると記事が表示されます!

最後に、記事のページに移動するための便利な方法を追加しましょう。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>

7.2 リソースフルなルーティング

これまで、CRUDの「R」(Read)について説明しました。次に、「C」(Create)、 「U」(Update)、および「D」(Delete)をカバーします。これらの操作を実行するために、新しいルート、コントローラのアクション、ビューを追加します。ルート、コントローラのアクション、ビューが組み合わさってエンティティのCRUD操作を実行する場合、そのエンティティを「リソース」と呼びます。例えば、このアプリケーションでは、記事がリソースであると言えます。

Railsは、resourcesというルートメソッドを提供しており、コレクションのリソース(例:記事)に対してすべての標準的なルートをマッピングします。したがって、「C」、「U」、「D」のセクションに進む前に、config/routes.rbの2つのgetルートをresourcesで置き換えましょう。

Rails.application.routes.draw do
  root "articles#index"

  resources :articles
end

bin/rails routesコマンドを実行してマッピングされたルートを確認できます。

$ 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とパスのヘルパーメソッドも設定します。これらのヘルパーメソッドは、コードが特定のルート構成に依存しないようにするために使用できます。上記の「Prefix」列の値に_urlまたは_pathの接尾辞を追加したものがこれらのヘルパーメソッドの名前になります。例えば、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ヘルパーを使用して、さらに改善します。link_toヘルパーは、最初の引数をリンクのテキストとし、2番目の引数をリンクの先としてリンクをレンダリングします。2番目の引数にモデルオブジェクトを渡すと、link_toは適切なパスヘルパーを呼び出してオブジェクトをパスに変換します。例えば、記事を渡すと、link_toarticle_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 Routing from the Outside Inを参照してください。

7.3 新しい記事の作成

次に、CRUDの「C」(Create)に進みます。通常、Webアプリケーションでは、新しいリソースを作成するためには複数のステップが必要です。まず、ユーザーは入力フォームをリクエストします。次に、ユーザーがフォームを送信します。エラーがない場合、リソースが作成され、確認が表示されます。エラーがある場合は、フォームがエラーメッセージとともに再表示され、プロセスが繰り返されます。

Railsアプリケーションでは、これらのステップは通常、コントローラのnewアクションとcreateアクションで処理されます。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アクションは新しい記事をインスタンス化しますが、保存はしません。この記事は、ビューでフォームを構築する際に使用されます。デフォルトでは、newアクションはapp/views/articles/new.html.erbをレンダリングします。次に、このファイルを作成します。 createアクションは、タイトルと本文の値を持つ新しい記事をインスタンス化し、保存を試みます。記事が正常に保存された場合、アクションはブラウザを記事のページにリダイレクトします。URLは"http://localhost:3000/articles/#{@article.id}"です。 保存に失敗した場合、アクションはフォームを再表示し、ステータスコード422 Unprocessable Entityapp/views/articles/new.html.erbをレンダリングします。 ここでのタイトルと本文はダミーの値です。フォームを作成した後、これらを変更します。

注意:redirect_toは、ブラウザが新しいリクエストを行うため、 renderは現在のリクエストに対して指定されたビューをレンダリングします。 データベースやアプリケーションの状態を変更した後は、redirect_toを使用することが重要です。 そうしないと、ユーザーがページを更新すると、ブラウザが同じリクエストを行い、変更が繰り返されます。

7.3.1 フォームビルダーの使用

フォームを作成するためにRailsの機能であるフォームビルダーを使用します。フォームビルダーを使用すると、最小限のコードで設定が完了し、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_withブロック内で、フォームビルダーのlabeltext_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 Form Helpersを参照してください。

7.3.2 ストロングパラメータの使用

送信されたフォームデータは、キャプチャされたルートパラメータとともにparamsハッシュに格納されます。したがって、createアクションは、params[:article][:title]を介して送信されたタイトルにアクセスし、params[:article][:body]を介して送信された本文にアクセスすることができます。 これらの値を個別にArticle.newに渡すこともできますが、冗長でエラーの原因になる可能性があります。さらに、フィールドを追加するにつれて悪化します。

代わりに、値が含まれる単一のハッシュを渡します。ただし、そのハッシュ内で許可される値を指定する必要があります。そうしないと、悪意のあるユーザーが追加のフォームフィールドを送信してプライベートデータを上書きする可能性があります。実際には、未フィルタリングのparams[:article]ハッシュを直接Article.newに渡すと、RailsはForbiddenAttributesErrorを発生させて問題を警告します。 そのため、Railsの機能であるストロングパラメータを使用してparamsをフィルタリングします。paramsのためにarticle_paramsという名前のプライベートメソッドを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

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

ストロングパラメータについて詳しくは、Action Controller Overview § Strong Parametersを参照してください。

7.3.3 バリデーションとエラーメッセージの表示

これまで見てきたように、リソースの作成は複数のステップからなるプロセスです。無効なユーザー入力の処理もそのプロセスの一部です。Railsは、無効なユーザー入力に対処するためのバリデーションという機能を提供しています。バリデーションは、モデルオブジェクトが保存される前にチェックされるルールです。チェックのいずれかが失敗した場合、保存は中止され、適切なエラーメッセージがモデルオブジェクトのerrors属性に追加されます。

app/models/article.rbにモデルにいくつかのバリデーションを追加しましょう。

class Article < ApplicationRecord
  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

最初のバリデーションは、titleの値が存在する必要があることを宣言しています。titleは文字列なので、titleの値には少なくとも1つの空白以外の文字が含まれている必要があります。

2番目のバリデーションは、bodyの値も存在する必要があることを宣言しています。さらに、bodyの値は少なくとも10文字である必要があります。

注意:titlebodyの属性がどこで定義されているか疑問に思うかもしれません。Active Recordは、すべてのテーブル列に対してモデル属性を自動的に定義するため、モデルファイルでこれらの属性を宣言する必要はありません。 バリデーションが完了したので、app/views/articles/new.html.erbを変更してtitlebodyのエラーメッセージを表示しましょう。

<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メソッドは、指定された属性に対するユーザーフレンドリーなエラーメッセージの配列を返します。その属性にエラーがない場合、配列は空になります。

これがどのように動作するかを理解するために、newcreateのコントローラーアクションをもう一度見てみましょう。

  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 Validationsを参照してください。バリデーションエラーメッセージについて詳しくは、Active Record Validations § Working with Validation Errorsを参照してください。

7.3.4 最後に

http://localhost:3000/articles/newを訪れることで記事を作成できます。最後に、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 %>

7.4 記事の更新

CRUDの「CR」をカバーしました。次は「U」(更新)に移りましょう。リソースの更新はリソースの作成と非常に似ています。両方とも複数のステップで行われるプロセスです。まず、ユーザーはデータを編集するためのフォームをリクエストします。次に、ユーザーがフォームを送信します。エラーがない場合、リソースが更新されます。それ以外の場合、フォームがエラーメッセージと共に再表示され、プロセスが繰り返されます。

これらのステップは、通常、コントローラーのeditupdateアクションで処理されます。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

editupdateアクションがnewcreateアクションに似ていることに注意してください。

editアクションはデータベースから記事を取得し、フォームを構築する際に使用するために@articleに保存します。デフォルトでは、editアクションはapp/views/articles/edit.html.erbをレンダリングします。

updateアクションは(再)データベースから記事を取得し、article_paramsでフィルタリングされた送信されたフォームデータで更新を試みます。バリデーションが失敗せずに更新が成功した場合、アクションはブラウザを記事のページにリダイレクトします。それ以外の場合、アクションはエラーメッセージと共にフォームを再表示するためにapp/views/articles/edit.html.erbをレンダリングします。

7.4.1 パーシャルを使用してビューコードを共有する

editフォームはnewフォームと同じになります。Railsのフォームビルダーとリソースフルルーティングのおかげで、コードも同じになります。フォームビルダーは、モデルオブジェクトが以前に保存されているかどうかに基づいて、適切な種類のリクエストを行うようにフォームを自動的に設定します。

コードが同じであるため、それを共有ビューであるパーシャルに分解します。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に置き換えられています。 パーシャルは共有コードなので、コントローラアクションによって設定された特定のインスタンス変数に依存しないようにするのがベストプラクティスです。その代わりに、パーシャルに記事をローカル変数として渡します。

app/views/articles/new.html.erbを[render](https://api.rubyonrails.org/classes/ActionView/Helpers/RenderingHelper.html#method-i-render)を使用してパーシャルを使用するように更新しましょう。

<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 %>

ヒント:パーシャルについて詳しくは、Railsのレイアウトとレンダリング § パーシャルの使用を参照してください。

7.4.2 完了

記事の編集ページにアクセスすることで、記事を更新できるようになりました。例:http://localhost:3000/articles/1/edit。最後に、app/views/articles/show.html.erbの最下部に編集ページへのリンクを追加しましょう。

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
</ul>

7.5 記事の削除

最後に、CRUDの「D」(Delete)に到達します。リソースの削除は作成や更新よりも簡単なプロセスです。ルートとコントローラアクションのみが必要です。そして、リソースフルなルーティング(resources :articles)は、ArticlesControllerdestroyアクションにDELETE /articles/:idリクエストをマッピングするルートを既に提供しています。

したがって、app/controllers/articles_controller.rbupdateアクションの下に典型的な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](https://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-destroy)を呼び出します。その後、ルートパスにブラウザをリダイレクトし、ステータスコード[303 See Other](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303)を返します。

記事へのメインアクセスポイントがルートパスであるため、ルートパスにリダイレクトすることを選択しました。ただし、他の状況では、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」リンクのdata-turbo-methoddata-turbo-confirmのHTML属性を設定しています。これらの属性は、デフォルトで新しいRailsアプリケーションに含まれている[Turbo](https://turbo.hotwired.dev/)にフックされます。`data-turbo-method="delete"`は、リンクが`GET`リクエストではなく`DELETE`リクエストを行うようにします。`data-turbo-confirm="Are you sure?"`は、リンクがクリックされたときに確認ダイアログが表示されるようにします。ユーザーがダイアログをキャンセルすると、リクエストは中止されます。

以上です!記事の一覧表示、表示、作成、更新、削除ができるようになりました!InCRUDable!

8 2つ目のモデルの追加

アプリケーションに2つ目のモデルを追加する時が来ました。2つ目のモデルは、記事へのコメントを扱います。

8.1 モデルの生成

前に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

これは、先ほど見たArticleモデルと非常に似ています。違いは、belongs_to :articleという行で、Active Recordの関連付けを設定していることです。関連付けについては、このガイドの次のセクションで少し学びます。 シェルコマンドで使用される(:references)キーワードは、モデルのための特別なデータ型です。 これにより、提供されたモデル名に_idが追加された新しい列がデータベーステーブルに作成され、整数値を保持することができます。より良い理解を得るために、マイグレーションを実行した後にdb/schema.rbファイルを分析してください。

モデルに加えて、Railsは対応するデータベーステーブルを作成するためのマイグレーションも作成しました。

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という名前の整数列、それに対するインデックス、およびarticlesテーブルのid列を指す外部キー制約を作成します。マイグレーションを実行してください。

$ bin/rails db:migrate

Railsは、現在のデータベースに対してまだ実行されていないマイグレーションのみを実行するように賢く設計されているため、この場合は次のように表示されます。

==  CreateComments: migrating =================================================
-- create_table(:comments)
   -> 0.0115s
==  CreateComments: migrated (0.0119s) ========================================

8.2 モデルの関連付け

Active Recordの関連付けを使用すると、2つのモデル間の関係を簡単に宣言できます。コメントと記事の場合、関係を次のように書くことができます。

  • 各コメントは1つの記事に所属します。
  • 1つの記事には複数のコメントがあります。

実際には、これはRailsがこの関連付けを宣言するために使用する構文に非常に近いです。すでにCommentモデル(app/models/comment.rb)内のコード行を見たことがあります。それにより、各コメントが記事に所属するようになります。

class Comment < ApplicationRecord
  belongs_to :article
end

関連付けのもう一方の側面を追加するために、app/models/article.rbを編集する必要があります。

class Article < ApplicationRecord
  has_many :comments

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

これらの2つの宣言により、多くの自動動作が可能になります。たとえば、記事を含むインスタンス変数@articleがある場合、@article.commentsを使用してその記事に所属するすべてのコメントを配列として取得できます。

Active Recordの関連付けの詳細については、Active Record Associationsガイドを参照してください。

8.3 コメントのルートの追加

articlesコントローラと同様に、commentsを表示するためのルートを追加する必要があります。再度、config/routes.rbファイルを開き、次のように編集します。

Rails.application.routes.draw do
  root "articles#index"

  resources :articles do
    resources :comments
  end
end

これにより、commentsarticlesのネストされたリソースとして作成されます。これは、記事とコメントの階層関係をキャプチャするための別の部分です。

ルーティングの詳細については、Rails Routingガイドを参照してください。

8.4 コントローラの生成

モデルができたので、次は対応するコントローラを作成することにします。前と同じジェネレータを使用します。

$ bin/rails generate controller Comments

これにより、3つのファイルと1つの空のディレクトリが作成されます。

ファイル/ディレクトリ 目的
app/controllers/comments_controller.rb Commentsコントローラ
app/views/comments/ コントローラのビューがここに保存されます
test/controllers/comments_controller_test.rb コントローラのテスト
app/helpers/comments_helper.rb ビューヘルパーファイル

ブログの場合と同様に、読者は記事を読んだ直後にコメントを作成し、コメントを追加した後は記事の表示ページに戻されます。そのため、CommentsControllerはコメントを作成し、スパムコメントが到着した場合に削除するためのメソッドを提供するために存在します。

まず、Articleの表示テンプレート(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の表示ページにフォームが追加され、CommentsControllercreateアクションを呼び出して新しいコメントを作成します。ここでのform_with呼び出しでは、配列が使用され、/articles/1/commentsのようなネストされたルートが作成されます。 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モデルのfindメソッドを呼び出して、対象の記事を取得しています。

さらに、コードは関連付けに利用できるいくつかのメソッドを活用しています。@article.comments上のcreateメソッドを使用して、コメントを作成して保存しています。これにより、コメントが特定の記事に所属するように自動的にリンクされます。

新しいコメントを作成したら、article_path(@article)ヘルパーを使用してユーザーを元の記事に戻します。既に見たように、これはArticlesControllershowアクションを呼び出し、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 %>

これで、ブログに記事とコメントを追加し、適切な場所に表示することができます。

Article with Comments

9 リファクタリング

記事とコメントが機能するようになったので、app/views/articles/show.html.erbテンプレートを見てみましょう。長くて扱いにくいです。パーシャルを使用して整理することができます。

9.1 パーシャルコレクションのレンダリング

まず、記事のすべてのコメントを表示するためのコメントパーシャルを作成します。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コレクションを反復処理するときに、各コメントをパーシャルと同じ名前のローカル変数(この場合はcomment)に代入し、パーシャル内で使用できるようにします。

9.2 パーシャルフォームのレンダリング

次に、新しいコメントセクションを独自のパーシャルに移動しましょう。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を定義しています。Railsは、その文字列内のスラッシュを認識し、app/views/commentsディレクトリ内の_form.html.erbファイルをレンダリングすることを理解します。

@articleオブジェクトは、ビューでレンダリングされるすべてのパーシャルで利用できます。なぜなら、それをインスタンス変数として定義したからです。

9.3 Concernの使用

Concernは、大きなコントローラーやモデルを理解しやすく管理しやすくするための方法です。また、複数のモデル(またはコントローラー)で同じConcernを共有する場合に再利用性の利点もあります。Concernは、モデルまたはコントローラーが担当する機能の明確に定義されたスライスを表すメソッドを含むモジュールを使用して実装されます。他の言語では、モジュールはしばしばミックスインとして知られています。 コントローラやモデルでconcernsを使用するには、他のモジュールと同じように使用できます。rails new blogでアプリを作成したときに、app/内に2つのフォルダが作成されました。

app/controllers/concerns
app/models/concerns

以下の例では、concernを使用することでメンテナンス性とDRYさを向上させることができる、ブログの新しい機能を実装します。

ブログ記事にはさまざまなステータスがあります。例えば、誰にでも表示される(つまりpublic)、または著者のみに表示される(つまりprivate)などです。また、非表示になっているが取得可能な状態(つまりarchived)もあります。コメントも同様に非表示または表示される場合があります。これは、各モデルにstatusカラムを使用して表すことができます。

まず、以下のマイグレーションを実行してArticlesCommentsstatusを追加します。

$ bin/rails generate migration AddStatusToArticles status:string
$ bin/rails generate migration AddStatusToComments status:string

次に、生成されたマイグレーションでデータベースを更新します。

$ bin/rails db:migrate

既存の記事とコメントのステータスを選択するには、生成されたマイグレーションファイルにdefault: "public"オプションを追加してマイグレーションを再度実行することができます。または、railsコンソールでArticle.update_all(status: "public")Comment.update_all(status: "public")を呼び出すこともできます。

マイグレーションについて詳しくは、Active Record Migrationsを参照してください。

また、app/controllers/articles_controller.rbstrong parameterの一部として:statusキーを許可する必要があります。


  private
    def article_params
      params.require(:article).permit(:title, :body, :status)
    end

そして、app/controllers/comments_controller.rbでも同様に設定します。


  private
    def comment_params
      params.require(:comment).permit(:commenter, :body, :status)
    end

articleモデル内では、bin/rails db:migrateコマンドを使用してstatusカラムを追加するマイグレーションを実行した後に、次のように追加します。

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)では、アーカイブされたコメントを表示しないようにします。

<% unless comment.archived? %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>

ただし、モデルを再度確認すると、ロジックが重複していることがわかります。将来的にブログの機能を拡張し、プライベートメッセージなどを含める場合、ロジックが再度重複する可能性があります。ここでconcernsが役立ちます。

concernは、モデルの責任範囲の一部にのみ責任を持つものです。concern内のメソッドはすべてモデルの可視性に関連しています。新しいconcern(モジュール)をVisibleと呼びましょう。app/models/concerns内にvisible.rbという新しいファイルを作成し、モデルで重複していたすべてのステータスメソッドを保存します。

app/models/concerns/visible.rb

module Visible
  def archived?
    status == 'archived'
  end
end

concernにステータスのバリデーションを追加することもできますが、これはやや複雑です。バリデーションはクラスレベルで呼び出されるメソッドです。ActiveSupport::ConcernAPIガイド)を使用すると、簡単に含めることができます。

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モジュールを含めることができます。

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にも追加することができます。メインページに公開された記事やコメントの数を表示したい場合、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

ビューでは、通常のクラスメソッドのように呼び出すことができます。

<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>

10 コメントの削除

ブログのもう一つの重要な機能は、スパムコメントを削除できることです。これを行うには、ビューにリンクを実装し、CommentsControllerdestroyアクションを追加する必要があります。

まず、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/:idCommentsControllerに送信され、コメントを削除するために使用されます。そのため、コントローラーにdestroyアクションを追加します(app/controllers/comments_controller.rb)。

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.commentsコレクション内のコメントを特定し、データベースから削除し、記事の表示アクションに戻ります。

10.1 関連オブジェクトの削除

記事を削除すると、関連するコメントも削除する必要があります。そうしないと、データベースに単にスペースを占有するだけになります。Railsでは、関連のdependentオプションを使用してこれを実現することができます。app/models/article.rbのArticleモデルを以下のように変更します。

class Article < ApplicationRecord
  include Visible

  has_many :comments, dependent: :destroy

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

11 セキュリティ

11.1 ベーシック認証

ブログをオンラインで公開する場合、誰でも記事を追加、編集、削除したり、コメントを削除することができます。

Railsには、このような状況でうまく機能するHTTP認証システムが用意されています。

ArticlesControllerでは、各アクションへのアクセスを認証されていない場合にブロックする方法が必要です。Railsのhttp_basic_authenticate_withメソッドを使用することで、要求されたアクションへのアクセスが許可される場合、そのメソッドを使用することができます。

認証システムを使用するために、ArticlesControllerの先頭に指定します。この場合、indexshow以外のすべてのアクションでユーザーが認証されるようにしたいので、次のように記述します。

class ArticlesController < ApplicationController

  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]

  def index
    @articles = Article.all
  end

  # 省略

コメントの削除も認証されたユーザーのみ許可するようにしたいので、CommentsControllerapp/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

  # 省略

これで、新しい記事を作成しようとすると、基本的なHTTP認証のチャレンジが表示されます。

Basic HTTP Authentication Challenge

正しいユーザー名とパスワードを入力すると、異なるユーザー名とパスワードが必要になるか、ブラウザが閉じられるまで認証が維持されます。 Railsアプリケーションでは、他の認証方法も利用できます。Rails向けの人気のある認証アドオンには、Devise railsエンジンとAuthlogic gemがあります。他にもいくつかの認証方法があります。

11.2 その他のセキュリティに関する考慮事項

セキュリティは、特にWebアプリケーションにおいて広範かつ詳細な領域です。Railsアプリケーションのセキュリティについては、Ruby on Railsセキュリティガイドで詳しく説明されています。

12 次は何ですか?

最初のRailsアプリケーションを見たので、自由に更新したり、自分で実験したりすることができます。

覚えておいてください、すべてを一人でやる必要はありません。Railsの使用方法に関するサポートが必要な場合は、次のサポートリソースを参照してください。

13 設定の注意点

Railsで作業する最も簡単な方法は、すべての外部データをUTF-8で保存することです。そうしないと、RubyライブラリやRailsは、ネイティブデータをUTF-8に変換できることが多いですが、これは常に信頼性がありません。したがって、すべての外部データがUTF-8であることを確認する方が良いです。

この領域でミスをした場合、最も一般的な症状は、ブラウザに黒いダイヤモンドと内部に疑問符が表示されることです。もう一つの一般的な症状は、"ü"のような文字が表示されることです。Railsは、これらの問題の一般的な原因を軽減するために、自動的に検出および修正できるいくつかの内部手順を実行します。ただし、UTF-8以外の形式で保存されている外部データがある場合、Railsでは自動的に検出および修正できないこの種の問題が発生することがあります。

UTF-8以外の非常に一般的なデータソースは次のとおりです。

  • テキストエディタ:ほとんどのテキストエディタ(TextMateなど)は、ファイルをUTF-8で保存するようにデフォルトになっています。テキストエディタがそうでない場合、テンプレートに入力した特殊文字(例:é)がブラウザでダイヤモンドと疑問符の内部で表示されることがあります。これはi18nの翻訳ファイルにも適用されます。UTF-8をデフォルトにしないほとんどのエディタ(Dreamweaverの一部のバージョンなど)は、デフォルトをUTF-8に変更する方法を提供しています。変更してください。
  • データベース:Railsは、データベースからのデータをUTF-8に変換することをデフォルトで行います。ただし、データベースが内部的にUTF-8を使用していない場合、ユーザーが入力したすべての文字を保存できない場合があります。たとえば、データベースが内部的にLatin-1を使用している場合、ユーザーがロシア語、ヘブライ語、または日本語の文字を入力した場合、データはデータベースに入力された時点で永久に失われます。可能であれば、データベースの内部ストレージにUTF-8を使用してください。

フィードバック

このガイドの品質向上にご協力ください。

タイポや事実の誤りを見つけた場合は、ぜひ貢献してください。 開始するには、ドキュメントへの貢献セクションを読んでください。

不完全なコンテンツや最新でない情報も見つかるかもしれません。 メインのドキュメントに不足しているドキュメントを追加してください。 修正済みかどうかは、まずEdge Guidesを確認してください。 スタイルと規約については、Ruby on Rails Guides Guidelinesを確認してください。

修正すべき点を見つけたが、自分で修正できない場合は、 問題を報告してください

そして最後に、Ruby on Railsのドキュメントに関するあらゆる議論は、公式のRuby on Railsフォーラムで大歓迎です。