読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

Railsを始めたばかりの人向け!Railsの仕組みを一から理解しながらブログを作成する

Rails初級 作ってみよう

Rails初心者がRailsの一通りの仕組みを理解できることをために、Railsの仕組みを一つ一つ理解しながらブログを作成していきます。
Railsでのルーティング、コントローラー、モデル、ビューの規約や使い方、ScssでのスタイリングやCoffeeScriptでのJavascriptの記述なども説明します。

Rails Guide - Getting Started with Railsをベースにしました。

まずは、完成させるブログアプリケーションの画面です。
f:id:nipe880324:20140813160348p:plain:w320


動作確認

前提条件

  • Ruby がインストール済み
  • Rails 4 がインストール済み
  • SQLite3データベースがインストール済み

※もしインストールされていない場合は、「Rails4 インストール」などでGoogle検索してインストールしてください

目次

  1. Railsとは
  2. 新しいRailsプロジェクトを作成する
  3. Hello, Rails! を表示する
  4. 投稿機能を作成する
  5. コメントを追加する
  6. リファクタリング
  7. コメントを削除する
  8. HTTP認証を追加する
  9. 画面のスタイリングをする
  10. CoffeeScriptでJavaScriptを使う
  11. 次は?


1. Railsとは

Railsは、プログラミング言語Rubyで書かれたWebアプリケーション開発フレームワークです。
「設定よりも規約」とい考えで、いろいろとルールを決めることでWebアプリケーションを簡易に開発できようにするという設計思想のもとに作られています。このため、この規約を守ることで、他のプログラミング言語フレームワークよりも少ないソースコード(生産性が高い)でWebアプリケーションを開発できます。
これにより、Rails開発者の多くは、RailsでのWebアプリケーション開発は楽しいものであると感じています。

Railsの規約に基づいた開発を学ぶことで、素晴らしい生産性の向上を体感するでしょう。他のプログラミング言語での古い考え方にいつまでも固執していると、Webアプリケーション開発が楽しくないものになってしまうかもれません。

