Ruby on Rails Guides(v5.1.3) Getting Started with Railsを読む#1

これは、最新のRuby on Rails ガイド(v5.1.3) を読んだ際の学習記録です。

Getting Started with Rails — Ruby on Rails Guides

#1 Start Here

以下を学ぶ

  • Railsインストール、Railsアプリケーションの作成、そしてアプリケーションをデータベースへ接続する方法
  • 一般的なRailsアプリケーションのレイアウト
  • MVC, RESTfulデザインの基本原則
  • Railsアプリケーションを素早く生成する方法

What is Rails?

RailsRubyで書かれたWebアプリケーションフレームワークRailsには以下の2つの重要な原則がある。

  • Don’t Repeat Yourself

Don't repeat yourself - Wikipedia

Don’t repeat yourself (DRY) あるいはSingle Source of truth(英)[要出典]は、特にコンピューティングの領域で、重複を防ぐ考え方である。この哲学は、情報の重複は変更の困難さを増大し透明性を減少させ、不一致を生じる可能性につながるため、重複するべきでないことを強調する。

  • Convention Over Configuration

設定より規約 - Wikipedia

設定より規約(せっていよりきやく、英: convention over configuration)とは、開発者の決定すべきことを減少させ、単純にするが柔軟性は失わせないというソフトウェア設計パラダイム

Creating a New Rails Project

Creating the Blog Application

‘blog’ アプリケーションを生成する。

$ rails new blog

blog配下には以下のファイルを生成される。

  • app/ Contains the controllers, models, views, helpers, mailers, channels, jobs and assets for your application. You’ll focus on this folder for the remainder of this guide.
  • bin/ Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.
  • config/ Configure your application’s routes, database, and more. This is covered in more detail in Configuring Rails Applications. config.ru Rack configuration for Rack based servers used to start the application.
  • db/ Contains your current database schema, as well as the database migrations.
  • Gemfile
  • Gemfile.lock These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the Bundler website.
  • lib/ Extended modules for your application.
  • log/ Application log files.
  • public/ The only folder seen by the world as-is. Contains static files and compiled assets.
  • Rakefile This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.
  • README.md This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.
  • test/ Unit tests, fixtures, and other test apparatus. These are covered in Testing Rails Applications.
  • tmp/ Temporary files (like cache and pid files).
  • vendor/ A place for all third-party code. In a typical Rails application this includes vendored gems.
  • .gitignore This file tells git which files (or patterns) it should ignore. See Github - Ignoring files for more info about ignoring files.

Hello, Rails!

Starting up the Web Server

web serverを起動する。

$ cd blog
$ rails server
#shorthand: rails s 

Say “Hello”, Rails

Railsで"Hello" を表示させるために、viewとcontrollerを生成する。

‘Welcome’ controllerの'index' アクションを生成する。

$ rails generate controller Welcome index

上記のgenerateコマンドで以下のファイル群が生成される。

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
invoke  assets
invoke    coffee
create      app/assets/javascripts/welcome.coffee
invoke    scss
create      app/assets/stylesheets/welcome.scss

controllerは、app/controllers/welcome_controller.rb, viewはapp/views/welcome/index.html.erb へ生成された。

app/views/welcome/index.html.erb のh1タグを以下のように修正する。

<h1>Hello, Rails!</h1>

Setting the Application Home Page

Railsのroot を設定する。

config/routes.rb へ以下を追加する。

  root 'welcome#index'

http://localhost:3000 へアクセスすると、Hello, Railsが表示される。

Getting Up and Running

CRUD操作を実現するためのresourceを定義する。

以下をconfig/routes.rb へ追加。

  resources :articles

rails routes コマンドを実行すると、標準のRESTfulアクションが定義される。

$ rails routes
       Prefix Verb   URI Pattern                  Controller#Action
welcome_index GET    /welcome/index(.:format)     welcome#index
     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

Laying down the groundwork

生成した、http://localhost:3000/articles/new へアクセスしてみよう。'Routing Error'が出る。 これはrouteは生成したが、まだcontroller、viewが生成されてないため。

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

$ rails generate controller Articles

生成した、app/controllers/articles_controller.rb を見てみよう。

class ArticlesController < ApplicationController
end

ApplicationController を継承した、ArticlesControllerが定義されているが、まだ中には何も定義されていない。

この状態で先程のURIへアクセスしてみよう。

http://localhost:3000/articles/new

‘Unknown action’ エラーが出る。

app/controllers/articles_controller.rb を以下のように更新する。

class ArticlesController < ApplicationController
  def new
  end
end

この状態で先程のURIへアクセスしてみよう。

http://localhost:3000/articles/new

ActionController::UnknownFormat in ArticlesController#new’ 今度は先程と別のエラーが出る。 viewがないためこのようなExceptionがなげられているため、次のviewテンプレートを新規作成する。

app/views/articles/new.html.erb

<h1>New Article</h1>

これでlocalhost:3000/articles/new へアクセスすると正常にページが表示される。

The first form

Railsのフォームビルダーは form_for と呼ぶヘルパーメソッドで提供される。

form_for: ActionView::Helpers::FormHelper

app/views/articles/new.html.erb へ以下を追加する。

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

生成されたhtmlを見ると、formのアクション属性が /articles/new を指している。 これを、/articles へ移動するように変更するため、form_forを以下のように書き換える。

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

ここで、rails routes コマンドでRailsのアクションを振り返ってみる。

$ rails routes
       Prefix Verb   URI Pattern                  Controller#Action
welcome_index GET    /welcome/index(.:format)     welcome#index
     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

POST /articles のアクションは create となっているため、画面のsave articleボタンを押すと ‘Unknown action’ エラーとなる。

Creating articles

