とほほのRuby on Rails入門

目次

Ruby on Railsとは

インストール

下記の環境を想定しています。

OS:Ubuntu 20.04 LTS
Ruby: 2.7
Rails: 7.0.1

コンテナで試す場合は下記の様にコンテナを起動します。

# docker run -d -it --name rails -h rails -p 3000:3000 -v `pwd`/mnt:/mnt ubuntu:20.04
# docker exec -it rails /bin/bash

必要なモジュールをインストールします。途中でタイムゾーンを聞かれたら Asia/Tokyo を選択してください。コンテナに root でログインする場合は sudo は不要です。時刻がずれているとインストールに失敗することがあります。

$ sudo apt update		# 必要に応じてアップデート
$ sudo apt -y upgrade		# 必要に応じてアップグレード
$ sudo apt -y install gcc make git
$ sudo apt -y install ruby sqlite3 nodejs yarn
$ sudo apt -y install ruby-dev libsqlite3-dev
$ gem install rails

チュートリアル

プロジェクトを作成する

rails new でプロジェクトを作成します。試しに myapp という名前のプロジェクトを作成してみます。

$ rails new myapp
$ cd myapp

テストサーバを起動する

./bin/rails server でテスト用のサーバを起動します。-b 0.0.0.0 を指定すると外部ホストからの接続を受け付けることができます。別コンソールで起動しておけば起動しなおす必要はありません。

$ bin/rails server -b 0.0.0.0 -p 3000

http://{SERVER_ADDR}:3000/ に接続して赤い日の丸のような RAILSアイコンが表示されれば成功です。必要に応じて ufwfirewall-cmd などでファイアウォールの穴あけを行ってください。

MVCモデルを理解する

Rails では MVC(Model, View, Controller) モデルで開発を行います。Model はデータベースなどでデータを管理します。View は HTML でデータを表示したり利用者からの画面入力を受け付けます。Controller は Model からデータを読み出して成形して View に引き渡したり、View からデータを受け取って成形して Model に格納したりします。

コントローラを作成する

下記を実行して index というメソッドを持つ、Home という名前のコントローラを作成します。

$ bin/rails generate controller Home index

コントローラと、コントローラが持つメソッドに対応するビューも作成されます。

$ cat app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
  end
end
$
$ cat app/views/home/index.html.erb
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>

app/views/home/index.html.erb ファイルを次のように変更してみましょう。

<h1>My Application</h1>

テストサーバを起動し、http://{SERVER_ADDR}:3000/home/index にアクセスし、My Application と表示されれば成功です。

パラメータを表示する

コントローラで設定したパラメータをビューで表示してみます。app/controllers/home_controller.rb に下記を追記します。

class HomeController < ApplicationController
  def index
    @message = "This is a test site of Ruby on Rails."
  end
end

app/views/home/index.html.erb に下記を追記します。<% ~ %> は Ruby コードを実行します。<%= ~ %> は Ruby コードの結果を表示します。

<h1>My Application</h1>
<%= @message %>

http://{SERVER_ADDR}:3000/home/index にアクセスし、上記メッセージが表示されれば成功です。

パラメータリストを表示する

リストを表示してみます。app/controllers/home_controller.rb に下記を追記します。

class HomeController < ApplicationController
  def index
    @message = "This is a test site of Ruby on Rails"
    @links = [ "users", "books", "help" ]
  end
end

app/views/home/index.html.erb に下記を追記します。

<h1>My Application</h1>
<%= @message %>
<ul>
<% @links.each do |link| %>
  <li><a href="/<%= link %>"><%= link %></a></li>
<% end %>
</ul>

http://{SERVER_ADDR}:3000/home/index にアクセスし、users 等のリンクが表示されれば成功です。

ビューを追加する

Home コントローラに help メソッドとビューを追加してみます。app/controllers/home_controller.rb に下記を追記します。

class HomeController < ApplicationController
  def index
    @message = "This is a test site of Ruby on Rails."
    @links = [ "users", "books", "help" ]
  end

  def help
  end