Railsの主要な2つの原則

  • DRY(Don't Repeat Yourself)- 同じコードを何度も書くことは良くないという考え
  • 設定より規約(Convention Over Configuration)- 設定ファイルに細かな設定情報を記載するのではなく、ファイル名やクラス名などの規約があらかじめRailsによって決められている


2. 新しいRailsプロジェクトを作成する

実際に手を動かしながら以降の説明を実施していくと理解が深まるのでお勧めです。
この記事では、「簡単なブログアプリケーションを作成」していきます。

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

Railsには「ジェネレーター」と呼ばれるスクリプトがあり、Railsでなにか作業を始めるときの最初のステップとしてよく使うスクリプト郡ですので覚えておいてください。
新しいRailsアプリケーションを作成するときもこの「ジェネレーター」を使うことができます。

ターミナルを開いて、Railsアプリケーションを作成したいフォルダに移動して、次のコマンドを実行してください。

$ rails new blog

このコマンドは内部的に、blogフォルダを作成し、そのフォルダ内に「Blog」というRailsアプリケーションに必要なファイルを作成しています。その後、bundle installを行いGemfile内に記載されているgemをインストールしています。

rails new -hコマンドで全てのコマンドラインオプションを確認できます。

ブログアプリケーションで作業をするために作成したblogフォルダに移動します。

$ cd blog

rails new blogコマンドは、自動でRailsのフォルダ構成、ファイルを作成します。このチュートリアルでは、app/配下のファイルを修正することが多くなりますが、その他のフォルダも重要です。そのため、デフォルトで作成されるフォルダとファイルの基礎をまとめておきました。
Railsのフォルダ構造の説明


3. Hello, Rails! を表示する

まずは、画面にテキストを表示させてみましょう。

3.1. Webサーバーを起動する

実は今の段階で既にRailsアプリケーションは機能します。開発マシン上でWebサーバーを起動させましょう。

$ rails server

CoffeeScriptJavaScriptコンパイルするために「JavaScriptランタイム」が必要です。もし、「JavaScriptランタイム」が存在しない場合、execjs errorが発生します。Mac OS XWindowsでは「JavaScriptランタイム」は通常はインストールされているので問題ありません。

コマンドを実行すると、WebサーバーのWeBrickが起動します。動作を確認するために、ブラウザを開き、http://localhost:3000にアクセスしてください。デフォルトのインフォページが表示されます。
f:id:nipe880324:20140812025948p:plain:w480

※Webサーバーを止めるには、ターミナル内でCtrl+Cを押して下さい。
ソースコードを変更したときにサーバーが自動的に読み込むを行うのでサーバーの再起動は必要ありません。ルーティングなどの設定ファイルを変更した場合はサーバーの再起動が必要です。変更したのに画面上で変化がないときはサーバーの再起動をするようにしてみてください。

画面にインフォページが表示されるということは、画面を表示するために十分な設定が正しく行われているという証拠です。また、この画面上部にある"About your application’s environment"リンクをクリックすることでアプリケーション環境を確認することができます。

3.2. "Hello, Rails"を画面表示

"Hello, Rails"を画面表示するためには、少なくともコントローラーとビューを作成する必要があります。

コントローラーの目的は、アプリケーションへのリクエストを処理することです。ルーティングは、どのコントローラーがどのリクエストを処理するかを決めます。つまり、リクエストされたURLとコントローラーのマッピングを行っています。たいていは、コントローラーは複数のルートを処理できるので、それぞれのルートはコントローラー内のそれぞれのアクション(コントローラーのメソッドのことをアクションと呼ぶ)にマッピングされます。アクション内では、情報を集め、それをビューに渡しています。

ビューの目的は、人間が読める形式で情報を表示することです。重要なことは、表示する情報を集めるのはコントローラーであり、ビューではないということです。ビューはコントローラーが集めた情報を表示することに専念すべきです。デフォルトでは、ビューテンプレートはERB(組み込みRuby)という言語で書かれています。

コントローラーを作成するために、「ジェネレーター」にcontrollerを指定し、welcomeというコントローラ名、そして、indexというアクションを指定します。

$ rails generate controller welcome index

Railsはいくつかのファイルと1つのルートを追加します。

    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

これらのファイルのなかで一番重要なのは、
「コントローラー(app/controllers/welcome_controller.rb)」と
「ビュー(app/views/welcome/index.html.erb)」です。

app/views/welcome/index.html.erbテキストエディタで開きましょう。
全てのコードを削除し、次のコードを記載して保存してください。

<h1>Hello, Rails!</h1>

3.3. アプリケーションのルート画面を設定

さて、私たちは今コントローラーとビューを作成しました。ルートURL(http://localhost:3000)にアクセスしたときに、"Hello, Rails!"を画面に表示させたいです。しかし、今は"Welcome Aboard"のインフォページが表示されてしまいます。

これを解消するために、ルートを指定する必要があります。

# config/routes.rb
Rails.application.routes.draw do
  get 'welcome/index'

  # The priority is based upon order of creation: first created -> highest priority.
  # See how all your routes lay out with "rake routes".

  # You can have the root of your site routed with "root"
  # root 'welcome#index'
  ...

これはアプリケーションのルーティングファイルです。特殊なDSL(Domain-Specific-Language)で書かれており、アプリケーションへのリクエストをコントローラーとアクションにマッピングしています。このファイル内には既にいくつかのサンプルがコメントアウトされて記載されています。その中の一つに、ルートURLをコントローラーとアクションに結びつけている記載があります。rootから始まるコメントアウトされている行を探して、コメントを外して下さい。

# config/routes.rb
  root 'welcome#index'

root 'welcome#index'はルートへのリクエストをWelcomeコントローラーのindexアクションにマッピングさせることを意味しています。
また、get 'welcome/index'http://localhost:3000/welcome/indexへのリクエストをWelcomeコントローラーのindexアクションにマッピングさせることを意味しています。これは、rails generate controller welcome indexコマンドを実行したときに作られたルートです。

ブラウザでhttp://localhost:3000にアクセスしましょう。
すると、app/views/welcome/index.html.erbに記載された"Hello, Rails!"メッセージが表示されるます。
これは、config/routes.rb内に追加したルートがWelcomeコントローラーのindexアクションにマッピングされ、indexアクションがapp/views/welcome/index.html.erbレンダリングしたからです。

indexアクションを確認すると、アクション内にソースコードが記載されていません。しかし、Railsはビューをレンダリングするメソッドrenderメソッドが呼ばれていないときは、暗黙でコントローラー名とアクション名からレンダリングするビューファイルを推測します。今回の場合、コントローラ名がWelcomeでアクション名がindexなので、app/views/welcome/index.html.erbレンダリングされます。

f:id:nipe880324:20140812031054p:plain:w480


4. 投稿機能を作成する

さて、あなたはもうすでに「コントローラー、アクション、ビューの作成方法」を知ることができました。
それでは、ブログの投稿機能を作っていきましょう。

Railsでは、標準的なRESTリソースを宣言するために使われるresourcesメソッドが定義されています。>|ruby|
# config/routes.rb
Rails.application.routes.draw do

resources :posts

root 'welcome#index'
end
|

ターミナルでrake routesを実行すると、標準のRESTfulアクションのルートを確認することができます。

$ rake routes
   Prefix Verb   URI Pattern               Controller#Action
    posts GET    /posts(.:format)          posts#index
          POST   /posts(.:format)          posts#create
 new_post GET    /posts/new(.:format)      posts#new
edit_post GET    /posts/:id/edit(.:format) posts#edit
     post GET    /posts/:id(.:format)      posts#show
          PATCH  /posts/:id(.:format)      posts#update
          PUT    /posts/:id(.:format)      posts#update
          DELETE /posts/:id(.:format)      posts#destroy
     root GET    /                         welcome#index

それでは、新しい投稿(Post)を作成する機能とフォームを追加します。
これは、CRUD操作の作成(Create)検索(Read)に当てはまります。これから作成するフォームは次のようになります。
f:id:nipe880324:20140812034136p:plain:w480

今のところ見た目がもっさいですが、まあいいでしょう。最後の方の9番目の大項目でスタイリングを改善していきます。

4.1. 前準備の実施

アプリケーションに新しい投稿を作成する最初のステップは、「ルーティング」です。
/posts/newのURLにアクセスすることで投稿を作成します。しかし、resoucesメソッドにより既にそのルートは定義されています。

画面からhttp://localhost:3000/posts/newにアクセスしてください。ルーティングエラーが発生するでしょう。
f:id:nipe880324:20140812032446p:plain:w480

このエラーの原因は、「リクエストを処理するためのコントローラーを必要としているが、そのコントローラーが存在していなかった」ためです。
そのためPostsControllerを作成すれば解決します。次のコマンドでコントローラーを作成しましょう。

$ rails generate controller posts

空のコントローラーが表示されます。

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
end

コントローラーはApplicationControllerを継承したクラスです。コントローラークラス内にメソッドを定義することで、それがコントローラーのアクションとなります。通常そのアクションはPostに対するCRUD操作を行います。

Rubyにはpublic, private, protectedメソッドがあります。publicメソッドだけがコントローラーのアクションとなります。privateやprotectedメソッドはアクションにはならないことに注意して下さい。

http://localhost:3000/posts/newにアクセスし直すと、新しいエラーが表示されます。
f:id:nipe880324:20140812032733p:plain:w480

このエラーは、「PostsController内にnewアクションを見つからなかった」ということを表しています。

app/controllers/posts_controller.rbをエディタで開き、PostsControllerクラス内にnewアクションを定義しましょう。

class PostsController < ApplicationController
  # GET /posts/new
  def new 
  end
end

newメソッドを定義したので、http://localhost:3000/posts/newにアクセスし直しましょう。また新しいエラーが発生します。
f:id:nipe880324:20140812032951p:plain:w480

「アクションを処理した後にビュー(テンプレート)を表示しようとしたが、それが存在しなかった」ためエラーが発生しています。

上記の画像のエラー文を細かく見てみましょう。

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

それぞれの部分が何を意味しているかささっと確認しましょう。

最初の部分は、何のテンプレートが存在しなかったかを示しています。今回の場合、posts/newテンプレートです。Railsは最初にこのテンプレートを探します。そしてもし見つけれなかった場合、今度はapplication/newテンプレートを表示しようと試みます。これは、PostsControlerApplicationControllerを継承しているためです。

次の部分は、ハッシュです。localeキーはテンプレートがどの言語を使うべきかを示しいます。デフォルトは「英語(:en)」です。
次のformatsキーはテンプレートの形式を表しています。デフォルトは「HTML(html)」のため、RailsはHTMLテンプレートを探します。
最後のhandlersキーはテンプレートをレンダリングするためにどのテンプレートハンドラーを使えるか表しています。今回の場合で言うと、erbはHTMLテンプレート用、builderXMLテンプレート用に使われ、coffeeCoffeeScriptJavaScriptテンプレートに変換するために使われます。

最後の部分は、Railsがテンプレートファイルを探すパスを表しています。

今回のエラーを解消するための一番簡単なテンプレートは、app/views/posts/new.html.erbファイルを作成することです。1つ目の拡張子(html)がテンプレートのフォーマットであり、2つ目の拡張子(erb)がテンプレートのハンドラーを表します。Railsapp/views/配下からposts/newテンプレートを探します。
今私たちはHTMLフォームを作りたいので、フォーマットはhtml、そして、ハンドラーはerbとしました。そのため、ファイルはapp/views/posts/new.html.erbにするべきです。

エラーを解消するために、app/views/posts/new.html.erbを新たに作成し、次の内容を記載してください。

<h1>新しい投稿</h1>

http://localhost:3000/posts/newにアクセスすると画面が表示されるはずです。
f:id:nipe880324:20140812033848p:plain:w480

ルート、コントローラー、アクションそしてビューが連動して上手く動いています。

4.2. 最初のフォーム

さて、「投稿を作成するフォーム」を作っていきましょう。
テンプレートでフォームを作るためには、form_forメソッドと呼ばれるヘルパーメソッドを使います。app/views/posts/new.html.erbを次のように修正しましょう。

<h1>新しい投稿</h1>
<%= form_for :post, url: posts_path do |f| %>
  <p>
    <%= f.label :title, "タイトル" %><br>
    <%= f.text_field :title %>
  </p>
 
  <p>
    <%= f.label :text, "内容" %><br>
    <%= f.text_area :text, cols: 60, rows: 8 %>
  </p>
 
  <p>
    <%= f.submit %>
  </p>
<% end %>

画面を更新すると、この章の始めにお見せした画面とまったく同じ画面が表示されるでしょう。Railsでフォームを作ることは本当に簡単ですね。
f:id:nipe880324:20140812034136p:plain:w480

form_forでは、どのオブジェクトを作成するためのフォームを作成するかを明示するために引数にオブジェクトを渡す必要があります。今回の場合:postを渡すことで、Postオブジェクトを作成するためのフォームを作るように指定しています。
form_forメソッドのブロック内では、FormBuilderオブジェクト(fで表されている)は、投稿の「タイトル(title)」と「内容(text)」のためにラベルとテキストフィールド、テキストエリアを作成しています。そして、最後にsubmitメソッドにより、登録ボタンを作成しています。

しかし、このフォームには1つ問題があります。フォームのHTMLソースを確認すれば分かると思いますが、フォーム要素のaction属性の値が/posts/newになっています。このURLは、resoucesメソッドで自動的に作成されるルートではないため、フォームの登録ボタンを押したときにルーティングエラーになってしまいます。

そのため、form_forの行を次のように修正してください。

<%= form_for :post, url: posts_path do |f| %>

今回は、urlオプションにposts_pathヘルパーを追加しました。
こうすることによりPostsControllercreateアクションが呼び出されます。ちなみに、Webの世界では一般的にフォームでサーバーにデータを送信するときにはHTTPのGETメソッドではなくPOSTメソッドが使われます。Railsでも自動的にPOSTメソッドが使われます。

xxx_pathxxx_urlRailsが自動的に生成するヘルパーメソッドです。中身はURLであり、xxx_pathはルートのURLからの相対パスxxx_url絶対パスです。
例えば、posts_pathの場合、/postsに変換されます。
どのヘルパーが使えるかどうかは、rake routesコマンドの結果の一番左側のprefixの列を見て、それに_pathもしくは_urlを足したヘルパーメソッドが使用可能です。

では、新しい投稿を作成するために、画面を更新してから、フォームに値を入力し、サブミットボタンを押して下さい。すると、次のようなエラーが発生します。
f:id:nipe880324:20140812035717p:plain:w480

これは、「PostsControllercreateアクションがない」ため発生しています。

4.3. 投稿の作成

Unknown actionエラーが発生したので、app/controllers/posts_controller.rbPostsControllercreateアクションを定義します。

class PostsController < ApplicationController
  # GET /posts/new
  def new
  end

  # POST /posts
  def create
  end
end

再び、画面を更新して、サブミットボタンを押すとテンプレートファイルがないというエラーが発生します。
f:id:nipe880324:20140812040412p:plain:w480

しかし、心配しないで下さい。今のところこのエラーを無視しましょう。createアクションに「新しい投稿をデータベースに登録する処理」を追加しましょう。

登録ボタンを押してフォームのフィールドデータを送信すると、そのデータはパラメーターとしてRailsに送られます。このパラメーターはコントローラーのアクション内で参照可能です。では、これらのパラメーターに含まれる値を確認するために、ソースコードを少し修正しましょう。

  # POST /posts
  def create
    render text: params[:post].inspect
  end

ここでは、renderメソッドtext:キーにparams[:post].inspectを渡しています。
paramsメソッドはフォームから送られてきたパラメーター(もしくは、フィールド)の値を保持しているオブジェクトです。paramsメソッドは、文字列かシンボルをキーとしてハッシュのようにアクセスできるActiveSupport::HashWithIndifferentAccessオブジェクトを返します。今回は、フォームで入力されたパラメーターのみ保持しています。

再びフォームに値を入力し、登録ボタンを押すと、Template is missingエラーが発生する代わりに、画面に次のような出力がされます。

{"title"=>"最初の投稿", "text"=>"これは最初の投稿です。"}

今このcreateアクションはフォームから送られてきたパラメータを表示しています。しかし、パラメーターを確認できますが、実際にどこかにこのデータを保存しているわけではありません。

4.4. Postモデルの作成

Railsのモデル名は「単数系」、テーブル名はその複数形で定義する必要があります。今回の場合で言うと、モデル名はPost、テーブル名はpostsになります。
多くのRails開発者がモデルを作成するときに必ずといっていいほど使うモデルを生成ジェネレーターがあります。次のコマンドを実行し、新しいモデルを作成しましょう。

$ rails generate model Post title:string text:text

このコマンドは、String型のtitle属性、Text型のtext属性を持ったPostモデルを作成しています。

※Active Recordは「テーブルのカラム名」から「モデルの属性」を自動的に作成してくれます。これは、Railsのモデル内に属性を宣言する必要がなく、Active Recordが自動的にそれを行ってくれることを意味しています。

4.5. マイグレーションの実行

rails generate modelコマンドによりdb/migrateディレクトリ内にデータベースのマイグレーションファイルが作成されました。マイグレーションはテーブル作成やテーブル変更などのデータベース操作を行うためのRubyのクラスです。
Rails開発者はrakeコマンドを使ってそのマイグレーションファイルを実施します。データベースにマイグレーションを適用した後にその変更をロールバックをすることも可能です。
マイグレーションファイルのファイル名は、マイグレーションファイルが作成された順番で処理をさせるためにタイムスタンプを含んでいます。

作成されたマイグレーションファイルのdb/migrate/20140811045600_create_posts.rbを確認しましょう。(タイムスタンプが含まれているのでファイル名は少し違うことに注意して下さい)

class CreatePosts < ActiveRecord::Migration
  def change
    create_table :posts do |t|
      t.string :title
      t.text :text

      t.timestamps
    end
  end
end

changeメソッドマイグレーションが実行されるときに呼ばれます。このメソッド内で定義されたコードは「逆実行が可能」です。つまり、Railsマイグレーションによる変更を逆実行(戻すこと)が可能ということです。
このマイグレーションファイルを実行すると、String型とText型のカラムをもったpostsテーブルが作成されます。また、postレコードの作成時間(created_at)と更新時間(updated_at)を保持する2つのタイムスタンプも作成されます。

マイグレーションについてより詳細を知りたい場合は、マイグレーションファイルの作り方Rails Guides - Rails Database Migrationsを参照して下さい。

では、rakeコマンドでマイグレーションをしましょう。

$ rake db:migrate

マイグレーションが成功した場合、次のようにpostsテーブルが作成された旨の表示がされるでしょう。

== 20140811045600 CreatePosts: migrating ======================================
-- create_table(:posts)
   -> 0.0013s
== 20140811045600 CreatePosts: migrated (0.0014s) =============================

基本的にはdevelopment環境で作業をしています。そのため、このコマンド結果は、config/database.ymlファイルのdevelopmentセクションで定義されているデータベースに適用されます。他の環境にマイグレーションを適用させたいときは、コマンド実行時に環境名を指定して下さい。
例:rake db:migrate RAILS_ENV=production

4.6. コントローラー内でデータを保存する

PostsControllerに戻り、データベース内にデータを保存するためにcreateアクション内でPostモデルを使うように変更しましょう。

  # POST /posts
  def create
    @post = Post.new(params[:post])
    @post.save
    redirect_to @post
  end

全てのRailsのモデルは属性で初期化することができます。
最初の行では、params[:post]はフォームからの属性を保持しているので、モデルのnewメソッドにそのまま渡して、モデルを作成しています。
それから、@post.saveは、データベースにモデルを保存しています。後から確認しますが、@post.saveはモデルが保存できたか、できなかったというboolean値を返します。
最後に、後ほど実装予定のshowアクションにリダイレクトするようにしています。

render、redirect_toメソッドなどでモデルクラスのオブジェクト(@postなど)を引数に渡すと、自動的に/posts/1のようなURLに変換され、showアクションが呼ばれます。

さあhttp://localhost:3000/posts/newにアクセスし、新しい投稿をしてみてください。次のエラーが発生するでしょう。
f:id:nipe880324:20140812042243p:plain:w480

Railsではセキュアなアプリケーションの開発を促すためにいくつかのセキュリティ機構が存在します。そして、今そのセキュリティ機構によりエラーが発生しました。この機構は、Strong Parametersと呼ばれ、Railsは、私たちに「パラメーターのどの値を取得したいか」をコントローラー内に明示することを要求しています。今回の場合で言うと、パラメーター内のtitletextの値を取得したいので、次のように変更して下さい。

  # POST /posts
  def create
    @post = Post.new(post_params)
    @post.save
    redirect_to @post
  end

  private
    def post_params
      params.require(:post).permit(:title, :text)
    end

permitメソッドは、私たちがアクション内でtitletextを取得できるようにRailsに明示しています。

def post_paramsがプライベートメソッドであることに注意して下さい。Strong Parametersは、攻撃者がパラメーターの値を操作し、モデル内の属性を開発者側の意図しない値に設定することを防ぐRails4から導入されたセキュリティ機構です。より詳細については、this blog post about Strong Parametersを参照ください。

4.7. 投稿を確認する

画面でフォームの登録ボタンを押すと、showアクションが見つからないといったエラーが発生するでしょう。
f:id:nipe880324:20140812042621p:plain:w480

コントローラーにshowアクションを追加する前に、まずはルートを確認しましょう。
rake routesコマンドの結果を見てみると、showアクションのためのルートがあります。

Prefix Verb   URI Pattern               Controller#Action
  post GET    /posts/:id(.:format)      posts#show

この特殊なシンタックス:idは、このルートではidという名のパラメーターを使うことを意味しています。今回の場合は、postクラスのidを示しています。

※全てのActive Recordを継承したクラスにはidという名の属性があります。これは、主キーであり、一意にレコードを特定することができます。

app/controllers/posts_controller.rbshowアクションを追加しましょう。

  # GET /posts/:id
  def show
    @post = Post.find(params[:id])
  end

Post.findは、データベースから「指定したidのpostレコード」を取得するメソッドです。そして、その値をインスタンス変数(インスタンス変数は@で始まる)に設定しています。なぜなら、Railsインスタンス変数を使ってコントローラーからビューにデータを渡すからです。

さて、app/views/posts/show.html.erbを新たに作成しましょう。

<p>
  <strong>タイトル:</strong>
  <%= @post.title %>
</p>
 
<p>
  <strong>内容:</strong>
  <%= @post.text %>
</p>

変更を加えたら、http://localhost:3000/posts/newにアクセスし、なにか新しく投稿してみましょう。
f:id:nipe880324:20140812043231p:plain:w480

4.8. 投稿の一覧画面を表示する

次は、全ての投稿を一覧表示するようにしてみましょう。rake routesコマンドを実行し、一覧表示をするためのルートがあることを確認します。

Prefix Verb   URI Pattern               Controller#Action
 posts GET    /posts(.:format)          posts#index

このルートから、PostsControllerindexアクションが呼ばれるため、現在は作成されていないので追加しましょう。

# GET /posts
def index
  @posts = Post.all
end

Post.allは、postsテーブル内の全てのpostレコードを取得するメソッドです。

そして、アクションの結果を表示するapp/views/posts/index.html.erbを新規で作成します。

<h1>全ての投稿一覧</h1>

<table>
  <tr>
    <th>タイトル</th>
    <th>内容</th>
  </tr>

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

http://localhost:3000/postsにアクセスすれば、全ての投稿が一覧で表示されます。
f:id:nipe880324:20140812043711p:plain:w480

4.9. リンクを追加する

さて、投稿を作成(new, create)、確認(show)、一覧表示(index)ができるようになりました。それらの画面間を遷移できるようにするためにリンクを追加しましょう。

app/views/welcome/index.html.erbを開き、リンクを追加します。

<h1>Hello, Rails!</h1>
<%= link_to "私のブログ", controller: "posts" %>

link_toメソッドは、Railsに標準で使えるビューのヘルパーメソッドです。このメソッドは、引数で渡したテキストでHTMLのa要素を作成します。"私のブログ"という文字列の「投稿の一覧画面へ遷移する」aタグリンクが画面に表示されます。

同様に他の画面にもリンクを追加しましょう。app/views/posts/index.html.erbtableタグの下にリンクを追加して下さい。

...
...
</table>

<%= link_to '新しい投稿', new_post_path %>

このリンクは、"新しい投稿"という文字列で表示され、新しい投稿を作成するフォーム画面(newアクションを呼び出す)に遷移します。

次は、app/views/posts/new.html.erbの一番したの行にindexアクションに戻るためのリンクを追加しましょう。

<h1>新しい投稿</h1>
<%= form_for :post, url: posts_path do |f| %>
  .....
<% end %>

<%= link_to '戻る', posts_path %>

最後に、app/views/posts/show.html.erbテンプレートの一番したの行にindexアクションに戻るリンクを追加しましょう。

.....

<p>
  <strong>内容:</strong>
  <%= @post.text %>
</p>

<%= link_to '戻る', posts_path %>

※同じコントローラー内のアクションにリンクを貼りたい場合は、:controllerオプションを指定する必要はありません。Railsのデフォルトでは現在のコントローラーに基づいて遷移を行うためです。

development環境(デフォルトの作業環境)では、Railsがブラウザのリクエスト毎に自動でソースコードをリロードするので、ソースを修正した後にサーバーの再起動は必要ありません。

4.10. 少し休憩して、Postモデルの説明

Postモデルのapp/models/post.rbは次のようになっています。

class Post < ActiveRecord::Base
end

このファイルには特にこれといったコードは記載されていません。しいて言えば、PostクラスではActiveRecord::Baseクラスを継承しています。しかしこれは非常に重要であり、Active Recordは非常の多くの機能を自分で作成したモデルクラスで使えるようにしてくれます。例えば、基本的なデータベースのCRUD操作(作成 Create、検索 Read、更新 Update、削除 Destroy)、データのバリデーション(検証)、さらには、複雑な検索や他のモデルへのアソシエーション(リレーション)をサポートしています。

4.11. バリデーションを追加する

Railsでは、モデルを扱うときにデータのバリデーションを行うことができます。
app/models/post.rbを開いて、バリデーションのためのメソッドを追加してください。

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

これは、全ての投稿が「titleの値を持っており、それは、少なくとも5文字以上の長さが必要」と制限をつけています。Railsではモデルのカラムの値の存在有無、一意性、フォーマットチェックなど様々な状況を検証することができます。

バリデーションについてより詳しく知りたい場合は、「Railsのバリデーションと独自バリデート」や「Rails Guides - Active Record Validations」を参照して下さい。

バリデーションを追加したので、無効なpostオブジェクトを@post.saveで保存したとき(今回の場合はタイトルが空か5文字以下の場合)に、バリデーションによりfalseが返ってきます。
そのため、app/controllers/posts_controller.rbを再び開き、createアクションのソースコードを見て下さい。現在@post.saveの返り値を無視していますが、もし、返り値がfalse(バリデーションが失敗する)場合は、ユーザーにフォームを再表示させるようにしましょう。
このためには、newcreateアクション内を次のように修正します。

# GET /posts/new
def new
  @post = Post.new
end

# POST /posts
def create
  @post = Post.new(post_params)

  if @post.save
    redirect_to @post
  else
    render :new, status: :unprocessable_entity
  end
end

newアクションでは、インスタンス変数の@postに空のPostオブジェクトを設定しています。なぜそのようにしているかはすぐに分かるのでこのまま読み進めて下さい。

createアクションでは、saveメソッドfalseを返すときにredirect_toメソッドの代わりにrenderメソッドを使うように修正しました。renderメソッドの引数に'new'を渡すことで、newテンプレートを表示するように指定しています。

http://localhost:3000/posts/newをリロードし、タイトルを空のまま登録ボタンを押して下さい。すると、同じ画面(フォームが表示されている画面)が表示されます。
しかし、エラーメッセージなどが表示されないのでユーザーにはなぜ同じ画面が表示されたか分からりません。そのため、app/views/posts/new.html.erbを修正し、エラーメッセージを表示するように修正しましょう。

<h1>新しい投稿</h1>
<%= form_for :post, url: posts_path do |f| %>
  <% if @post.errors.any? %>
    <div id="error_explanation">
      <h2><%= @post.errors.count %>件のエラーが発生したため保存ができませんでした。</h2>
      <ul>
      <% @post.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
  ...
  ...

@post.errors.any?@post@post.errors.full_messagesで全てのエラーを表示しています。

さて、なぜコントローラーのnewアクションに@post = Post.newを追加したか説明します。
もし、@post = Post.newを追加しないと、new.html.erbが表示されるときに、@postnilなので、@post.errors.any?を呼んだときに例外が発生してしまうからです。

Railsはバリデーションエラーが発生したフィールドを自動的に<div class="field_with_errors">で囲みます。これを使い、エラーが発生したフィールドを目立たせるためにcssを定義することができます。是非バリデーションエラーが発生したHTMLソースを確認してみて下さい。

さて、再度http://localhost:3000/posts/newで、タイトルを空のままサブミットしてください。素敵なエラーメッセージが表示されるでしょう。
f:id:nipe880324:20140813135554p:plain:w480

エラーメッセージの日本語化などより詳細に知りたい場合は「モデルのバリデーションエラーメッセージ errors や エラーメッセージの日本語化」を確認して下さい。

4.12. 投稿を更新する

現在、CRUDの"CR"を実装しました。次は、"U"の部分である「投稿を更新」できるように実装していきましょう。

最初のステップは、PostsControllerクラスにeditアクションを追加することです。

# GET /posts/:id/edit
def edit
  @post = Post.find(params[:id])
end

次のステップは、編集用の画面を作ります。内容は新しい投稿を作成する画面とほとんど同じです。
app/views/posts/edit.html.erbテンプレートを作成し、次の内容を記載して下さい。
new.html.erbの内容をコピーし、違いを比較しながら編集するとそれぞれの差分がよく分かるのでお勧めです)

<h1>投稿を更新</h1>
<%= form_for :post, url: posts_path, method: :patch do |f| %>
  <% if @post.errors.any? %>
    <div id="error_explanation">
      <h2><%= @post.errors.count %>件のエラーが発生したため保存ができませんでした。</h2>
      <ul>
      <% @post.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, cols: 60, rows: 8 %>
  </p>
 
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to '戻る', posts_path %>

form_for内のmethod: :patchは、HTTPのPATCHメソッドでフォームの内容をアプリケーションに送信するように指定しています。RESTでは、リソースを更新するためにPATCHPUTメソッドを使います。Railsはどちらも同じように処理できますが、全体としてPATCHメソッドを使おうという流れがあるので、ここではオプションに:patchを指定しています。
form_forヘルパーで作成されるフォームのデフォルトのmethod属性はPOST
のため、ここでは明示的にオプションで指定する必要があったわけです。

さて、app/controllers/posts_controller.rbupdateアクションを作成しましょう。
こちらもほとんど、createアクションと同じになります。

# PATCH /posts/:id
# PUT   /posts/:id
def update
  @post = Post.find(params[:id])

  if @post.update(post_params)
    redirect_to @post
  else
    render :edit, status: : unprocessable_entity
  end
end

既に存在しているレコードを更新するときに、コントローラー内のeditupdateアクションが使われます。
updateアクションでは、createアクションと同様に、

  • バリデーションエラーが発生した場合、ユーザーにエラーメッセージと共に編集画面(edit.html.erb)が表示されます。
  • 更新に成功した場合は、詳細画面(show.html.erb)が表示されます。

最後に、投稿の一覧画面(index.html.erb)にeditアクションへのリンクを追加しましょう。
app/views/posts/index.html.erbを開きlink_toメソッドを追加します(link_to "編集" ...)。ついでに、詳細画面(show.html.erb)へのリンク(link_to "詳細" ...)も追加しています。

<h1>全ての投稿一覧</h1>

<table>
  <tr>
    <th>タイトル</th>
    <th>内容</th>
    <th></th>
    <th></th>
  </tr>

  <% @posts.each do |post| %>
    <tr>
      <td><%= post.title %></td>
      <td><%= post.text %></td>
      <td><%= link_to '詳細', post %></td>
      <td><%= link_to '編集', edit_post_path(post) %></td>
    </tr>
  <% end %>
</table>

<%= link_to '新しい投稿', new_post_path %>

そして、app/views/posts/show.html.erbテンプレートにも同様に、編集画面へのリンクを追加しておきましょう。

...
<%= link_to '戻る', posts_path %>
| <%= link_to '編集', edit_post_path(@post) %>

現在の投稿一覧画面を確認しましょう。
f:id:nipe880324:20140813140345p:plain:w480

4.13. 部分テンプレート(パーシャル)を使ってビューの重複箇所を削除する

前にも触れたましたが、作成画面(new.html.erb)と編集画面(edit.html.erb)はとても似ています。部分テンプレートを使うことで重複部分を削除しましょう。
部分テンプレートの規約として、「ファイル名はアンダースコア(_)で始める必要」があります。

※部分テンプレートについてより詳しく知りたい場合は、「Railsのrenderメソッドと部分テンプレート」や「Rails Guides - Layouts and Rendering in Rails」を参照して下さい

app/views/posts/_form.html.erbという部分テンプレートを新しく作成して、次の内容を記載して下さい。
new.html.erbedit.html.erb のどちらからかソースコードをコピーして、それを修正する形で_form.html.erbを作ると理解が深まるのでお勧めです。
ちなみに、コピーした後の修正内容は、フォーム内はform_forメソッドの行以外はそのままです。あとは、最上部のh1タグと最下部のlink_toを削除するだけです。)