‘Unknown action’ エラーを取り除くため、create アクションをコントローラへ追加する必要がある。

app/controllers/articles_controller.rb

class ArticlesController < ApplicationController
  def new
  end
 
  def create
  end
end

このままだと 204 No Content エラーが出てしまう。 フォームがsubmitされるとパラメータがRailsに送信されるので、以下のようにして表示してみよう。

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

これで再度画面の'save submit' を押すと以下が表示される。title, text はformに入力した値が反映される。

<ActionController::Parameters {"title"=>"First Article!", "text"=>"This is my first article."} permitted: false>

Creating the Article model

次のコメンドでArticleモデルを生成する。モデルには単数形が使われる。

$ rails generate model Article title:string text:text
Running via Spring preloader in process 12909
      invoke  active_record
      create    db/migrate/20170815123358_create_articles.rb
      create    app/models/article.rb
      invoke    test_unit
      create      test/models/article_test.rb
      create      test/fixtures/articles.yml

このコマンドは、RailsにArticleモデルを共にstring型のtitle, text型のtextの属性を生成する。 これらの属性は自動的にarticlesテーブルに追加され、さらにArticleモデルにマッピングされる。

Active Recordは列名をモデル属性に自動的に対応させる。つまり、Railsモデル内で属性を宣言する必要はない。これは、Active Recordによって自動的に行われる。

Running a Migration

生成された db/migrate/20170815123358_create_articles.rb

class CreateArticles < ActiveRecord::Migration[5.0]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :text
 
      t.timestamps
    end
  end
end

次のコマンドで、articles テーブルを実際にデータベースへ生成する。

$ rails db:migrate

Saving data in the controller

ArticlesControllerへ戻り、submitしたデータをデータベースへ保存するためにcreateアクションを変更する。

app/controllers/articles_controller.rb

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

実際に画面から'save article'を押すと ‘ActiveModel::ForbiddenAttributesError in ArticlesController#create’ エラーが表示される。 Railsが備えているセキュリティ機能が働いているため、コントローラーにどのパラメータが許可されているか伝える必要がある。

title と text を許可してみよう。

app/controllers/articles_controller.rb

def create
  @article = Article.new(article_params)
 
  @article.save
  redirect_to @article
end
 
private
  def article_params
    params.require(:article).permit(:title, :text)
  end

article_params は外部から意図せず呼ばれることを防ぐため、基本はprivateメソッドとして定義される。

Showing Articles

再度画面から、'Save Article'を押してみよう。すると、show アクションがないため以下のエラーが表示される。

Unknown action
The action 'show' could not be found for ArticlesController

rails routes で確認。

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

show アクションをコントローラへ追加しよう。

app/controllers/articles_controller.rb

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

続いてshowアクションのためのviewを追加する。

app/views/articles/show.html.erb

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

再度画面から'Save Article'を押してみると、以下が画面に表示される。

Title: test title

Text: test Text

Listing all articles

記事リストページを追加する。

$ rails routes
articles GET    /articles(.:format)          articles#index

app/controllers/articles_controller.rb

  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>
      <td><%= link_to 'Show', article_path(article) %></td>
    </tr>
  <% end %>
</table>

Adding links

記事のcreate, show, list が作成できたので、今度は記事へナビゲートするためのリンクを追加してみよう。

app/views/welcome/index.html.erb

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

link_to メソッドはRailsのview helperの1つ。articles へのリンクを生成する。

app/views/articles/index.html.erb の

タグの上に以下を追加してみよう。

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

上記は新しい記事作成ページへのリンクとなる。

次に indexアクションへのbackリンクを追加してみよう。

app/views/articles/new.html.erb

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

最後にshowページにもbackボタンを追加してみよう。 app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<%= link_to 'Back', articles_path %>

Adding Some Validation

app/models/article.rb ファイルを見てみよう

class Article < ApplicationRecord
end

Article クラスは ApplicationRecord を継承している。 さらにApplicationRecordクラスは、ActiveRecord::Baseを継承しており、様々な機能が使えるようになっている。

Railsはモデルに送信するデータのバリデーションを行うメソッドを備えている。以下のようにarticle.rbモデルを変更してみよう。

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

この変更により、すべてのArticleのタイトルが5文字以上になるようになります。

不正なタイトルで@article.saveを呼び出すと、falseが返されるようになるので、 @article.save の結果をチェックするコードを追加しよう。

app/controllers/articles_controller.rb

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

上記の変更で@article.save がfalseを返す場合は、 new アクションへ遷移するようになった。 ただ、これだけでは不十分なので、app/views/articles/new.html.erb を修正して、ユーザにエラーを通知しよう。

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

ArticlesControllerに@article = Article.newを追加した理由は、@articleがビューでnilとなり、@article.errors.any?でエラーが発生するため。

Updating Articles

CRUDのreadとcreateについては学んだので、次のはupdateを行う。

edit メソッドをコントローラーへ追加する。

app/controllers/articles_controller.rb

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

editテンプレートファイルを作成する。

app/views/articles/edit.html.erb

<h1>Edit article</h1>
 
<%= 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 %>
 
<%= link_to 'Back', articles_path %>

app/controllers/articles_controller.rb

def update
  @article = Article.find(params[:id])
 
  if @article.update(article_params)
    redirect_to @article
  else
    render 'edit'
  end
end

app/views/articles/index.html.erb

<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

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

Using partials to clean up duplication in views

partialファイルを作成し、コードの重複を排除する。 partialファイルには慣例として、ファイル名の先頭に ‘_’ をつける。

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

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

Deleting Articles

articleを削除するDELETEメソッドを作成する。

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

app/controllers/articles_controller.rb

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

app/views/articles/index.html.erb

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