Rails をはじめよう

このガイドでは、Ruby on Rails (以下 Rails) を初めて設定して実行するまでを解説します。

このガイドの内容:

1 本ガイドの前提条件

本ガイドは、ゼロからRailsアプリケーションを構築したいと考えている初心者を対象にしています。読者がRailsの経験がないことを前提としています。ただし、このドキュメントを最大限に活用するために、以下のソフトウェアがインストールされ、利用可能な状態になっていることを前提としています。

  • Ruby 1.9.3以降。
  • RubyGemsパッケージングシステム。これはRuby 1.9以降であればRubyをインストールすると自動的に導入されます。RubyGemsの詳細については、RubyGemsガイドを参照してください。
  • SQLite3データベースがインストールされ、正常に動作する状態になっていること。

Railsとは、Rubyプログラミング言語の上で動作するWebアプリケーションフレームワークです。 Rubyの経験がまったくない場合、Railsを学ぶのはかなり大変な作業になるでしょう。Rubyを学ぶための精選されたオンラインリソース一覧はたくさんありますので、その中から以下をご紹介します。

これらはいずれもよくできていますが、中にはRubyのバージョンが1.6など古いものもありますのでご注意ください。また、バージョン1.8を対象にしているものが多く、Railsでの日常的な開発に使用されている新しい文法が含まれていないこともあります。

2 Railsとは何か

Railsとは、Rubyプログラミング言語で書かれたWebアプリケーションフレームワークです。 Railsは、あらゆる開発者がWebアプリケーションの開発を始めるうえで必要となる作業やリソースを事前に仮定して準備しておくことで、Webアプリケーションをより簡単にプログラミングできるように設計されています。他の多くの言語によるWebアプリケーションフレームワークと比較して、アプリケーションを作成する際のコード量がより少なくて済むにもかかわらず、より多くの機能を実現できます。 Rails経験の長い多くの開発者から、おかげでWebアプリケーションの開発がとても楽しくなったという意見をいただいています。

Railsは、最善の開発方法というものを1つに定めるという、ある意味大胆な判断に基いて設計されています。Railsは、何かをなすうえで最善の方法というものが1つだけあると仮定し、それに沿った開発を全面的に支援します。言い換えれば、ここで仮定されている理想の開発手法に沿わない別の開発手法は行いにくくなるようにしています。この「The Rails Way」、「Rails流」とでもいうべき手法を学んだ人は、開発の生産性が著しく向上することに気付くでしょう。従って、Rails開発において別の言語環境での従来の開発手法に固執し、他所で学んだパターンを強引に適用しようとすると、せっかくの開発が楽しくなくなってしまうでしょう。