<%= form_for @post do |f| %>
  <% if @post.errors.any? %>
    <div id="error_explanation">
      <h2><%= @post.errors.count %>件のエラーが発生したため保存ができませんでした。</h2>
      <ul>
      <% @post.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, cols: 60, rows: 8 %>
  </p>
 
  <p>
    <%= f.submit %>
  </p>
<% end %>

form_forはどのようにして正しいHTTPメソッドとアクションを作成しているのでしょうか。
ということは後に回し、先に作成した部分テンプレートを使うためにapp/views/posts/new.html.erbビューを更新しましょう。

<h1>新しい投稿</h1>

<%= render 'form' %>

<%= link_to '戻る', posts_path %>

では同じく、app/views/posts/edit.html.erbビューも更新しましょう。

<h1>投稿を更新</h1>

<%= render 'form' %>

<%= link_to '戻る', posts_path %>

画面を確認すると前と同じように動くでしょう。

4.14. 投稿を削除する

次は、"CRUD"の"D"の部分である「投稿を削除する機能」を実装しましょう。
RESTの規約に従うと、リソースを削除するルートは次のようになります。

Verb   URI Pattern               Controller#Action
DELETE /posts/:id(.:format)      posts#destroy

リソースを削除するためにdestroyアクションを定義します。
app/controllers/posts_controller.rbdestroyアクションを追加しましょう。