end

app/views/home/help.html.erb ファイルを作成します。

<h1>Help</h1>
<a href="/">Return</a>
<p>This is help page...</p>

config/routes.rb に下記のルーティングを追加します。/help の URL に GET メソッドでアクセスしたら Home コントローラの help メソッドを呼び出すという命令です。get 'home/index'get "/home/index", to: "home#index" の省略形です。

Rails.application.routes.draw do
  get 'home/index'
  get '/help', to: 'home#help'
end

help リンクをクリックしてヘルプ画面が表示されれば成功です。

ルートページを設定する

config/routes.rb に下記を追記すると、ルート(/)へのアクセスでも Home コントローラの index メソッドを呼び出すようになります。

Rails.application.routes.draw do
  root 'home#index'
  get 'home/index'
  get '/help', to: 'home#help'
end

http://{SERVER_ADDR}:3000/ にアクセスしても My Application のページが表示されれば成功です。

スタイルシートを適用する

app/assets/stylesheets/common.css ファイルを作成します。デフォルトでは、app/assets/stylesheets 配下にあるすべての .css ファイルが読み込まれます。

h1 { background-color: black; color: white; padding: .8rem; }
a { color: #339; }
input { height: 1.2rem; width: 25rem; margin-bottom: .5rem; }
textarea { height: 3rem; width: 25rem; }
button, input[type=submit] { height: 1.4rem; width: 10rem; margin-bottom: .5rem; }

ページを表示してタイトルの背景が黒くなれば成功です。

スキャフォールドを試してみる

scaffold は「足場」という意味です。開発の足場となるサンプルのようなコントローラを作成します。

$ bin/rails generate scaffold User name:string age:integer

DBも作成するので、DBのマイグレーションも行います。詳細は後述。

$ bin/rails db:migrate

users リンクをクリックすると、ユーザ管理アプリケーションに遷移し、ユーザ情報の作成、一覧/詳細、編集、削除を行うことができます。この一連の機能群を CRUD(Create, Read, Update, Delete) とも呼びます。HTTPメソッドと URL、機能の関係は次のようになります。

GET    http://{SERVER_ADDR}:3000/users		# 一覧画面(表示)
GET    http://{SERVER_ADDR}:3000/users/1	# 詳細画面(表示)
GET    http://{SERVER_ADDR}:3000/users/new	# 作成画面(入力)
POST   http://{SERVER_ADDR}:3000/users		# 作成(実行)
GET    http://{SERVER_ADDR}:3000/users/1/edit	# 編集画面(入力)
PATCH  http://{SERVER_ADDR}:3000/users/1	# 編集(実行)
PUT    http://{SERVER_ADDR}:3000/users/1	# 編集(実行) ... PATCHと同じ
DELETE http://{SERVER_ADDR}:3000/users/1	# 削除(実行)

ルート画面に戻るリンクをつけておきましょう。app/views/users/index.html.erb に下記を追記します。

...
<h1>Users</h1>
<a href="/">Return</a>

ブック管理アプリを作成する

ユーザ管理アプリと同様なブック管理アプリを手作業で開発していきます。この章で作成するアプリの最終形を サンプル に掲載します。

モデルを作成する

titleauthor カラムを持つ Book モデルを作成します。

$ bin/rails generate model Book title:string author:string

モデルを作成・変更すると、前回からの差分ファイルが db/migrate フォルダに作成されます。下記を実行すると、未適用のマイグレーションファイルがあれば、それをデータベースに反映します。

$ bin/rails db:migrate

下記の様にコンソールからモデルを参照・変更することができます。

$ bin/rails console
irb> book = Book.new(title: "吾輩は猫である", author: "夏目漱石")
irb> book.save		# 保存
irb> Book.all		# 全件表示
irb> Book.find(1)	# 検索表示
irb> Ctrl-D		# コンソールモード終了

コントローラを作成する

コントローラを作成します。

$ bin/rails generate controller Books

一覧画面を作成する

app/controllers/books_controller.rb に下記を追記します。

class BooksController < ApplicationController
  def index
    @books = Book.all
  end
end

app/views/books/index.html.erb を作成します。

<h1>Books</h1>
<a href="/">Return</a>
<ul>
  <% @books.each do |book| %>
    <li><a href="/books/<%= book.id %>"><%= book.title %></a></li>
  <% end %>
</ul>

config/routes.rb にルーティングを追加します。

Rails.application.routes.draw do
  ...
  get '/help', to: 'home#help'
  get '/books', to: 'books#index'
end

books リンクをクリックして一覧画面が表示されれば成功です。

詳細画面を作成する

app/controllers/books_controller.rb に下記を追記します。

class BooksController < ApplicationController
  def index
    @books = Book.all
  end

  def show
    @book = Book.find(params[:id])
  end
end

app/views/books/show.html.erb を作成します。

<h1>Books</h1>
<a href="/books">Return</a>
<div>Title: <%= @book.title %></div>
<div>Author: <%= @book.author %></div>

config/routes.rb にルーティングを追加します。

Rails.application.routes.draw do
  ...
  get '/books', to: 'books#index'
  get '/books/:id', to: 'books#show', as: :book
end

ブックタイトルをクリックして詳細画面が表示されれば成功です。

作成画面を作成する

app/controllers/books_controller.rb に下記を追記します。

class BooksController < ApplicationController
  ...
  def show
    @book = Book.find(params[:id])
  end

  def new
    @book = Book.new
  end

  def create
    @book = Book.new(book_params)
    if @book.save
      redirect_to @book
    else
      render :new, status: :unprocessable_entity
    end
  end

  private
    def book_params
      params.require(:book).permit(:title, :author)
    end
end

app/views/books/new.html.erb を作成します。

<h1>Books</h1>
<a href="/books">Return</a>
<%= form_with model: @book do |form| %>
  <div>
    <div><%= form.label :title %></div>
    <div><%= form.text_field :title %></div>
  </div>
  <div>
    <div><%= form.label :author %></div>
    <div><%= form.text_field :author %></div>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>

config/routes.rb にルーティングを追加します。/books/new/books/:id にマッチしないように /books/:id よりも先に書く必要があります。

Rails.application.routes.draw do
  ...
  get '/books', to: 'books#index'
  get '/books/new', to: 'books#new', as: :new_book
  post '/books', to: 'books#create'
  get '/books/:id', to: 'books#show', as: :book
end

一覧画面 app/views/books/index.html.erb に作成画面へのリンクを追記します。link_to の第一引数はリンクテキスト、第二引数には config/routes.rbas: で指定した名前に _path をつけたものを指定できます。

<h1>Books</h1>
<a href="/">Return</a>
| <%= link_to "Add", new_book_path %>
...

Add リンクをクリックしてブックを新規登録することができれば成功です。

編集画面を作成する

app/controllers/books_controller.rb に下記を追記します。

class BooksController < ApplicationController
  ...
  def create
    ...
  end

  def edit
    @book = Book.find(params[:id])
  end

  def update
    @book = Book.find(params[:id])
    if @book.update(book_params)
      redirect_to @book
    else
      render :new, status: :unprocessable_entity
    end
  end

  private
    ...
end

app/views/books/edit.html.erb を作成します。

<h1>Books</h1>
<a href="/books">Return</a>
<%= form_with model: @book do |form| %>
  <div>
    <div><%= form.label :title %></div>
    <div><%= form.text_field :title %></div>
  </div>
  <div>
    <div><%= form.label :author %></div>
    <div><%= form.text_field :author %></div>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>

config/routes.rb にルーティングを追加します。

Rails.application.routes.draw do
  ...
  get '/books/:id', to: 'books#show', as: :book
  get 'books/:id/edit', to: 'books#edit', as: :edit_book
  patch '/books/:id', to: 'books#update'
end

詳細画面 app/views/books/show.html.erbEdit ボタンを追加します。

<h1>Books</h1>
<a href="/books">Return</a>
<div>Title: <%= @book.title %></div>
<div>Author: <%= @book.author %></div>
<%= button_to "Edit", edit_book_path, method: :get %>

Edit ボタンからブックを編集することができれば成功です。

削除画面を作成する

app/controllers/books_controller.rb に下記を追記します。

class BooksController < ApplicationController
  ...
  def update
    ...
  end

  def destroy
    @book = Book.find(params[:id])
    @book.destroy
    redirect_to books_path
  end

  private
    ...

config/routes.rb にルーティングを追加します。

Rails.application.routes.draw do
  ...
  patch '/books/:id', to: 'books#update'
  delete '/books/:id', to: 'books#destroy'
end

詳細画面 app/views/books/show.html.erbDelete ボタンを追加します。下記の様に指定すると /books/:id パスに対して DELETE メソッドが発行されます。

<h1>Books</h1>
...
<%= button_to "Edit", edit_book_path, method: :get %>
<%= button_to "Delete", @book, method: :delete %>

Delete ボタンでブックを削除できれば成功です。

リファクタリング

絶対パスから相対パスへの修正

app/views/home/help.html.erb, app/views/users/index.html.erb, app/views/books/index.html.erb の中のルート画面へのパスを相対パスに修正します。

修正前:<a href="/">Return</a>
修正後:<%= link_to "Return", root_path %>

app/views/books/new.html.erb, app/views/books/edit.html.erb, app/views/books/show.html.erb の中にあるパスも相対パスに修正します。

修正前:<a href="/books">Return</a>
修正後:<%= link_to "Return", books_path %>

app/views/books/index.html.erb の詳細画面へのリンクも相対パスに修正します。book がブックオブジェクトを示している場合、book オブジェクト自体を指定すると、そのオブジェクトの book_path に遷移します。

修正前:<li><a href="/books/<%= book.id %>"><%= book.title %></a></li>
修正後:<li><%= link_to book.title, book %></li>

トップページのメニューを、ラベルを指定可能に、パスを絶対パスから _path を用いた相対パスに書き換えます。app/controllers/home_controller.rb を次のように修正します。

class HomeController < ApplicationController
  def index
    @message = "This is a test site of Ruby on Rails."
    @menus = [
      { :label => "User", :path => users_path },
      { :label => "Book", :path => books_path },
      { :label => "Help", :path => help_path }
    ]
  end
  ...

app/views/home/index.html.erb を次のように修正します。

<h1>My Application</h1>
<%= @message %>
<ul>
<% @menus.each do |menu| %>
  <li><%= link_to menu[:label], menu[:path] %></li>
<% end %>
</ul>

ルート情報の一括指定

config/routes.rb で book に関するルーティングを削除し、代わりに resources を使用すると、Books コントローラに対する /books/new/books/:id/edit などのルーティング設定をまとめて一括指定することができます。

Rails.application.routes.draw do
  resources :users
  root 'home#index'
  get 'home/index'
  get '/help', to: 'home#help'
  resources :books
end

下記を実行すると定義されているルーティング情報の一覧が表示されます。Prefix に _path を加えたものが link_to などで使用できます。

$ bin/rails routes
   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

ビューの共通部をパーシャル化する

app/views/books/new.html.erbapp/views/books/edit.html.erb の内容はほぼ同じですので、共通部をパーシャルとして切り出します。まず、app/views/books/_form.html.erb ファイルを作成します。パーシャルのファイル名はアンダーバー(_)で始める必要があります。@book が book に変わることに注意してください。

<%= form_with model: book do |form| %>
  <div>
    <div><%= form.label :title %></div>
    <div><%= form.text_field :title %></div>
  </div>
  <div>
    <div><%= form.label :author %></div>
    <div><%= form.text_field :author %></div>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>

app/views/books/new.html.erbapp/views/books/edit.html.erb を次のように修正します。

<h1>Books</h1>
<%= link_to "Return", books_path %>
<%= render "form", book: @book %>

機能追加

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

作成や編集フォームにバリデーションを追加するには次のようにします。app/models/book.rb に下記を追記します。

class Book < ApplicationRecord
  validates :title, presence: true, length: { maximum: 20 }
  validates :author, presence: true, length: { maximum: 20 }
end

app/views/books/_form.html.erb に下記を追記します。

<%= form_with model: book do |form| %>
  <div>
    <div><%= form.label :title %></div>
    <div><%= form.text_field :title %></div>
    <% @book.errors.full_messages_for(:title).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>
  <div>
    <div><%= form.label :author %></div>
    <div><%= form.text_field :author %></div>
    <% @book.errors.full_messages_for(:author).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>

TitleAuthor に20文字以上の文字を入力して登録・変更しようとするとエラーメッセージが表示されれば成功です。

コメント機能を追加する

ブックに対してコメントを追記できるようにします。親子関係をもつテーブルを扱う練習でもあります。下記の様にコントローラと、Bookに関係づいたモデルを追加してマイグレーションします。

$ bin/rails generate controller Comments
$ bin/rails generate model Comment commenter:string body:text book:references
$ bin/rails db:migrate

app/models/book.rb に下記を追記します。

class Book < ApplicationRecord
  has_many :comments
  validates :title, presence: true, length: { maximum: 20 }
  validates :author, presence: true, length: { maximum: 20 }
end

config/routes.rb に下記を追記します。/books/:book_id/comments/:id などでのルーティングが追加されます。

Rails.application.routes.draw do
  ...
  get '/help', to: 'home#help'
  resources :books do
    resources :comments
  end
end

app/controllers/comments_controller.rb に下記を追記します。

class CommentsController < ApplicationController
  def create
    @book = Book.find(params[:book_id])
    @comment = @book.comments.create(comment_params)
    redirect_to book_path(@book)
  end

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

app/views/books/show.html.erb に下記を追記します。上半分がコメント表示欄、下半分がコメント追記欄です。

<h1>Books</h1>
<%= link_to "Return", books_path %>
<div>Title: <%= @book.title %></div>
<div>Author: <%= @book.author %></div>
<%= button_to "Edit", edit_book_path, method: :get %>
<%= button_to "Delete", @book, method: :delete %>
<h2>Comments</h2>
<% @book.comments.each do |comment| %>
  <div>
    <strong><%= comment.commenter %></strong>
    <%= comment.body %>
  </div>
<% end %>
<%= form_with model: [ @book, @book.comments.build ] do |form| %>
  <div>
    <div><%= form.label :commenter %></div>
    <div><%= form.text_field :commenter %></div>
  </div>
  <div>
    <div><%= form.label :body %></div>
    <div><%= form.text_area :body %></div>
  </div>
  <div><%= form.submit %></div>
<% end %>

ブックの詳細画面からコメントを追加できるようになったら成功です。

その他

ページ全体のレイアウトを変更する

必要に応じて app/views/layouts/application.html.erb を編集します。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Myapp</title>
    ...

キャッシュをクリアする

修正したはずなのに修正がうまく反映されないなどの場合、キャッシュ情報が残っている場合があります。サーバーを再起動してみたり、下記のコマンドでキャッシュ情報をクリアしてみてください。

$ bin/rails tmp:clear

単数形・複数形を変換する

Rails では単語の単数形と複数形を使用します。スキャフォールド で指定する名前は単数形ですが、自動生成されるコントローラ名は複数形、モデルは単数形、ビューのディレクトリ名は複数形になります。これらは Rails が単数形と複数形の変換アルゴリズムと辞書を持っていて自動変換しています。複数形化メソッド pluralize と単数形化メソッド singularize もサポートしています。

puts "person".pluralize		# => "people"
puts "people".singularize	# => "person"

単数形・複数形が期待通りに変換されない場合は config/initializers/inflections.rb に単数形・複数形の単語を追加してください。

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.irregular "love", "loves"
end