とほほのRuby on Rails入門
Ruby on Railsとは
- Ruby言語ベースのWebアプリケーションフレームワークです。
- 「ルビー・オン・レイルズ」と読みます。
- RoR や Rails と略されることもあります。
- MITライセンスで公開されており、私用・商用を問わず無償で利用・複製・改造・再配布が可能です。
- デンマークのデイヴィット・ハイネマイヤー・ハンソンにより開発されました。
- MVC(Model/View/Controller)モデルに基づいています。
- 基本理念として「同じことを繰り返さない」(DRY: Don't Repeat Yourself)と、「設定より規約」(CoC: Convention over Configuration) というものがあります。
- Ruby on Rails の Rails も軌道(レール)に従って...の意味を持ちます。
- 2005年にv1.0がリリースされました。現時点の最新版は2021年12月15日リリースのバージョン7.0です。
インストール
下記の環境を想定しています。
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アイコンが表示されれば成功です。必要に応じて ufw や firewall-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>
ブック管理アプリを作成する
ユーザ管理アプリと同様なブック管理アプリを手作業で開発していきます。この章で作成するアプリの最終形を サンプル に掲載します。
モデルを作成する
title と author カラムを持つ 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.rb の as: で指定した名前に _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.erb に Edit ボタンを追加します。
<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.erb に Delete ボタンを追加します。下記の様に指定すると /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.erb と app/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.erb と app/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 %>
Title や Author に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
リンク
- https://rubyonrails.org/ (英語)
- https://railsguides.jp/ (日本語)