# DELETE /posts/:id
def destroy
  @post = Post.find(params[:id])
  @post.destroy

  redirect_to posts_path
end

データベースからレコードを削除したいときには、Active Recordのdestroyメソッドを使います。
destoryアクションの後に、posts_pathにリダイレクトする(indexアクションが呼ばれる)のでビューを新たに作る必要はありません。

最後に、destoryアクションを呼び出すリンクを一覧画面に追加します。app/views/posts/index.html.erbを開き、link_toメソッドを追加してください。

<h1>全ての投稿一覧</h1>

<table>
  <tr>
    <th>タイトル</th>
    <th>内容</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

  <% @posts.each do |post| %>
    <tr>
      <td><%= post.title %></td>
      <td><%= post.text %></td>
      <td><%= link_to '詳細', post %></td>
      <td><%= link_to '編集', edit_post_path(post) %></td>
      <td><%= link_to '削除', post_path(post),
                method: :delete, data: { confirm: '削除しますか?' } %></td>
    </tr>
  <% end %>
</table>

<%= link_to '新しい投稿', new_post_path %>

削除するためにlink_toメソッドを新しい使い方で使っています。methodオプションでHTTPメソッドを指定し、dataオプションで確認ダイアログを表示するように指定しています。
ユーザーが「削除」リンクを押すと、確認ダイアログが表示され、そこで「OK」ボタンを押すと、DELTE /posts/:idがサーバー上に送信され、destroyアクションが実行されることによりデータが削除されます。