Railsの哲学には、以下の2つの主要な基本理念があります。

  • 同じことを繰り返すな (Don't Repeat Yourself: DRY): DRYはソフトウェア開発上の原則であり、「システムを構成する知識のあらゆる部品は、常に単一であり、明確であり、信頼できる形で表現されていなければならない」というものです。同じコードを繰り返し書くことを徹底的に避けることで、コードが保守しやすくなり、容易に拡張できるようになり、そして何よりバグを減らすことができます。
  • 設定より規約が優先される (Convention Over Configuration): Railsでは、Webアプリケーションで行われるさまざまなことを実現するための最善の方法を明確に思い描いており、Webアプリケーションの各種設定についても従来の経験や慣習を元に、それらのデフォルト値を定めています。このようにある種独断でデフォルト値が決まっているおかげで、開発者の意見をすべて取り入れようとした自由過ぎるWebアプリケーションのように、開発者が延々と設定ファイルを設定して回らずに済みます。

3 Railsプロジェクトを新規作成する

本ガイドを活用するための最善の方法は、以下の手順を文字どおり1つずつ実行し、手順を取りこぼさないようにすることです。取りこぼしがあると、その後の手順が期待どおりに進まない可能性があります。この手順で作成された完全なRailsプロジェクトのコードはコチラで公開されているので、躓いてしまったときなどに参照してください。また、完成したサンプルアプリケーションはコチラで公開されています。

本ガイドの手順に従うことで、blogという名前の非常にシンプルなブログのRailsプロジェクトを作成できます。Railsアプリケーションを構築する前に、Rails本体がインストールされていることを確認してください。

以下の例では、Unix系OSのプロンプトとして$記号を使用していますが、これはカスタマイズ可能であり、自分の環境では異なる記号になっていることもあります。Windowsではc:\source_code>のように表示されます。

3.1 Railsのインストール

ターミナル (コマンドプロンプトとも言います) ウィンドウを開いてください。Mac OS Xの場合、ターミナル (Terminal.app) という名前のアプリケーションを実行します。Windowsの場合は[スタート] メニューから [ファイル名を指定して実行] をクリックして'cmd.exe'と入力します。$で始まる記述はコマンド行なので、これらはコマンドラインに入力して実行してください。続いて現在インストールされているRubyのバージョンが最新のものであることを確認してください。

RubyやRuby on Railsを素早くインストールするためのツールは多数存在します。Windowsユーザーの場合はRailsインストーラをお使いください。Mac OS XユーザーはTokaidoをお使いください。(訳注: 具体的なインストール方法についてはRailsチュートリアル 1.2 さっそく動作させるを参照してください)

$ ruby -v
ruby 2.0.0p353

自分のPC環境にRubyがインストールされていない場合は、ruby-lang.org を参照して、自分の環境に合うインストール方法を参照してください。

多くのUnix系OSには実用的なバージョンのSQLite3が同梱されています。 Windowsユーザーなどその他の環境の方はSQLite3のインストール方法を参照してください。 正しくインストールされていること、PATH環境変数が正しく通っていることを確認してください。

$ sqlite3 --version

上を実行することでバージョンを確認できます。

Railsをインストールするには、gem installコマンドを実行します。このコマンドはRubyGemsによって提供されます。

$ gem install rails

以下のコマンドを実行することで、すべて正常にインストールできたかどうかを確認できます。

$ rails --version

"Rails 4.2.1"のように表示されれば、次に進むことができます。

3.2 ブログアプリケーションを作成する

Railsには、ジェネレータという多数のスクリプトが付属しており、これらが特定のタスクを開始するために必要なものを自動的に作り出してくれるので、開発が容易になります。その中から、新規アプリケーション作成用のジェネレータを使ってみましょう。これを実行すればRailsアプリケーションの基本的な部分が提供されるので、開発者が自分でこれらを作成する必要はありません。

ジェネレータを実行するには、ターミナルを開き、Railsファイルを作成したいディレクトリに移動して、以下を入力します。

$ rails new blog

これにより、Blogという名前のRails アプリケーションがblogディレクトリに作成され、Gemfileというファイルで指定されているgemファイルがbundle installコマンドによってインストールされます。

rails new -hを実行すると、Railsアプリケーションビルダで使用できるすべてのコマンドラインオプションを確認できます。

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

$ cd blog

blogディレクトリの下には多数のファイルやフォルダが生成されており、これらがRailsアプリケーションを構成しています。このチュートリアルではほとんどの作業をappディレクトリで行いますが、Railsが生成したファイルとフォルダについてここで簡単に説明しておきます。

ファイル/フォルダ 目的
app/ ここにはアプリケーションのコントローラ、モデル、ビュー、ヘルパー、メイラー、そしてアセットが置かれます。以後、本ガイドでは基本的にこのディレクトリを中心に説明を行います。
bin/ ここにはアプリケーションを起動したりデプロイしたりするためのRailsスクリプトなどのスクリプトファイルが置かれます。
config/ アプリケーションの設定ファイル (ルーティング、データベースなど) がここに置かれます。詳細についてはRailsアプリケーションを設定する を参照してください。
config.ru アプリケーションの起動に必要となる、Rackベースのサーバー用のRack設定ファイルです。
db/ 現時点のデータベーススキーマと、データベースマイグレーションファイルが置かれます。
Gemfile
Gemfile.lock
これらのファイルは、Railsアプリケーションで必要となるgemの依存関係を記述します。この2つのファイルはBundler gemで使用されます。Bundlerの詳細についてはBundlerのWebサイトを参照してください。
lib/ アプリケーションで使用する拡張モジュールが置かれます。
log/ アプリケーションのログファイルが置かれます。
public/ このフォルダの下にあるファイルは外部 (インターネット) からそのまま参照できます。静的なファイルやコンパイル済みアセットはここに置きます。
Rakefile このファイルには、コマンドラインから実行できるタスクを記述します。ここでのタスク定義は、Rails全体のコンポーネントに対して定義されます。独自のRakeタスクを定義したい場合は、Rakefileに直接書くと権限が強すぎるので、なるべくlib/tasksフォルダの下にRake用のファイルを追加するようにしてください。
README.rdoc アプリケーションの概要を説明するマニュアルをここに記入します。このファイルにはアプリケーションの設定方法などを記入し、これさえ読めば誰でもアプリケーションを構築できるようにしておく必要があります。
test/ Unitテスト、フィクスチャなどのテスト関連ファイルをここに置きます。テストについてはRailsアプリケーションをテストするを参照してください。
tmp/ キャッシュ、pid、セッションファイルなどの一時ファイルが置かれます。
vendor/ サードパーティによって書かれたコードはすべてここに置きます。通常のRailsアプリケーションの場合、外部からのgemファイルをここに置きます。

4 Hello, Rails!

手始めに、画面に何かテキストを表示してみましょう。そのためには、Railsアプリケーションサーバーを起動しなくてはなりません。

4.1 Webサーバーを起動する

先ほど作成したRailsアプリケーションは、既に実行可能な状態になっています。Webアプリケーションを開発用のPCで実際に動かしてこのことを確かめてみましょう。blogディレクトリに移動し、以下のコマンドを実行します。

$ rails server

CoffeeScriptをJavaScriptにコンパイルするにはJavaScriptランタイムが必要です。ランタイムが環境にない場合はexecjsエラーが発生します。Mac OS XやWindowsにはJavaScriptランタイムが同梱されています。Railsが新規アプリケーション用に生成するGemfileにはtherubyracerというgemがコメントアウトされた状態で含まれており、必要であればこのgemのコメントアウトを解除して有効にすることもできます。therubyrhinoはJRubyユーザー向けに推奨されているランタイムであり、JRuby環境下ではデフォルトでアプリケーションのGemfileに追加されます。サポートされているランタイムの詳細についてはExecJSで確認できます。

Railsで起動されるWebサーバーは、Rubyにデフォルトで付属しているWEBrickです。Webアプリケーションが実際に動作しているところを確認するには、ブラウザを開いて http://localhost:3000 を表示してください。以下のようなRailsのデフォルト情報ページが表示されます。

Welcome画面のスクリーンショット

Webサーバーを停止するには、実行されているターミナルのウィンドウでCtrl + Cキーを押します。コマンドプロンプトのカーソルがふたたび表示されれば、サーバーは停止しています。Mac OS Xを含む多くのUnix系OSではプロンプトとしてドル記号$が使用されます。一般に、Railsの開発モードではファイルに変更を加えた場合でもサーバーを再起動する必要はありません。ファイルの変更は自動的にサーバーに反映されます(訳注: libファイルやapplication.rbなど一部の設定ファイルなどはサーバーを再起動しないと読み込まれません)。

Railsの初期画面である「Welcome aboard」ページは、新しいRailsアプリケーションの スモークテスト として使えます。このページが表示されれば、サーバーが正常に動作していることまでは確認できたことになります。 About your application's environment リンクをクリックすれば、アプリケーション環境の概要を確認できます。

4.2 Railsに"Hello"と挨拶させる

Railsに"Hello"と表示するには、最低でも コントローラビュー が必要です。

コントローラは、アプリケーションに対する特定のリクエストを受け取って処理するのが役割です。ルーティング は、リクエストをどのコントローラに割り振るかを決定するためのものです。1つのコントローラに対して複数のルーティングがあるのはよくあることです。そしてコントローラにはいくつかの アクション があります。いくつかの異なるルーティングに対して、それぞれ異なるアクションを割り当てることができます。それぞれのアクションは、情報を集めてビューに送り出すのが役割です。

ビューの役割は、この情報をユーザーが読める形式で表示することです。ここで気を付けていただきたい重要な違いは、表示する情報を集めるのは コントローラ であって、ビューではないということです。ビューは、コントローラが作成した情報に対して余計なことをせずに表示する必要があります。ビューテンプレートで使用できる言語は、デフォルトではeRuby (ERBとも、Embedded Rubyとも呼ばれます) が使用されます (訳注: 近年はhamlテンプレートがよく使われます)。ERBで書かれたコードは、ユーザーに表示される前のリクエストサイクルでRailsによって処理されます。

コントローラを新規作成するには、コントローラ用のジェネレータを実行します。ここでは以下のように、welcomeという名前のコントローラの中にindexというアクションを作成するよう指定します。

$ rails generate controller welcome index

Railsは指定どおりコントローラを作成し、関連ファイルやルーティングも設定してくれます。

create  app/controllers/welcome_controller.rb
route  get 'welcome/index'
invoke  erb
create    app/views/welcome
create    app/views/welcome/index.html.erb
invoke  test_unit
create    test/controllers/welcome_controller_test.rb
invoke  helper
create    app/helpers/welcome_helper.rb
invoke    test_unit
create      test/helpers/welcome_helper_test.rb
invoke  assets
invoke    coffee
create      app/assets/javascripts/welcome.js.coffee
invoke    scss
create      app/assets/stylesheets/welcome.css.scss

この中でもっとも重要なのはもちろんコントローラです。welcomeコントローラはapp/controllers/welcome_controller.rbに作成され、対応するindexビューがapp/views/welcome/index.html.erbに作成されます。

テキストエディタでapp/views/welcome/index.html.erbを開いてみましょう。ファイルの中身をすべて削除し、以下の1行に置き換えてください。

<h1>Hello, Rails!</h1>

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

以上でコントローラとビューが作成されました。Railsに"Hello, Rails!"と表示させてみましょう。ここでは、サイトのルートURL http://localhost:3000 にアクセスしたときにこのメッセージが表示されるようにします。現時点のルートURLでは、デフォルトの"Welcome aboard"が表示されていますので、これを変更します。

Railsで表示させたい実際のホームページの場所を指定します。

エディタでconfig/routes.rbを開いてください。

Rails.application.routes.draw do
  get 'welcome/index'

  # The priority is based upon order of creation:
  # first created -> highest priority.
  #
  # You can have the root of your site routed with "root"
  # root 'welcome#index'
  #
  # ...

上はアプリケーションの ルーティングファイル の内容です。外部からのリクエストをどのようにコントローラとアクションに振り分けるかを、DSL (ドメイン特化言語: domain-specific language) という特殊な言語を使用してこのファイル内に記述します。デフォルトのconfig/routes.rbには多数のルーティングサンプルがコメント行に記載されており、そのうちの1つに、サイトのルートにアクセスがあったときに接続するコントローラとアクションを指定する方法が書かれています。rootで始まっている行を見つけ、コメント記号を外してください。以下のようになるはずです。

root 'welcome#index'

root 'welcome#index'と記述することで、アプリケーションのルートURLへのアクセスをwelcomeコントローラのindexアクションに割り当てるようRailsに指示が伝わります。同様に、get 'welcome/index'http://localhost:3000/welcome/indexというリクエストをwelcomeコントローラのindexアクションに割り当てます。後者は先ほどコントローラ用ジェネレータ (rails generate controller welcome index) を実行した時に自動的に作成されています。

ブラウザでhttp://localhost:3000を表示してみましょう (ジェネレータを実行するためにRailsを止めていた場合はrails serverを再実行してください)。app/views/welcome/index.html.erbの中に書いた"Hello, Rails!"という文字がブラウザ上に表示されるはずです。WelcomeControllerindexアクションへのルーティングが新たに形成され、ビューが正しく表示されたことがこれで確認できました。

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

5 アプリケーションの実装と実行

以上で、コントローラとアクションとビューの作成方法を説明いたしました。ここからはもう少しブログらしい体裁を整えていきましょう。

今度はBlogアプリケーションに新しく リソース を作成します。ここで言う「リソース」とは、記事、人、動物などのよく似たオブジェクト同士が集まったものを指します。 リソースに対して作成 (create)、読み出し (read)、更新 (update)、削除 (destroy) の4つの操作を行なうことができるようになっており、これらの操作の頭文字を取って CRUD と呼ばれます。

Railsのルーティングにはresourcesメソッドがあり、これを使用してRESTリソースへの標準的なルーティングを宣言できます (訳注: RESTについてはWikipediaを参照してください)。たとえばconfig/routes.rbarticleリソース を宣言すると以下のようになります。

Rails.application.routes.draw do

  resources :articles

  root 'welcome#index'
end

コマンドラインでrake routesコマンドを実行すると、標準的なRESTfulアクションへのルーティングがすべて定義されていることが確認できます。以下の出力のprefix列や他の列については後ほど解説しますが、ここでご注目いただきたいのは、Railsは「articles」というリソース名から単数形の「article」を推測し、両者をその意味にそって使い分けているという点です。prefix列で単一の項目には単数形のarticle、複数項目を扱う場合には複数形のarticlesが使われているという具合です。

$ rake routes
      Prefix Verb   URI Pattern                  Controller#Action
    articles GET    /articles(.:format)          articles#index
             POST   /articles(.:format)          articles#create
new_article GET    /articles/new(.:format)      articles#new
edit_article GET    /articles/:id/edit(.:format) articles#edit
     article GET    /articles/:id(.:format)      articles#show
             PATCH  /articles/:id(.:format)      articles#update
             PUT    /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy
        root GET    /                            welcome#index

次の節では、アプリケーションで新しい記事を作成してそれを表示する機能を追加しましょう。これはCRUDでいう"C" (作成) と"R" (読み出し) の操作に相当します。作成するフォームは以下のような感じになります。

新規記事投稿フォーム

これだけでは飾り気がなさすぎる感じもしますが、今はこれでよしとします。スタイルの追加はその後に行います。

5.1 土台を設置する

最初に、新規記事を作成するための場所がアプリケーション内に必要です。置き場所はやはり/articles/newでしょう。ルーティングは既に定義されているので、リクエストはアプリケーションの/articles/newに送られます。ブラウザでhttp://localhost:3000/articles/newを開くと、今はルーティングエラーが表示されます。

Another routing error, uninitialized constant ArticlesController

このエラーが発生したのは、ルーティングで指定された先に、リクエストを処理するように定義されたコントローラが見つからないためです。この問題を解決するには、それに対応するArticlesControllerを作成すればよいのです。以下のコマンドを実行して解決します。

$ rails g controller articles

今作成されたapp/controllers/articles_controller.rbをエディタで開くと、以下のような空のコントローラが作成されています。

class ArticlesController < ApplicationController
end

コントローラは、ApplicationControllerを継承する形で定義されるシンプルなクラスです。コントローラの内側で定義されたメソッドは、コントローラのアクションになります。制作中のブログアプリケーションでは、これらのアクションがarticleに対するCRUD操作を担当します。

Rubyのメソッドはpublicprivateprotectedに分けられますが、コントローラのアクションになれるのはpublicメソッドだけです。詳細についてはProgramming Rubyを参照してください。

ブラウザのhttp://localhost:3000/articles/newを再表示すると、今度は別のエラーが表示されます。

Unknown action new for ArticlesController!

生成したArticlesControllerコントローラにnewアクションが見つからないというエラーです。これは、Railsでアクションを指定せずに生成したコントローラは中身が空のままになるためです。

コントローラ内にアクションを手作りするには、単にコントローラ内でメソッドを定義すればよいのです。app/controllers/articles_controller.rbをエディタで開き、ArticlesControllerクラスの内側に以下のようにnewメソッドを作成します。

def new
end

ArticlesControllerコントローラにnewメソッドを作成してからブラウザでhttp://localhost:3000/articles/newを再表示すると、今度はまた違うエラーが表示されます。

Template is missing for articles/new

Railsでは、このシンプルなアクションに関連付けられたビューがあり、そこで情報を表示できることを期待しています。アクションは定義されましたが、これに関連付けられたビューがないのでエラーが表示されます。

なお、上の画像ではエラーメッセージの下の部分は切り捨ててあります。完全なメッセージは以下のような感じになります。

Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"

何だかたくさんのテキストが表示されました。それぞれの部分がどういう意味なのかを見てみましょう。

最初の部分では、どのテンプレートが見当たらないかが示されています。ここではarticles/newというテンプレートがあるはずだと言っています。Railsは最初にこのテンプレートを探します。見つからない場合は次にapplication/newというテンプレートがあるかどうかを探します。application/newにテンプレートがあるかどうかを探しているのは、ArticlesControllerコントローラはApplicationControllerコントローラを継承しているからです。

次の部分にはハッシュがあります。ハッシュの:localeキーは、単にそのテンプレートが何語向けなのかを示しています。デフォルトでは英語 ("en") テンプレートが使用されます。次の:formatsキーは、応答時に返されるテンプレートのフォーマットを示します。デフォルトのフォーマットは:htmlなので、RailsはHTMLテンプレートを探します。最後の:handlersキーは、テンプレートを描画するときに使用される テンプレートハンドラ を示します。HTMLテンプレートで最もよく使用されるのは:erbです。同様に、XMLテンプレートには:builderが指定が、CoffeeScriptには:coffeeが最もよく使用されます。

最後の部分では、Railsがテンプレートを探した場所が示されています。このブログアプリケーションのようなシンプルなRailsアプリケーションでは、テンプレートの置き場所は1箇所ですが、複雑なアプリケーションではさまざまな場所にテンプレートが置かれることもあります。

この場合、テンプレートをapp/views/articles/new.html.erbに置くのが最もシンプルです。テンプレートのファイル名に付いている拡張子に気を付けてください。1つ目の拡張子はテンプレートの フォーマット を表し、2つ目の拡張子は使用される ハンドラー を示します。Railsは、articles/newというテンプレートをアプリケーションのapp/viewsで探そうとします。ここではテンプレートのフォーマットはHTMLでなければならず、ハンドラーはerbbuildercoffeeのいずれかでないといけないということになります。ここで作成しようとしているのは新しいHTMLフォームなので、ERB言語が使用されます。従って、テンプレートのファイル名はarticles/new.html.erbでなければならず、アプリケーションのapp/viewsディレクトリの下になければならないことになります。

それではapp/views/articles/new.html.erbを作成し、その中に以下のように記入しましょう。

<h1>New Article</h1>

http://localhost:3000/articles/newをブラウザで再表示すると、ページにタイトルが表示されるようになりました。ついに、ルーティングとコントローラとアクションとビューが協調して動作するようになりました。いよいよ新規記事を投稿するフォームを作成することにしましょう。

5.2 最初のフォーム

このテンプレート内にフォームを作成するために、form builder を使用します。Railsにはform_forというヘルパーメソッドがあり、主にこれを使用してフォームを作成します。以下のコードをapp/views/articles/new.html.erbに追加して、form_forメソッドを使用できるようにしましょう。

<%= form_for :article do |f| %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

ページをブラウザで再表示すると、先に図に示したフォームの例のとおりにフォームが表示されます。Railsのフォーム作成は非常に簡単です。

form_forメソッドを呼び出すときには、このフォームを識別するためのオブジェクトを渡してください。ここでは:articleというシンボルを渡します。form_forヘルパーは、これを見て何のフォームであるかを知ることができます。このメソッドのブロックの内側はFormBuilderオブジェクトを置きます(fで表すのが通例です)。ここでは2つのラベルと2つのテキストフィールドが置かれ、それぞれタイトルと記事本文になります。最後に、fオブジェクトに対してsubmitを実行すると、フォームの送信ボタンが作成されます。

しかし、このフォームには1つ問題があります。このフォームページのソースを表示して、生成されたHTMLをよく調べてみると、フォームのaction属性の送信先が/articles/newになってしまっています。/articles/newというルーティングは、このフォームを最初に表示するときに使用されるものなので、記入されたフォームの送信先まで同じルーティングにしてしまうのは変です。/articles/newはフォームの表示専用にすべきです。

どうやらフォームの送信先は別のURLにしなければならないようです。送信先の指定はform_for:urlオプションで簡単に指定できます。Railsでは、新しいフォームの送信先となるアクションは"create"にするのが普通ですので、それに従って送信先を変更しましょう。

app/views/articles/new.html.erbをエディタで開き、form_forの行を以下のように変更します。

<%= form_for :article, url: articles_path do |f| %>

この例では、:urlオプションにarticles_pathヘルパーが渡されています。 このときRailsの内部で何が行われているのかを知るために、rake routesの出力結果をもう一度見てみましょう。

$ rake routes
      Prefix Verb   URI Pattern                  Controller#Action
    articles GET    /articles(.:format)          articles#index
             POST   /articles(.:format)          articles#create
new_article GET    /articles/new(.:format)      articles#new
edit_article GET    /articles/:id/edit(.:format) articles#edit
     article GET    /articles/:id(.:format)      articles#show
             PATCH  /articles/:id(.:format)      articles#update
             PUT    /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy
        root GET    /                            welcome#index

articles_pathヘルパーは、articlesという接頭語に関連付けられているURIパターンをフォームの送信先とするようRailsに指示します。そしてこのフォームはデフォルトに従ってPOSTリクエストとしてルーティングに送信されます。そしてこのルーティングは、現在のコントローラであるArticlesControllercreateアクションに関連付けられます。

このフォームと、それに関連付けられたルーティングが定義されることで、フォームに記入して送信ボタンをクリックすると新しい記事作成プロセスが開始されるようになります。この状態でフォームを送信すると、既にお馴染みの以下のエラーが表示されます。

Unknown action create for ArticlesController

そこで今度はArticlesControllerコントローラ内にcreateアクションを作成し、フォームが動作するようにしましょう。

5.3 記事を作成する

"Unknown action"エラーを解消するには、app/controllers/articles_controller.rbファイル内のArticlesControllerクラス内のnewアクションの下にcreateアクションを追加します。

class ArticlesController < ApplicationController 
  def new
  end

  def create
  end
end

修正後フォームを再送信すると、今度はまたしても「a template is missing」エラーが表示されます。ひとまずこのエラーは無視しましょう。createアクションの役割は、記事をデータベースに保存することです。

フォームを送信すると、フォームに含まれるフィールドは パラメータ としてRailsに送信されます。これらのパラメータは、受け取ったコントローラ内のアクションで参照可能になっており、これを使用して特定のタスクを実行します。実際のパラメータがどのようになっているかを確認するために、createアクションに以下の変更を加えてみましょう。

def create
  render plain: params[:article].inspect
end

ここでrenderメソッドは非常に単純なハッシュを引数に取ります。ハッシュのキーはplain、ハッシュの値はparams[:article].inspectです。paramsメソッドは、フォームから送信されてきたパラメータ (つまりフォームのフィールド) を表すオブジェクトです。paramsメソッドはActiveSupport::HashWithIndifferentAccessオブジェクトを返します。文字列またはシンボルを使用して、このオブジェクトのハッシュのキーを指定できます。今回の場合、必要なのはフォームの値のうちの1つだけです。

フォームを再送信してみると、今度はmissing templateエラーが表示されなくなりました。今度は以下が表示されました。

{"title"=>"First article!", "text"=>"This is my first article."}

このアクションは、フォームから送信されたパラメータをそのまま表示するようになりました。しかしこのままでは役に立ちそうにありません。確かにパラメータは表示されるようになりましたが、何の加工もされていません。

5.4 Articleモデルを作成する

Railsのモデルは、単数形の名前を持ち、対応するデータベーステーブル名は複数形で表されるというルールがあります。Railsにはモデル作成用のジェネレータもあり、多くのRails開発者がモデル作成の際に使用しています。モデルを作成するにはターミナルで以下のコマンドを実行します。

$ rails generate model Article title:string text:text

このコマンドを実行すると、Articleモデルが作成されます。その中にはstring型の title 属性とtext型の text 属性が作成されています。これらの属性は、データベースのarticlesテーブルに自動的に追加され、Articleモデルと対応付けられます (訳注: 実際には後述するマイグレーションを行わないとデータベースとの対応付けは完了しません)。

Railsによって多数のファイルが作成されました。ここで必要なのは、app/models/article.rbdb/migrate/20140120191729_create_articles.rbの2つだけです (後者のファイル名には日付が含まれているのでこれと同じにはなりません)。後者のマイグレーションファイルは、データベース構造を作成するためのものであり、この次に説明します。

Active Recordは、データベースのカラム名とモデルの属性を自動的に対応付けるインテリジェントな機能を有しています。このおかげで、Railsのモデルでは属性をいちいち宣言する必要がありません。そうした作業はActive Recordが自動的にやってくれます。

5.5 マイグレーションを実行する

既に見たようにrails generate modelを実行すると データベースマイグレーション ファイルがdb/migrateの下に作成されます。マイグレーションはRubyのクラスであり、データベーステーブルの作成や変更を簡単に行うためのしくみです。マイグレーションを実行するにはrakeコマンドを実行します。マイグレーションを使用して行ったデータベース構成の変更は、後から取り消すことができます。マイグレーションファイルの名前にはタイムスタンプが含まれており、これに基いて、マイグレーションは作成された順に実行されます。

ここでdb/migrate/20140120191729_create_articles.rb ファイルをエディタで開いてみると (タイムスタンプは各自異なることにご注意ください)、以下のようになっています。

class CreateArticles < ActiveRecord::Migration
  def change
    create_table :articles do |t|
      t.string :title
      t.text :text

      t.timestamps
    end
  end
end

上のマイグレーションファイルにはchangeという名前のメソッドが作成されており、マイグレーションの実行時に呼び出されます。このメソッドで定義されてる操作は取り消しが可能です。つまり、Railsはchangeメソッドで行われたマイグレーションを必要に応じて元に戻すことができます。このマイグレーションを実行すると、articlesというテーブルが作成され、文字列カラムとテキストカラムが1つずつ作成されます。Railsは、マイグレーション時に作成日と更新日を追跡するためのタイムスタンプフィールドを2つ作成します。これは指定がなくても自動的に行われます。

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

ここでは、以下のようにrakeコマンドでマイグレーションを実行します。

$ rake db:migrate

マイグレーションコマンドによってArticlesテーブルがデータベース上に作成されます。

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

マイグレーションはデフォルトではdevelopment (開発) 環境で実行されます。そのため、config/database.ymlファイルのdevelopmentセクションで定義されている開発用データベースに対して実行される点にご注意ください。production (本番) 環境など、development以外の環境に対してもマイグレーションを実行したい場合は、rake db:migrate RAILS_ENV=productionのように環境変数を明示的に指定する必要があります。

5.6 コントローラでデータを保存する

ふたたびArticlesControllerに戻りましょう。先ほど作成したArticleモデルを使用して、createアクションを変更しなければなりません。app/controllers/articles_controller.rbをエディタで開き、createアクションを次のように変更します。

def create
  @article = Article.new(params[:article])

  @article.save
  redirect_to @article
end

変更内容を説明します。Railsのすべてのモデルは初期化時に属性(フィールド)を与えられ、それらはデータベースカラムに自動的に対応付けられます。メソッドの1行目ではまさにそれが行われています (取り出したい属性はparams[:article]の中にあります)。次の@article.saveで、このモデルをデータベースに保存します。最後に、ユーザーをshowアクションにリダイレクトします (showアクションはこの後定義します)。訳注: モデルを保持している@articleを指定するだけで、そのモデルを表示するためのshowアクションにリダイレクトされる点にご注目ください。

後に解説しますが、@article.saveは保存に成功したかどうかを真偽値 (trueまたはfalse) で返します。

この時点でブラウザでhttp://localhost:3000/articles/newを表示すると、記事の作成が ほぼ 可能な状態になっています。実際にやってみましょう。すると、以下のようなエラーが表示されます。

Forbidden attributes for new article

Railsにはセキュリティの高いアプリケーションを開発するのに便利な機能が多数あり、ここではその機能に引っかかったのです。これはstrong_parametersと呼ばれるもので、コントローラのアクションで本当に使用してよいパラメータだけを厳密に指定することを強制するものです。

なぜそんな面倒なことをしないといけないのでしょうか。コントローラが受け取ったパラメータをノーチェックでまるごと自動的にモデルに渡せるようにする方が確かに開発は楽なのですが、パラメータの渡し方をこのように便利にしてしまうと、パラメータがチェックされていない点を攻撃者に悪用される可能性があります。たとえば、サーバーへのリクエストに含まれる新規投稿送信フォームに、もともとフォームになかったフィールドが攻撃者によって密かに追加され、それがアプリケーションの整合性を脅かす可能性が考えられます。チェックされていないパラメータをまるごとモデルに保存する行為は、モデルに対する「マスアサインメント」と呼ばれています。これが発生すると、正常なデータの中に悪意のあるデータが含まれてしまう可能性があります。

そこで、コントローラで渡されるパラメータはホワイトリストでチェックし、不正なマスアサインメントを防止する必要があるのです。この場合、createでパラメータを安全に使用するために、titletextパラメータの利用を「許可」し、かつ「必須」であることを指定したいのです。この指定を文法化するために、requireメソッドとpermitメソッドが導入されました。これに基いて、該当行を以下のように変更します。

  @article = Article.new(params.require(:article).permit(:title, :text))

この記法を毎回繰り返すのは煩雑なので、たとえばcreateアクションとupdateアクションで共用できるようにこのメソッドをくくりだしておくのが普通です。くくりだしたメソッドは、マスアサインメントを避けるだけでなく、外部から不正に呼び出されることのないようにprivate宣言の後に置いてください。 修正結果は以下のようになります。

def create
  @article = Article.new(article_params)

  @article.save
  redirect_to @article
end

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

詳細については、上に挙げた参考資料に加えてStrong Parametersに関する公式ブログの記事 (英語) を参照してください。

5.7 記事を表示する

現時点の状態でフォームを再度送信すると、showアクションがないというメッセージがRailsから返されます。このままでは実用に耐えないので、showアクションを追加して先に進むことにしましょう。

rake routesの出力結果にもあったようにshowアクションへのルーティングは以下のようになります。

article GET    /articles/:id(.:format)      articles#show

:idは、ここに:idパラメータが置かれることを指定するための特殊な文法です。この場合は記事のidを表します。

newで既に行ったのと同じ要領で、app/controllers/articles_controller.rbshowアクションを追加し、対応するビューも追加する必要があります。

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

ここでいくつか注意すべき点があります。ここではArticle.findを使用して、取り出したい記事をデータベースから探しています。このとき、リクエストの:idパラメータを取り出すためにparams[:id]を引数としてfindに渡しています。そして、取り出した記事オブジェクトへの参照を保持するために、通常の変数ではなく、インスタンス変数 (@が頭に付いているのが印です) が使用されている点にもご注目ください。これは、Railsではコントローラのインスタンス変数はすべてビューに渡されるようになっているからです (訳注: Railsはそのために背後でインスタンス変数をコントローラからビューに絶え間なくコピーし続けています)。

それでは、app/views/articles/show.html.erbファイルを作成し、以下のように記入しましょう。

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

上のように変更したことで、新しい記事の作成がようやくできるようになりました。 http://localhost:3000/articles/newをブラウザで開いて試してみましょう。

Show action for articles

5.8 すべての記事を一覧表示する

単独の記事は表示できるようになりましたが、今度は記事の一覧も表示できるようにしてみましょう。 今度もrake routesでルーティングを確認すると、以下のようなルーティングが既にあります。

articles GET    /articles(.:format)          articles#index

以下のように、このルーティングに対応するindexアクションを、app/controllers/articles_controller.rbArticlesControllerの中に作成します。

def index
  @articles = Article.all
end

最後に、このアクションに対応するビューをapp/views/articles/index.html.erbに追加します。

<h1>Listing articles</h1>

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
  </tr>

  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
    </tr>
  <% end %>
</table>

これで、http://localhost:3000/articlesをブラウザで開くと、作成された記事の一覧が表示されるようになりました。

5.9 リンクの追加

ここまでで、記事の作成、表示、一覧表示ができるようになりました。今度は、ページ間を移動するためのリンクを追加してみましょう。

app/views/welcome/index.html.erbを開いて以下のように変更してください。

<h1>Hello, Rails!</h1>
<%= link_to 'My Blog', controller: 'articles' %>

link_toメソッドは、Railsのビルトインヘルパーの1つです。このメソッドは、指定されたテキストに基いたリンクを作成し、ジャンプ先を表示します。ここでは各記事へのパスを指定します。

他のビューへのリンクも作成してみましょう。"New Article"リンクをapp/views/articles/index.html.erbに追加し、<table>タグの上に置きます。

<%= link_to 'New article', new_article_path %>

このリンクをクリックするとフォームが表示され、そこで新しい記事を作成することができるようになります。

app/views/articles/new.html.erbのフォームの下に、記事を作成せずに元のindexアクションに戻るリンクも作成しましょう。

<%= form_for :article, url: articles_path do |f| %>
  ...
<% end %>

<%= link_to 'Back', articles_path %>

最後に、app/views/articles/show.html.erbテンプレートに、indexアクションに戻るためのリンクも追加し、記事単体を見ていたユーザーが元に戻って一覧を参照できるようにします。

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<%= link_to 'Back', articles_path %>

現在と同じコントローラのアクションにリンクする場合は、controllerの指定は不要です。デフォルトでは現在のコントローラが使用されるからです。

developmentモード (これはRailsのデフォルトのモードです) では、Railsはリクエストのたびにアプリケーションを再読み込みします。これは開発をやりやすくするためであり、変更を行なうたびにRailsのWebサーバーを再起動する必要はありません。

5.10 検証 (バリデーション) の追加

モデルファイルapp/models/article.rbの中身は、以下のように驚くほどシンプルです。

class Article < ActiveRecord::Base
end

ファイルにはこれしか書かれていませんが、このArticleクラスがActiveRecord::Baseクラスを継承していることにご注目ください。Active Recordは、基本的なデータベースCRUD (Create、Read、Update、Destroy) 操作、データの検証 (バリデーション)、洗練された検索機能、複数のモデルを関連付ける(リレーションシップ) など、きわめて多くの機能をRailsモデルに無償で提供しています。

Railsには、モデルに渡したデータを検証する機能もあります。app/models/article.rbファイルをエディタで開き、以下のように変更します。

class Article < ActiveRecord::Base
  validates :title, presence: true,
                    length: { minimum: 5 }
end

このように変更されると、すべての記事にタイトルが存在し、その長さが5文字以上であることが保証されます。そうでない場合には記事はデータベースに保存されません。Railsには豊富な検証機能があり、存在確認、カラムでの重複確認、フォーマット確認、関連付けられたオブジェクトがあるかどうかの確認などが行えます。検証の詳細についてはActive Record バリデーションを参照してください。

検証機能が追加されたので、検証が通らない内容を持つ@articleに対して@article.saveを実行するとfalseが返されるようになりました。さて、app/controllers/articles_controller.rbを再度開いてみると、残念なことにまだcreateアクションで@article.saveの結果を利用するようになっていません。@article.saveが失敗したらそのことをユーザーに表示してあげないと不親切です。そのためには、app/controllers/articles_controller.rbnewアクションcreate`アクションを以下のように変更してください。

def new
  @article = Article.new
end

def create
  @article = Article.new(article_params)

  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end

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

new@articleというインスタンス変数が新たに作成されるようになりました。これを何に使うのかはすぐにわかります。

createアクションも、saveの結果がfalseの場合には、redirect_toではなく、newテンプレートに対するrenderを実行するように変更されました。ここでrenderメソッドを使用する理由は、ビューのnewテンプレートが描画されたときに、@articleオブジェクトがビューのnewテンプレートに返されるようにするためです。renderによる描画は、フォームの送信時と同じリクエスト内で行われます。対照的に、redirect_toはサーバーに別途リクエストを発行するようブラウザに対して指示するので、やりとりが1往復増えます。

http://localhost:3000/articles/newをブラウザで再表示し、わざと記事のタイトルを空にして保存してみましょう。Railsは記事入力フォームを再表示するはずです。しかしこれだけではまだ不親切です。入力のどこに問題があったのかをユーザーに通知する必要があります。そこで、app/views/articles/new.html.erbを変更して、エラーメッセージがある場合に表示するようにしてみましょう。

<%= form_for :article, url: articles_path do |f| %>
  <% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited
      this article from being saved:</h2>
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', articles_path %>

何やら目新しいコードが追加されています。ここでは、@article.errors.any?でエラーが発生しているかどうかをチェックしています。そしてエラーの場合は@article.errors.full_messagesでエラーメッセージを全文表示します。

pluralizeは、数値を受け取ってそれに応じて英語の「単数形/複数形」活用を行ってくれるRailsのヘルパーメソッドです。数値が1より大きい場合は、引数の文字列を自動的に複数形に変更します(訳注:pluralizeはたいていの不規則活用にも対応しています)。

ArticlesController@article = Article.newを追加した理由は、そうしないとビューで受け取る@articlenilになってしまい、@article.errors.any?を呼び出すところでエラーになってしまうためです。Articleのインスタンス作成に成功したときは@articleがnilにならないようにしておきたいわけです。

Railsでは、エラーメッセージを含むフィールドは自動的にfield_with_errorsクラスを持つdivタグで囲まれます。これを利用して、エラーメッセージをもっと目立たせるようにcssルールを定義しても構いません。

これで、http://localhost:3000/articles/newのフォームで新しい記事を保存する時にタイトルがなかった場合に、適切なエラーメッセージが表示されるようになりました。

エラーが表示されているフォーム

5.11 記事を更新する

ここまでで、CRUDのうちCとRを実現しました。今度はUの部分、つまり記事の更新を実装してみましょう。

最初に、ArticlesControllereditアクションを追加しましょう。

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

編集用のビューに含まれるフォームは、記事を作成するときのビューに含まれるフォームと基本的にほとんど同じです。app/views/articles/edit.html.erbというファイルを作成し、以下のコードを入力してください。

<h1>Editing article</h1>

<%= form_for :article, url: article_path(@article), method: :patch do |f| %>
  <% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited
      this article from being saved:</h2>
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', articles_path %>

このフォームの送信先はupdateアクションになります。今の時点では未定義ですが、この後すぐ定義します。

ここでmethod: :patchというオプションが指定されていますので、PATCHというHTTPメソッドを使用してこのフォームを送信しようとしていることがRailsに伝わります。PATCHメソッドは、RESTプロトコルに基いてリソースを 更新 するために使用されます。

form_forヘルパーメソッドの最初のパラメータには@articleのようなオブジェクトを使用できます。@articleのようなオブジェクトが最初のパラメータとして渡されると、ヘルパーはそのパラメータに含まれているフィールドを使用してフォームの項目を埋めます。ここで面白いのは、@articleのようなインスタンス変数の代わりに同じ名前のシンボル (:articleなど) を渡した場合にも動作はまったく同じであることです。以上がこのコードで行われていることです。詳細については、form_forに関するAPIドキュメント (英語) を参照してください。

続いて、app/controllers/articles_controller.rbupdateアクションを作成しましょう。

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

  if @article.update(article_params)
    redirect_to @article
  else
    render 'edit'
  end
end

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

既存のレコードを更新したいときには新たにupdateアクションを使用します。このアクションには、更新後の属性を含むハッシュを渡すことができます。createのときに既に行ったように、記事の更新に失敗してエラーが発生した場合、そのことをユーザーに伝えるようにしましょう。

createアクションで使用したarticle_paramsメソッドをここでも使うことにします。

updateに属性をすべて渡す必要はありません。たとえば、@article.update(title: 'A new title')を実行した場合、Railsはtitle属性のみを更新し、それ以外の属性は変更しません。

最後に、editアクションへのリンクを全記事の一覧に追加しましょう。app/views/articles/index.html.erbに以下のように手を加えて"Show"リンクの隣にEditリンクを追加します。

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="2"></th>
  </tr>

<% @articles.each do |article| %>
  <tr>
    <td><%= article.title %></td>
    <td><%= article.text %></td>
    <td><%= link_to 'Show', article_path(article) %></td>
    <td><%= link_to 'Edit', edit_article_path(article) %></td>
  </tr>
<% end %>
</table>

同様に、app/views/articles/show.html.erbテンプレートにもEditリンクを追加しましょう。こうしておけば各記事のページから編集を行えるようになります。テンプレートの最下部に以下を追加します。

...

<%= link_to 'Back', articles_path %>
| <%= link_to 'Edit', edit_article_path(@article) %>

ここまでの変更で、アプリケーションの外観は以下のような感じになっているはずです。

Editリンクが追加されたindexアクション

5.12 部分テンプレート(パーシャル)を使用してビューの重複コードをきれいにする

さて、editページをよく見ると、newページとほとんど違いがありません。実際、フォームを表示するコードはどちらでもまったく同じになっています。パーシャル(部分テンプレートとも呼ばれます)を使用して、このような無駄な重複を取り除きましょう。Rubyの慣例として、パーシャルのファイル名の先頭にはアンダースコアを追加します。

パーシャルについての詳細は本ガイドのレイアウトとレンダリングを参照してください。

app/views/articles/_form.html.erbという名前のパーシャルファイルを作成し、以下の内容を入力してください。

<%= form_for @article do |f| %>
  <% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited
      this article from being saved:</h2>
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

このコードをよく観察してみると、form_forの宣言部分以外には元のコードとの違いがないことがわかります。他のフォーム内のコードを置き換えるパーシャル内でのform_for宣言がこのように短くて簡潔で済むのは、@articleがRESTfulルーティングの完全なセットに対応する リソース であり、必要なURIとメソッドをRailsがそれに基いて推測できるからです。 form_forの使用法の詳細については、Rails APIのリソース指向のスタイル (英語) を参照してください。

今度はapp/views/articles/new.html.erbビューを完全に書き直して、今作成したパーシャルをここで使ってみましょう。

<h1>New article</h1>

<%= render 'form' %>

<%= link_to 'Back', articles_path %>

続いて、app/views/articles/edit.html.erbビューでも同じ作業を行います。

<h1>Edit article</h1>

<%= render 'form' %>

<%= link_to 'Back', articles_path %>

5.13 記事を削除する

いよいよCRUDのDまで到達しました。ここでは記事をデータベースから削除します。RESTの慣例に従い、記事の削除に使用するルーティングをrake routesの出力結果から取り出したのが以下です。

DELETE /articles/:id(.:format)      articles#destroy

ルーティングメソッドであるdeleteは、リソースを削除するときに使用する必要があります。なお、この削除用ルーティングに通常のgetルーティングが使用されていると、以下のような危険なURLを送信できてしまいます。

<a href='http://example.com/articles/1/destroy'>look at this cat!</a>

リソースの削除にdeleteメソッドが使用され、このルーティングがdestroyアクションに割り当てられる流れになります。このdestroyアクションはまだ作成してなかったのでここで以下の内容で作成しましょう。

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

  redirect_to articles_path
end

データベースのレコードを削除したい場合には、Active Recordのdestroyメソッドを呼びます。なお、レコードの削除の場合、それ専用のビューテンプレートは不要です。その代わりに削除後にindexアクションにリダイレクトします。

最後に、 indexアクションのテンプレート(app/views/articles/index.html.erb)に'Destroy'リンクを追加し、機能を完成させましょう。

<h1>Listing Articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="3"></th>
  </tr>

<% @articles.each do |article| %>
  <tr>
    <td><%= article.title %></td>
    <td><%= article.text %></td>
    <td><%= link_to 'Show', article_path(article) %></td>
    <td><%= link_to 'Edit', edit_article_path(article) %></td>
    <td><%= link_to 'Destroy', article_path(article),
                    method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>
</table>

上で追加したコードでは、link_toメソッドの使い方がこれまでと違っていることにご注目ください。2番目の引数で名前付きルートを渡している点はこれまでと同じですが、その後に別の引数があります。この:methodオプションと:'data-confirm'オプションはHTML5の属性です。このリンクをクリックすると、本当に削除してよいかどうかを確認するメッセージを表示し、その後deleteメソッドとリンクを送信します。このダイアログボックスの表示はjquery_ujsというJavaScriptファイルによって自動的に行われます。このファイルはアプリケーションの生成時に自動的にアプリケーションレイアウト (app/views/layouts/application.html.erb) に含まれます。このJavaScriptファイルがないと、ダイアログボックスは表示されなくなります。

Confirm Dialog

以上で記事の作成、表示、一覧表示、更新、削除をひととおり実装できました。お疲れさまでした!

Railsでは、ルーティングを1つずつ手作りするよりもresourcesオブジェクトを使用してルーティングを設定することが推奨されています。 ルーティングの詳細については、本ガイドのRailsのルーティングを参照してください。

6 2番目のモデルを追加する

今度はアプリケーションに第2のモデルを追加しましょう。この第2のモデルでは、記事へのコメントを扱います。

6.1 モデルを生成する

今回のモデルの生成には、Articleモデルを生成したときと同じジェネレータを使用します。作成するCommentモデルは、記事への参照を保持します。以下のコマンドをターミナルで実行してください。

$ 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 < ActiveRecord::Base
  belongs_to :article
end

Commentモデルの内容は、これまでに見たArticleモデルと非常によく似ています。違いといえば、Active Recordの 関連付け (アソシエーション) を設定するためのbelongs_to :articleという行がある点です。関連付けの詳細については、本ガイドの次の節で説明します。

モデルのファイルの他にマイグレーションファイルも生成されています。マイグレーションファイルは、モデルに対応するデータベーステーブルを生成するために使用されます。

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body

      # 以下の行によって`article_id`という整数カラムが追加される
      t.references :article, index: true

      t.timestamps
    end
  end
end

t.referencesという行は、2つのモデルの関連付けを指定するための外部キーを設定します。このとき、関連付け用のインデックスもカラム上に作成されます。それではマイグレーションを実行しましょう。

$ rake db:migrate

Railsは、これまで実行されていないマイグレーションだけを適切に見分けて実行しますので、以下のようなメッセージだけが表示されるはずです。

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

6.2 モデル同士を関連付ける

Active Recordの関連付け機能により、2つのモデルの間にリレーションシップを簡単に宣言することができます。今回の記事とコメントというモデルの場合、以下のいずれかの方法で関連付けを設定できます。

  • 1つのコメントは1つの記事に属する (Each comment belongs to one article)。
  • 1つの記事は複数のコメントを持てる (One article can have many comments)。

そして上の方法(における英語の記述)は、Railsで関連付けを宣言するために使用される文法と非常に似ています。Comment モデル (app/models/comment.rb) 内のコードに既に書かれていたように、1つの記事には1つのコメントが属しています。

class Comment < ActiveRecord::Base
  belongs_to :article
end

そして、Articleモデルapp/models/article.rbを編集して、他方のモデルを追加する必要があります。

class Article < ActiveRecord::Base
  has_many :comments
  validates :title, presence: true,
                    length: { minimum: 5 }
end

2つのモデルで行われているこれらの宣言によって、さまざまな動作が自動化されています。たとえば、@articleというインスタンス変数に記事が1つ含まれているのであれば、@article.commentsと書くだけでその記事に関連付けられているコメントをすべて取得することができるのです。

Active Recordの関連付けの詳細については、Active Recordの関連付け(アソシエーション)ガイドを参照してください。

6.3 コメントへのルーティングを追加する

welcomeコントローラで行ったときと同様、commentsを参照するためにRailsが知っておくべきルーティングを追加する必要があります。再びconfig/routes.rbファイルを開き、以下のように変更してください。

resources :articles do
  resources :comments
end

この設定により、articleの内側に ネストされたリソース としてcommentsが作成されます。これは、モデルの記述とは別の視点から、記事とコメントの間のリレーションシップを階層的に捉えたものであると言えます。

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

6.4 コントローラを生成する

モデルを手作りしたのですから、それに合ったコントローラも作ってみたくなります。ということで再びこれまでと同じジェネレータを使用してみましょう。

$ rails generate controller Comments

上のコマンドを実行すると、6つのファイルと1つの空ディレクトリが作成されます。

ファイル/ディレクトリ 目的
app/controllers/comments_controller.rb コメント用コントローラ
app/views/comments/ コントローラのビューはここにおかれる
test/controllers/comments_controller_test.rb コントローラのテスト用ファイル
app/helpers/comments_helper.rb ビューヘルパー
test/helpers/comments_helper_test.rb ヘルパー用のファイル
app/assets/javascripts/comment.js.coffee コントローラ用のCoffeeScript
app/assets/stylesheets/comment.css.scss コントローラ用のCSS (カスケーディングスタイルシート) ファイル

一般的なブログと同様、このブログの記事を読んだ人はそこに直接コメントを追加したくなるでしょう。そしてコメントを追加後に元の記事表示ページに戻り、コメントがそこに反映されていることを確認したいはずです。そこで、CommentsControllerを使用してコメントを作成したり、スパムコメントが書き込まれたら削除できるようにしたいと思います。

そこで最初に、Articleのshowテンプレート (app/views/articles/show.html.erb) を改造して新規コメントを作成できるようにしましょう。

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', articles_path %>
| <%= link_to 'Edit', edit_article_path(@article) %>

上のコードでは、Articleのshowページにフォームが1つ追加されています。このフォームはCommentsControllercreateアクションを呼び出すことでコメントを新規作成します。form_for呼び出しでは配列を1つ渡しています。これは/articles/1/commentsのような「ネストしたルーティング (nested route)」を生成します。

今度はapp/controllers/comments_controller.rbcreateアクションを改造しましょう。

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)ヘルパーを使用して元の記事の画面に戻ります。既に説明したように、このヘルパーを呼び出すとArticlesControllershowアクションが呼び出され、show.html.erbテンプレートが描画されます。この画面にコメントを表示できるようにしたいので、app/views/articles/show.html.erbに以下のコードを追加しましょう。

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<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_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Edit Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>

以上で、ブログに記事やコメントを自由に追加して、それらを正しい場所に表示できるようになりました。

記事にコメントが追加されたところ

7 リファクタリング

さて、ブログの記事とコメントが動作するようになったので、ここでapp/views/articles/show.html.erbテンプレートを見てみましょう。何やらコードがたくさん書かれていて読みにくいように思えます。ここでもパーシャルを使用してコードをきれいにしましょう。

7.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の内容を以下のように変更しましょう。

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Edit Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>

これにより、app/views/comments/_comment.html.erbパーシャルが、@article.commentsコレクションに含まれているコメントをすべて出力するようになりました。renderメソッドが@article.commentsコレクションに含まれる要素を1つ1つ列挙するときに、各コメントをパーシャルと同じ名前のローカル変数に自動的に割り当てます。この場合はcommentというローカル変数が使用され、これはパーシャルでの表示に使用されます。

7.2 パーシャルのフォームを描画する

今度はコメント作成部分もパーシャルに追い出してみましょう。app/views/comments/_form.html.erbファイルを作成し、以下のように入力します。

<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

続いてapp/views/articles/show.html.erbの内容を以下のように変更しましょう。

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

      <p>
  <strong>Text:</strong>
  <%= @article.text %>
      </p>

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= render "comments/form" %>

<%= link_to 'Edit Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>

2番目のrenderは、描画したいパーシャルテンプレートであるcomments/formを単純に定義しているだけです。comments/formと書くだけで、Railsは区切りのスラッシュ文字に気付き、app/views/commentsディレクトリの_form.html.erbパーシャルを描画すればよいということを理解し、実行してくれます。app/views/comments/_form.html.erbなどと書く必要はありません。

@articleオブジェクトはインスタンス変数なので、ビューで出力されるどのパーシャルからもアクセスできます。

8 コメントを削除する

スパムコメントを削除できるようにするのも、このブログでは重要な機能です。そのためのビューを作成し、CommentsControllerdestroyアクションを作成する必要があります。

最初にapp/views/comments/_comment.html.erbパーシャルに削除用のリンクを追加しましょう。

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

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

<p>
  <%= link_to 'Destroy Comment', [comment.article, comment],
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>

この新しい"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)
  end

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

destroyアクションでは、まずどの記事が対象であるかを検索して@articleに保存し、続いて@article.commentsコレクションの中のどのコメントが対象であるかを特定して@commentに保存します。そしてそのコメントをデータベースから削除し、終わったら記事のshowアクションに戻ります。

8.1 関連付けられたオブジェクトも削除する

ある記事を削除したら、その記事に関連付けられているコメントも一緒に削除する必要があります。そうしないと、コメントがいつまでもデータベース上に残ってしまいます。Railsでは関連付けにdependentオプションを指定することでこれを実現しています。Articleモデルapp/models/article.rbを以下のように変更しましょう。

class Article < ActiveRecord::Base
  has_many :comments, dependent: :destroy
  validates :title, presence: true,
                    length: { minimum: 5 }
end

9 セキュリティ

9.1 BASIC認証

このブログアプリケーションをオンラインで公開すると、このままでは誰でも記事を追加/編集/削除したり、コメントを削除したりできてしまいます。

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認証ダイアログが表示されます。

Basic HTTP Authentication Challenge

もちろん、Railsでは他の認証方法を使用することもできます。Railsにはさまざまな認証システムがありますが、その中で人気が高い認証システムはDeviseAuthlogic gemの2つです。

9.2 その他のセキュリティ対策

セキュリティ、それもWebアプリケーションのセキュリティは非常に幅広く、かつ詳細に渡っています。Railsアプリケーションのセキュリティの詳細については、本ガイドのRailsセキュリティガイドを参照してください。

10 次に学ぶべきこと

以上で、Railsアプリケーションを初めて作るという試みは終わりです。この後は自由に更新したり実験を重ねたりできます。もちろん、何の助けもなしにWebアプリケーションを作らなければならないなどということはありません。Railsを使用してWebアプリケーションを立ち上げたり実行したりするうえで助けが必要になったら、以下のサポート用リソースを自由に参照できます。

Railsには、rakeコマンドラインユーティリティを使用して生成できるビルトインヘルプもあります。

  • rake doc:guidesを実行すると、本Railsガイドの完全なコピーがアプリケーションのdoc/guidesフォルダに生成されます。ブラウザでdoc/guides/index.htmlを開くことでガイドを参照できます。
  • rake doc:railsを実行すると、Rails APIドキュメントの完全なコピーがアプリケーションのdoc/apiフォルダに生成されます。ブラウザでdoc/api/index.htmlを開いてAPIドキュメントを参照できます。

doc:guides rakeタスクを使用してRailsガイドをローカル生成するには、RedCloth gemをインストールする必要があります。RedCloth gemをGemfileに追記してbundle installを実行することで利用できるようになります。

11 設定の落とし穴

Railsでの無用なトラブルを避けるための最も初歩的な方法は、外部データを常にUTF-8で保存することです。このとおりにしないと、RubyライブラリやRailsはネイティブデータをたびたびUTF-8に変換しなければならず、しかもときに失敗することがあります。外部データを常にUTF-8にしておくことをぜひお勧めします。

外部データのエンコードが不統一な場合によく起きる症状としては、たとえば画面に黒い菱型◆と疑問符が表示されるというものがあります。他にも、"ü"という文字のはずが"ü"という文字に変わっている、などの症状もあります。Railsではこうした問題を緩和するため、問題の原因を自動的に検出して修正するために内部で多くの手順を行っています。しかし、UTF-8で保存されていない外部データがあると、Railsによる自動検出/修正が効かずに文字化けが発生することがあります。

UTF-8でないデータの主な原因は以下の2つです。

  • テキストエディタ: TextMateを含む多くのテキストエディタは、デフォルトでUTF-8エンコードでテキストを保存してくれます。使用しているテキストエディタがこのようになっていない場合、テンプレートを表示する時にéなどの特殊文字が◆?のような感じでブラウザで表示されることがあります。これはi18n(国際化)用の翻訳ファイルで発生することもあります。一部のDreamweaverのようにUTF-8保存がデフォルトでないエディタであっても、デフォルトをUTF-8に変更する方法は用意されているはずです。エンコードはUTF-8に変えてください。
  • データベース: Railsはデータベースから読みだしたデータを境界上でUTF-8に変換します。しかし、使用しているデータベースの内部エンコード設定がUTF-8になっていない場合、UTF-8の文字の一部をデータベースにそのまま保存できないことがあります。たとえばデータベースの内部エンコードがLatin-1になっていると、ロシア語・ヘブライ語・日本語などの文字をデータベースに保存したときにこれらの情報は永久に失われてしまいます。できるかぎり、データベースの内部エンコードはUTF-8にしておいてください。

12 参考資料