この削除処理の多くは JavaScript で実現されています。
アプリケーションのレイアウトファイル(app/views/layouts/application.html.erb)のheadタグ内で自動的にインクルードされるJavaScriptファイル(jquery_ujs)によって実現されています。このJavaScriptのファイルがない場合、確認ダイアログは表示されず、DELETEメソッドもサーバーに送信されないため、リソースを削除することができません。

f:id:nipe880324:20140813141050p:plain:w480


おめでとうございます!
あなたは、投稿を作成、詳細確認、一覧表示、更新、削除ができるようになりました。 :)



5. コメントを追加する

では、アプリケーションに2つ目のモデルを追加するときが来ました。
2つ目のモデルは投稿の「コメント」です。

5.1. モデルを生成する

Postモデルを生成するときに使ったときと同じジェネレーターを使い、Commentモデルを作成します。

$ rails generate model Comment commenter:string body:text post:references

このコマンドで以下の4つのファイルが作成されます。

ファイル目的
db/migrate/20140811090504_create_comments.rbcommentsデータベースにテーブルを作成するマイグレーションファイル
(ファイル名のタイムスタンプは異なります)
app/models/comment.rbCommentモデルを記述するファイル
test/models/comment_test.rbCommentモデルのテストを記述するファイル
test/fixtures/comments.ymlテストで使用するサンプルのCommentオブジェクトを定義するファイル

最初に、app/models/comment.rbのファイルの中身を確認しましょう。

class Comment < ActiveRecord::Base
  belongs_to :post
end

このファイルは、post.rbととてもよく似ています。主に違う点は、belongs_to :post列です。これは、Active Recordのアソシエーション(関連 or リレーション)を設定するためのものです。

ジェネレーターにより、モデルに加え、データベースのテーブルを作成するマイグレーションファイルも作成されます。db/migrate/20140811090504_create_comments.rbの中身を確認しましょう。

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body
      t.references :post, index: true

      t.timestamps
    end
  end
end

create_table :commentscommentsテーブルを作成ことを意味しており、その内部のブロックでcommentsテーブルのカラムを作成しています。
t.string :commenterはString型のcommenterというカラムを作成するという意味です。
次の行も同じです。
t.references行は「外部キー」定義しています。加えて、index: trueオプションによりその外部キーをインデックスにしています。
t.timestampsは、レコードの作成日付(created_at)と更新日付(updated_at)を保持するカラムを作成します(Active Recordにより自動的に値が更新されるので、私たちが直接値を操作する必要はありません。作成順や更新順などにデータを表示したい場合などにソートで使ったりします。)

では、マイグレーションを実施しましょう。

$ rake db:migrate

Railsは既にデータベースに適用済みのマイグレーションファイルは実行しません。(postsテーブルを作成するマイグレーションは実行されない)
そのため、次のようにcommentsテーブルを作成するマイグレーションファイルのみ実行されたます。

== 20140811090504 CreateComments: migrating ===================================
-- create_table(:comments)
   -> 0.0054s
== 20140811090504 CreateComments: migrated (0.0055s) ==========================

5.2. モデル間に関連づける

Active Recordアソシエーションはモデル間の関連を簡単に定義することができます。
今回のCommentモデルとPostモデルの場合、次のようにモデル間の関係を考えることができます。

  • Each comment belongs to one post.(各々のcommentは、1つのpostに属する)
  • One post can have many comments.(1つのpostは、複数commentsを持てる)

つまり、PostとCommentは「1対多関係」になっています。

この英語の書き方は、Railsでのアソシエーションの定義の仕方にとても似ています。
Commentモデル(app/models/comment.rb)で先ほど確認したアソシエーションの定義は、「comment belongs to a post」と読めます。

class Comment < ActiveRecord::Base
  belongs_to :post
end

Postモデル(app/models/post.rb)側にアソシエーションを定義しましょう。
こちらは、「post has many comments」と読めます。

class Post < ActiveRecord::Base
  has_many :comments

  validates :title, presence: true, length: { minimum: 5 }
end

この2つの宣言を追加することで、それぞれのモデルクラスは自動的にモデル間のアソシエーションを認識します。
例えば、@postというインスタンス変数にPostオブジェクトが設定されているとします。そのときに、@post.commentsにアクセスすることにより、そのPostオブジェクトに関連づく全てのCommentオブジェクトにアクセスできます。

Active Recordのアソシエーションについてより詳しく知りたい場合は、「Railsのアソシエーションの作成方法」や「Rails Guides- Active Record Association」を参照して下さい。


5.3. Commentモデルにルートを追加する

他のコントローラーと同じように、Comment用にもルートを追加する必要があります。
config/routes.rbファイルを開き、次のように修正して下さい。

resources :posts do
  resources :comments
end

今回追加したcommentspostsにネストされています。これは、「投稿」と「コメント」の間に意味的に存在する階層構造を表しています。「コメント」はコメント単体では存在せず、「投稿」に紐づいて「コメント」が存在するためです。

これを説明するために、現在のルートを表示してみましょう。

$ rake routes
           Prefix Verb   URI Pattern                                 Controller#Action
    post_comments GET    /posts/:post_id/comments(.:format)          comments#index
                  POST   /posts/:post_id/comments(.:format)          comments#create
 new_post_comment GET    /posts/:post_id/comments/new(.:format)      comments#new
edit_post_comment GET    /posts/:post_id/comments/:id/edit(.:format) comments#edit
     post_comment GET    /posts/:post_id/comments/:id(.:format)      comments#show
                  PATCH  /posts/:post_id/comments/:id(.:format)      comments#update
                  PUT    /posts/:post_id/comments/:id(.:format)      comments#update
                  DELETE /posts/:post_id/comments/:id(.:format)      comments#destroy
            posts GET    /posts(.:format)                            posts#index
                  POST   /posts(.:format)                            posts#create
         new_post GET    /posts/new(.:format)                        posts#new
        edit_post GET    /posts/:id/edit(.:format)                   posts#edit
             post GET    /posts/:id(.:format)                        posts#show
                  PATCH  /posts/:id(.:format)                        posts#update
                  PUT    /posts/:id(.:format)                        posts#update
                  DELETE /posts/:id(.:format)                        posts#destroy
             root GET    /                                           welcome#index

上の方のルートはcommentsと入っているURLがあります。
しかし、posts/:post_id/comments ...となっています。
これは今まさに説明した通り、「コメント」にアクセスするためには「投稿」がないと作成や更新、表示などができないことを表しています。

※「Railsのルーティング」についてより詳細に知りたい場合は、
Railsのルーティングメモ」や「Rails Guides - Rails Routing」を参照して下さい。

5.4. コントローラーを生成する

モデルを作成したので、次はコントローラーを作成していきましょう。毎度のことですが、コントローラーを生成するときも「ジェネレーター」を使います。

$ rails generate controller Comments

このコマンドで、以下の6つのファイルと1つの空ディレクトリが作成されます。

ファイル目的
app/controllers/comments_controller.rbCommentsControllerを実装するファイル
app/views/comments/このディレクトリ内にCommentsのビューファイルを配置する
test/controllers/comments_controller_test.rbCommentsConntrolerのテストを記述するファイル
app/helpers/comments_helper.rbCommentsビューのヘルパーファイル
test/helpers/comments_helper_test.rbCommentsビューのヘルパーのテスト用ファイル
app/assets/javascripts/comments.js.coffeeCommentsのCoffeeScriptを記述する
app/assets/stylesheets/comments.css.scssCommentsのScss(スタイルシート)を記述する

一般的なブログでは、「投稿記事」を読んだ最後に「コメント」を記載します。そして、一度「コメント」が記載されると、「投稿記事」の詳細画面(show.html.erb)でその「コメント」が一覧表示されます。

この挙動を実現するために、CommentsControllerでは、コメントを作成するcreateメソッドとスパムコメントを削除するdestroyメソッドを定義します。

最初に、投稿の詳細画面(app/views/posts/show.html.erb)にコメントを表示するように修正しましょう。

<p>
  <strong>タイトル:</strong>
  <%= @post.title %>
</p>
 
<p>
  <strong>内容:</strong>
  <%= @post.text %>
</p>

<!-- 追加箇所 開始 -->
<h2>コメントを追加</h2>
<%= form_for( [@post, @post.comments.build] ) do |f| %>
  <p>
    <%= f.label :commenter, "コメンター" %><br />
    <%= f.text_field :commenter %>
  </p>

  <p>
    <%= f.label :body, "コメント" %><br />
    <%= f.text_area :body, cols: 60, rows: 8 %>
  </p>

  <p>
    <%= f.submit, "コメント投稿" %>
  </p>
<% end %>
<!-- 追加箇所 終了 -->

<%= link_to '戻る', posts_path %>
| <%= link_to '編集', edit_post_path(@post) %>

ここでは、コメントを記載できるフォームを追加しました。
ここでのform_forでは配列を使っています。これは、ネストされたルートを作成します。例えば、/posts/1/commentsのようなルートを作成します。config/routes.rbでURLをネストしたため、form_forでもルートをネストさせる必要があったのです。

app/controllers/comments_controller.rbcreateアクションを追加しましょう。

class CommentsController < ApplicationController
  # POST /posts/:post_id/comments
  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.create(comment_params)

    redirect_to post_path(@post)
  end

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

今までのコントローラーより少しだけ複雑に見えるかもしれません。私たちが設定したネストの影響です。
「コメント」へのリクエストには、その「コメント」に関連している「投稿」の情報が必要になります。そのため、最初の行でPost.findで「コメント」に関連する「投稿」を取得しています。

さらに、このコードではアソシエーションを定義したことによって利用可能になったメソッドを使っています。
@post.comments.createは「コメント」を作成し、データベースに保存しています。これは、自動的に「コメント」と「投稿」の間でアソシエーションを追加(外部キーの設定)してくれます。

新しい「コメント」を作成すると、ユーザーはpost_path(@post)ヘルパーによって投稿の詳細画面にリダイレクトされます。リダイレクトされたパスにより、PostsControllershowアクションが呼び出されます。showアクション内では、show.html.erbテンプレートをレンダリングします。

では、「コメント」の表示を「投稿」の詳細画面(app/views/posts/show.html.erb)に追加しましょう。

<p>
  <strong>タイトル:</strong>
  <%= @post.title %>
</p>
 
<p>
  <strong>内容:</strong>
  <%= simple_format @post.text %>
</p>

<!-- 追加箇所 開始 -->
<h2>コメント</h2>
<% @post.comments.each do |comment| %>
  
  <p>
    <strong>コメンター:</strong>
    <%= comment.commenter %>
  </p>
   
  <p>
    <strong>コメント:</strong>
    <%= simple_format comment.body %>
  </p>
<% end %>
<!-- 追加箇所 終了 -->

<h2>コメントを追加</h2>
<%= form_for( [@post, @post.comments.build] ) do |f| %>
  <p>
    <%= f.label :commenter, "コメンター" %><br />
    <%= f.text_field :commenter %>
  </p>

  <p>
    <%= f.label :body, "コメント" %><br />
    <%= f.text_area :body, cols: 60, rows: 8 %>
  </p>

  <p>
    <%= f.submit, "コメント投稿" %>
  </p>
<% end %>

<%= link_to '戻る', posts_path %>
| <%= link_to '編集', edit_post_path(@post) %>

simple_formatヘルパーメソッドを使っています。これは、改行などを認識して表示するメソッドです。

さて、ブラウザを開いて、投稿にコメントを追加しましょう。次のように表示されるでしょう。
f:id:nipe880324:20140813144316p:plain:w480


6. リファクタリング

さてさて、今のところ私たちは投稿とコメント機能が実装できました。しかし、ちらっと app/views/posts/show.html.erbを見てください。
少しソースコードが長く、汚くないでしょうか。綺麗にするために部分テンプレート(パーシャル)を使ってリファクタリングしましょう。

6.1. 部分テンプレートでコレクションをレンダリングする

最初に、投稿の全てのコメントを表示するために部分テンプレートを作成します。app/views/comments/_comment.html.erbを新規に作成して、次の記述を追記して下さい。

<p>
  <strong>コメンター:</strong>
  <%= comment.commenter %>
</p>
 
<p>
  <strong>コメント:</strong>
  <%= simple_format comment.body %>
</p>

次は、app/views/posts/show.html.erb を修正して下さい。

<p>
  <strong>タイトル:</strong>
  <%= @post.title %>
</p>
 
<p>
  <strong>内容:</strong>
  <%= @post.text %>
</p>

<h2>コメント</h2>
<!-- 修正箇所 開始 -->
<%= render @post.comments %>
<!-- 修正箇所 終了 -->

<h2>コメントを追加</h2>
<%= form_for( [@post, @post.comments.build] ) do |f| %>
  <p>
    <%= f.label :commenter, "コメンター" %><br />
    <%= f.text_field :commenter %>
  </p>

  <p>
    <%= f.label :body, "コメント" %><br />
    <%= f.text_area :body, cols: 60, rows: 8 %>
  </p>

  <p>
    <%= f.submit "コメント投稿" %>
  </p>
<% end %>

<%= link_to '戻る', posts_path %>
| <%= link_to '編集', edit_post_path(@post) %>

render @post.commentsにより、@post.commentsコレクションのそれぞれのコメントで部分テンプレートのapp/views/comments/_comment.html.erbレンダリングします。
renderメソッドはコレクションを1つ1つ処理し、そして、それぞれのコメントを「部分テンプレートと同じ名前のローカル変数」に割り当てます。
今回の場合で言うと、部分テンプレート内では、commentという名のローカル変数が使えます。

7.2. 部分テンプレートでフォームをレンダリングする

コメントを追加する箇所のソースコードを部分テンプレートにしていきましょう。
app/views/comments/_form.html.erbを新規作成します。
app/views/comments/_comment.html.erbからソースコードから部分テンプレートに置き換える箇所をコピーすると簡単です。)

<%= form_for( [@post, @post.comments.build] ) do |f| %>
  <p>
    <%= f.label :commenter, "コメンター" %><br />
    <%= f.text_field :commenter %>
  </p>

  <p>
    <%= f.label :body, "コメント" %><br />
    <%= f.text_area :body, cols: 60, rows: 8 %>
  </p>

  <p>
    <%= f.submit "コメント投稿" %>
  </p>
<% end %>

そして、app/views/posts/show.html.erbを次のように修正します。

<p>
  <strong>タイトル:</strong>
  <%= @post.title %>
</p>
 
<p>
  <strong>内容:</strong>
  <%= @post.text %>
</p>

<h2>コメント</h2>
<%= render @post.comments %>

<h2>コメントを追加</h2>
<!-- 修正箇所 開始 -->
<%= render "comments/form" %>
<!-- 修正箇所 終了 -->

<%= link_to '戻る', posts_path %>
| <%= link_to '編集', edit_post_path(@post) %>

二つ目のrenderメソッドでは、レンダリングしたい部分テンプレートcomments/formとして指定しました。renderメソッドの引数が文字列で、その中にスラッシュ(/)が存在する場合、Railsapp/views/配下からのパスとして部分テンプレートを探します。
そのため、今回の場合は、comments/formを指定することで、app/views/comments/_form.html.erbレンダリングされました。

@postというインスタンス変数はどの部分テンプレートでも使うことが可能です。なぜならば、インスタンス変数として定義されているからです。

では、リファクタリング後の投稿の詳細画面を表示してみましょう。表示は変わってないですね。
リファクタリングをしただけなので、いいことです :D)
f:id:nipe880324:20140813144316p:plain:w480



7. コメントを削除する

ブログアプリケーションにおいて、スパムコメントを消せることは重要な機能でしょう。
それを実現するために、

  • スパムコメントを削除するCommentsControllerdestroyアクション
  • ビュー内にそのdestroyアクションを呼び出すリンク

を追加していきます。

7.1. コメント削除リンクとdestroyアクションを追加する

まずは、app/views/comments/_comment.html.erbパーシャルに削除リンクを追加しましょう。

<p>
  <strong>コメンター:</strong>
  <%= comment.commenter %>
</p>
 
<p>
  <strong>コメント:</strong>
  <%= comment.body %>
</p>

<!-- 追加箇所 開始 -->
<p>
  <%= link_to 'コメント削除', [comment.post, comment],
              method: :delete, data: { confirm: '削除しますか?' } %>
</p>
<!-- 追加箇所 終了 -->

このリンクをクリックすると、サーバーにDELETE /posts/:post_id/comments/:idがリクエストされるので、CommentsControllerdestroyアクションが呼ばれます。

では、app/controllers/comments_controller.rbdestroyアクションを追加しましょう。

# DELETE /posts/:post_id/comments/:id
def destroy
  @post = Post.find(params[:post_id])
  @comment = @post.comments.find(params[:id])
  @comment.destroy

  redirect_to post_path(@post)
end

destoryアクションは、@post.commentsコレクション内の「コメント」を探し出し、データベースから削除しています。そして、「投稿記事」の詳細画面にリダイレクトしています。

7.2. アソシエーションで関連したオブジェクトを削除する

もし「投稿記事(post)」を削除した場合、それに関連づいている「コメント(comment)」も削除されるべきです。そうしなければ、データベースにゴミデータが残ってしまうからです。
dependentオプションを使うことでこれをとっても簡単に実現することができます。
Postモデル(app/models/post.rb)にdependentオプションを追加しましょう。

class Post < ActiveRecord::Base
  has_many :comments, dependent: :destroy

  validates :title, presence: true, length: { minimum: 5 }
end

これで、@post.destoryで「投稿」を削除すると、その「投稿」に関連づいた「コメント」も自動的に一緒に削除されます。



8. HTTP認証を追加する

もしこのブログをオンラインに公開したい場合、他の誰か知らない人が投稿を追加、編集、削除してしまうかもしれません。
このような場合のために、Railsはとても簡単なHTTP認証システムを提供しています。

※本格的に認証する場合はdeviseという認証用のgemを使うことをお勧めします。deviseは、ログイン画面やユーザ登録など一般的な認証システムを提供してくれます。deviseの導入方法やカスタマイズは、deviseの導入を参照して下さい。

http_basic_authenticate_withメソッドを使うことで、特定のコントローラーのアクションの実施に認証が必要となります。
PostsController(app/controllers/posts_controller.rb)の上部にメソッドを追加しましょう。

class PostsController < ApplicationController

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

  ...
  ...

今回の場合は、indexshowアクション以外のアクション(new, create, edit, update, destroy)にHTTP認証が必要になります。
認証時のユーザ名とパスワードは、"user" と "password" です。

また、認証されたユーザーがコメントを削除するようにしたいので、CommentsController(app/controllers/comments_controller.rb)にも同じように追加しましょう。

class CommentsController < ApplicationController

  http_basic_authenticate_with name: "user", password: "secret", only: :destroy

  ...
  ...

上記を記載することで、CommentsControllerdestroyアクションだけ認証が必要になります。

さて、投稿の一覧画面(http://localhost:3000/posts)で新規の投稿を作成するために「新しい投稿」リンクを押すと、HTTP認証のダイアログが表示されます。
f:id:nipe880324:20140813150152p:plain:w480

9. 画面のスタイリングをする

ここまでブログの一通りの機能を実装することができました。

  • 投稿の一覧表示、作成、更新、詳細表示、削除ができます。
  • 投稿に対してコメントの作成と削除ができます。
  • さらに、認証に成功したユーザのみ上記の特定の操作ができます。



ここからは、Rails Guidesには乗っていない、Railsでの画面のスタイリング方法やJavaScriptの使い方を説明していきます。

まずは、Railsでのスタイリングです。
Railsスタイルシートは、app/stylesheets/配下に配置します。
デフォルトでは、拡張子css.scssとなっています。
.scssScssという「コンパイルによりCSSを出力するメタ言語」を表しています。

Scssは見た目はほとんどCSSと同じです。実際にCSSで記載しても動きます。しかし、主な機能としては、「変数が使える」「ネストが使える」「Mixinができる」「セレクタの継承ができる」などあります。そして、それらの機能によりCSSで書くよりもより生産性や保守性が向上することが期待できます。

ここでは、Scssの記載内容について細かく説明しません。
どちらかというと、Railsではどうやってスタイリングファイルが指定され、どのような流れでScssを記載していくかということを説明します。

9.1. Railsスタイルシートの読み込み

app/views/layouts/application.html.erbというファイルを開いてみて下さい。

<!DOCTYPE html>
<html>
<head>
  <title>Blog</title>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

HTMLのドキュメント定義やheadタグやbodyタグが記載されていることがわかると思います。
このファイルは、今まで表示してきたテンプレートファイル(new.html.erbなど)の外側の枠組みとなるファイルです。テンプレートファイルの内容は、<%= yield %>の箇所に表示されます。
そのため、このファイルを修正することにより、2段カラムなどのレイアウトをWebアプリケーション全ての画面に適用することができます。

さて、headタグ内のstylesheet_link_tagと記載されている行を確認して下さい。
これは、app/assets/stylesheets/application.cssをインクルードすることを意味しています。
そして、application.css内では、app/assets/stylesheets/配下の全てのファイルをインクルードするように指定されています。
そのため、この章の最初で述べたように、「Railsスタイルシートは、app/stylesheets/配下に配置」する必要があります。

9.2. アプリケーション全体のスタイリングをする

今回スタイリングした結果のイメージは次のようになります。
f:id:nipe880324:20140813153533p:plain:w480

まずアプリケーション全体のスタイリング(レイアウト作成)をしていきましょう。
アプリケーション全体なのでapp/views/layouts/application.html.erbを修正します。headerタグで「ヘッダー部分」と<div id="main">タグで「コンテンツ部分」に分けてみました。

<!DOCTYPE html>
<html ja="ja">
<head>
  <meta charset="utf-8" />
  <title>Blog</title>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body class='<%= controller.controller_name %>'>
<div id="wrap">

  <header id="header">
    <div class="inner">
      <h1 id="logo"><%= link_to "Sample My Blog", root_path %></h1>
      <p id="catchcopy">Description or something ....</p>
    </div><!-- /.inner -->
  </header><!-- /#header -->

  <div id="main">
    <%= yield %>
  </div><!-- /#main -->
  
</div><!-- /#wrap -->
</body>
</html>

その後、ScssCSSを記述していきます。
app/assets/stylesheets/配下を見て下さい。
application.csscomments.css.scssposts.css.scssなどのファイルが存在すると思います。application.cssについては上部で既に説明したのでその他のファイルについて説明します。
お気づきかもしれませんが、posts.css.scssのファイル名の先頭が「ビューのフォルダ名」と同じです。これは、投稿関連のビュー(app/assets/posts/配下のファイル)のスタイリングをする場合は、posts.css.scssを使うと、スタイリングのコードがどこに書かれているか分かりやすいのでよいです。

※上記の項目で説明しましたが、Railsではapplication.cssにより全ての画面でapp/assets/stylesheets/配下の全てのスタイリングシートを読み込むため、それぞれのファイルに分けてCSSを書いた方がよいだけであり、一つのファイルにCSSを動いても動きます。
また、Railsはビュー毎に読み込むCSSを変えているわけではないので、CSSファイル間のidclassのスタイリングの競合に注意して下さい。

今回は、基本的な色やレイアウトを指定するファイルのapp/assets/stylesheets/common.css.scssを作成し、次の内容を記載して下さい。
(スタイリングの仕方の話になると本が書けてしまうので、コピーで大丈夫です)

// カラー変数
$baseBlack:     #333;
$baseGray:      #aaa;
$baseLightGray: #eee;
$baseBlue:      #428bca;
$baseLightBlue: #5bc0de;

/*-----------------------------

 リセットCSS + サイト共通設定

------------------------------*/
body,
h1,h2,h3,h4,h5,h6,
p,ul,ol,dl,dt,dd,li,
table,th,td,
form,select,option,input
address,pre,strong,em,iframe,img{
  margin:0;
  padding:0;
  font-size:100%;
  border:0;
}

/* デフォルト文字設定 */
html {
  height: 100%;
}

body {
  height: 100%;

  color: $baseBlack;
  font-family: "Gotham Narrow SSm",Arial,"ヒラギノ角ゴ Pro W3","Hiragino Kaku Gothic Pro",Osaka,"メイリオ",Meiryo,"MS Pゴシック","MS PGothic",sans-serif;
  font-size: 14px;
  line-height: 1.4;
}


/* リストのマーカーを非表示 */
ul, ol {
  list-style-type:none;
}

/* デフォルトリンク色の設定 */
a,
a:-webkit-any-link {
  text-decoration: none;

  color: $baseGray;

  &:hover {
    color: $baseLightBlue;
  }
}


/*--------------------------

 レイアウト

---------------------------*/
body {
  border-top: $baseGray 1px solid;
}

/* ページ全体のラッパー
---------------------------*/
#wrap {
  position: relative;
  width: 100%;
  height: auto !important;
  height: 100%;
  min-height: 100%;

  background-color: $baseBlue;
}

/* ヘッダー
---------------------------*/
header {
  border-bottom: $baseGray 1px solid;
  background-color: white;

  .inner {
    width: 800px;
    height: 58px;
    margin: 0 auto;
   
    #logo {
      font-size: 250%;
      text-decoration: none;
    }

    #catchcopy {
      color: $baseGray;
      font-size: 90%;
      position: relative;
      top: -10px;
      left: 5px;
    }
  }
}


/* メインコンテンツ
---------------------------*/
#main {
  width: 800px;
  height: 100%;

  padding: 10px;

  margin: 30px auto;

  background-color: white;
  border-radius: 5px;


  h1 {
    font-size: 200%;
    margin-bottom: 20px;
    border-bottom: 1px solid $baseBlue;
  }
}


/*-----------------------------

 共通コンポーネント

------------------------------*/
input[type="text"],
textarea {
  border-radius: 3px;
  border: 1px solid $baseGray; 
}


では、画面を開き、スタイリングされていることを確認しましょう。
もっさりした感じがなくなりましたね。
f:id:nipe880324:20140813153533p:plain:w480

9.2. 投稿詳細画面のスタイリングをする

全体のスタイリングをしたので、ほとんど見た目はよくなりました。しかし、各メインコンテンツは各々スタイリングが必要です。
今回は特に最もスタイリングが必要そうな投稿詳細画面(app/views/posts/show.html.erb)をスタイリングしていきます。

まず、tableタグやinput要素はアプリケーション全体で使う「共通コンポーネント」のためcommonの一番下に追記します。

/*-----------------------------

 共通コンポーネント

------------------------------*/
input[type="text"],
textarea {
  border-radius: 3px;
  border: 1px solid $baseGray; 
}

次に、個別の画面の微調整はコントローラー毎にあるファイルを修正します。
今回は「投稿(post)」なので、app/assets/stylesheets/posts.css.scssです。
しかし、先ほど言った通り、Railsでは全ての画面で全てのScssファイルを読み込んでしまうので一工夫が必要です。
app/views/layouts/application.html.erbbodyタグにclass属性を追加し、値は現在ビューを表示している"コントローラー名"にしましょう。

<body class='<%= controller.controller_name %>'>

HTMLソースをみれば分かりますが、これは、次のようにHTMLに変換されます。

<!--
  ルート画面(app/views/welcome/index.html.erb)のような
  WelcomeControllerでレンダリングされた画面
-->
<body class='welcome'>

<!--
  投稿一覧画面(app/views/posts/index.html.erb)のような
  PostsControllerでレンダリングされた画面
-->
<body class='posts'>

投稿詳細画面(app/views/posts/show.html.erb)のスタイリングをするために、少しapp/views/posts/show.html.erbのHTML構造を修正します。

<div class="post_info">
  <p>
    <strong>タイトル:</strong>
    <%= @post.title %>
  </p>
   
  <p>
    <strong>内容:</strong>
    <%= simple_format @post.text %>
  </p>
</div>

<h2>コメント</h2>
<%= render @post.comments %>

<h2>コメントを追加</h2>
<%= render "comments/form" %>

<%= link_to '戻る', posts_path %>
| <%= link_to '編集', edit_post_path(@post) %>


それではapp/assets/stylesheets/posts.css.scssを記載します。

@import "common.css.scss";

.posts {
  h2 {
    margin-top: 20px;
    margin-bottom: 5px;

    font-size: 110%;
    border-bottom: 1px dotted $baseLightBlue;
  }

  .post_info {
    padding: 20px 5px;
    margin-bottom: 30px;

    background-color: $baseLightGray;
    border-radius: 5px;

    strong {
      font-size: 110%;
    }
  }
}

では、投稿の詳細画面(http://localhost:3000/posts/1)を確認しましょう。
f:id:nipe880324:20140813160348p:plain:w480
これで、スタイリングはおしまいです。


CoffeeScriptJavaScriptを使う

今度は、RailsJavaScriptの使い方を説明します。
その例として、「コメント入力時に文字数をJavaScriptで表示する」ようにします。
イメージ次のような形です。
f:id:nipe880324:20140813160712p:plain:w320


スタイリングをするためにScssが採用されているように、
RailsではJavaScriptを記載するためにCoffeeScriptというメタ言語が採用されています。
簡易にかけますが、学習が必要なのでドットインストール - CoffeeScript入門などを実施してみて下さい。

まず、文字数の表示領域を追加するために、app/views/comments/_form.html.erbを修正します。

<%= form_for( [@post, @post.comments.build] ) do |f| %>
  <p>
    <%= f.label :commenter, "コメンター" %><br />
    <%= f.text_field :commenter %>
  </p>

  <p>
    <%= f.label :body, "コメント" %> <br />
    <%= f.text_area :body, cols: 60, rows: 8 %>
    <!-- 追加箇所 開始 -->
    <div class="hint">入力文字数:<span id="char_num">0</span></div>
    <!-- 追加箇所 終了 -->
  </p>

  <p>
    <%= f.submit "コメント投稿" %>
  </p>


文字数表示をスタイリングしておきます。
app/assets/stylesheets/common.css.scssの一番下に下記を追加して下さい。

.hint {
  font-size: 86%;
  color: $baseGray;
  font-style: italic; 
}

では、JavaScriptを記載します。
Scssと同じように、コメントに関するJavaScriptなのでcomments.js.coffeeに下記を記載します。(CoffeeScriptで記載しています)

$ ->
  $('#comment_body').keyup ->
    $('#char_num').text($(this).val().length)

RailsではgemによりjQueryが標準でインクルードされているため使うことが可能です。しかし、jQueryをCoffeeScriptで記載するには少し癖ありますので、CoffeeScriptでjQueryを使うときのメモなどを読んでみて下さい。

では、実際にコメントを入力し、動的に入力文字列の値が変わることを確認してみて下さい。
f:id:nipe880324:20140813160712p:plain:w320


次は?

おつかれさまでした。そして、おめでとうございます。

あなたは、これでRailsの基本的な開発方法を学ぶことができるようになりました。
ルーティング、コントローラー、モデル、ビュー、スタイリング、JavaScriptなど数々のことについて知ることができました。



さて、今後どうしていったらいいかのアドバイスを簡単に説明していきます。

  • このブログに追加の自力で機能を追加する。例えば、ユーザ認証機能、ファイルアップロード機能など。そのときに、このページのトップにある「有用なライブラリ」のdevisepaperclipの記事がとても参考になると思います。
  • Railsチュートリアル」というオンラインブックで「Twitter風アプリをテスト駆動開発かつAgileで開発」する

GitやRSpecでテストを書いてからコードを書くなどさまざまなことが知れるのでとてもお勧め。

  • 「RailsによるアジャイルWebアプリケーション開発 第4版」という本で「ECサイトをAgileで開発」する。

Railsを開発した人が書いてあるのでとても内容が分かりやすくて実用的だた、Rails 3.2で説明しているのでRailsが少し古いことが欠点

RailsによるアジャイルWebアプリケーション開発 第4版

RailsによるアジャイルWebアプリケーション開発 第4版

  • 作者: Sam Ruby,Dave Thomas,David Heinemeier Hansson,前田修吾
  • 出版社/メーカー: オーム社
  • 発売日: 2011/12/01
  • メディア: 単行本(ソフトカバー)
  • 購入: 12人 クリック: 206回
  • この商品を含むブログ (40件) を見る

  • Rails Guides」を読んでより深く詳細にRailsの各機能について学ぶ。

英語で読むのに時間がかかるというのが欠点だが、内容の豊富さはダントツでお勧め。

Rails界隈ではとても人気な(だった?)スクリーンキャストでRailsGuidsと違った種類の内容の多さはピカイチ。英語で説明しているので分かりづらいことと、2013年1月から更新が止まっていて内容が一部古いこと、一部有料の記事があることが欠点。

「実際に独自で作って、つまりながらも完成させること」と「一部の機能を深く広く学ぶこと」を繰り返していくことにより、どんどんとRailsの知識やスキルが深まっていくのでまずは自分で何か機能を追加してみることをお勧めします。

では、以上です。
わかりづらい、間違っている箇所などありましたらコメントを下さい。
長くなりましたがここまで読んでいただきありがとうございました。
そして、おつかれさまです。