##はじめに
Ruby on Railsを最近勉強し始めたので、学んだことを自分なりにまとめておこうと思う。
この記事は基本的に、「Ruby on Rails チュートリアル(第6版)」を参考にさせていただき、図や文章などもこのページから引用させていただく。
文中に知らないキーワードがある場合は、その他のサイトから引用する。
##前提知識
このチュートリアルを学ぶにあたって、以下の知識をProgateやUdemyなどで学習した。
- Rubyの基礎
- HTML/CSSの基礎
- JavaScriptの基礎
- Linuxの基礎(コマンドライン)
- Git/GitHubの基礎
- SQLの基礎
##アプリケーションの計画
まず、rails new
コマンドでアプリケーションの骨組みを生成する。
$ cd ~/environment
$ rails _6.0.3_ new toy_app
$ cd toy_app/
次に、Bundlerで扱うGemfile
を下記のように編集する。
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '6.0.3'
gem 'puma', '4.3.6'
gem 'sass-rails', '5.1.0'
gem 'webpacker', '4.0.7'
gem 'turbolinks', '5.2.0'
gem 'jbuilder', '2.9.1'
gem 'bootsnap', '1.4.5', require: false
group :development, :test do
gem 'sqlite3', '1.4.1'
gem 'byebug', '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
gem 'web-console', '4.0.1'
gem 'listen', '3.1.5'
gem 'spring', '2.1.0'
gem 'spring-watcher-listen', '2.0.1'
end
group :test do
gem 'capybara', '3.28.0'
gem 'selenium-webdriver', '3.142.4'
gem 'webdrivers', '4.1.2'
end
group :production do
gem 'pg', '1.1.4'
end
# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
--without production
オプションを追加することで、本番用のgemを除いたローカルgemをインストールする。
エラーメッセージが出た場合は、bundle update
を実行する。
$ bundle update
$ bundle install --without production
Gitでこのtoy_app
をバージョン管理下におく。
$ git init
$ git add -A
$ git commit -m "Initialize repository"
GitHubで新しいリポジトリを作成し、生成したファイルをリモートリポジトリにプッシュする。
$ git remote add origin https://github.com/<あなたのGitHubアカウント名>/toy_app.git
$ git push -u origin master
最後に、デプロイの準備をする。
Applicationコントローラ人hello
アクションを追加し、ルートルーティングを設定する。
class ApplicationController < ActionController::Base
def hello
render html: "hello, world!"
end
end
Rails.application.routes.draw do
root 'application#hello'
end
これらの変更をコミットし、Herokuにプッシュする。
このとき$$
(アンパサンド演算子)を2つ用いて、GitHubとHerokuへのプッシュを連結する。
この場合、2番目のコマンドは1番目のコマンド実行に成功した場合に限って実行される。
実行時に警告メッセージが表示されることもあるが、今は無視する。
$ git commit -am "Add hello"
$ heroku create
$ git push && git push heroku master
これで、アプリケーション自体を作成するための下準備が整った、
Webアプリケーションを作る際、アプリケーションの構造を表すための データモデル を最初に作成しておく。
今回は、ユーザーで使うモデルとマイクロポスト(Twitterでいうツイート)で使うモデルを作成する。
##ユーザモデルの設計
ユーザモデルを下図のように構成する。
users
はデータベースのテーブル(table)
id
,name
,email
の属性はそれぞれテーブルのカラム(column)に相当する。
##マイクロポストモデルの設計
マイクロポストとは、その名の通り比較的短い投稿である。
マイクロポストモデルを下図のように構成する。
マイクロポストとユーザーを関連づける(associate)ために、user_id
属性も追加する。
##Userリソース
ユーザー用のデータモデルを、そのモデルを表示するためのWebインターフェイスに従って実装する。
データモデルとWebインターフェイスは、組み合わさってUsersリソースとなり、ユーザーというものを、HTTPプロトコル経由で自由に作成/取得/更新/削除できるオブジェクトとみなすことができるようになる。
この章のみ、scaffoldジェネレータを用いて生成する。
Railsのscaffoldは、rails generate
スクリプトにscaffold
コマンドを渡すことで生成されます。scaffold
コマンドの引数には、リソース名を単数形にしたもの(この場合はUser
)を使い、必要に応じてデータモデルの属性をオプションとしてパラメータに追加する。
name:string
とemail:string
オプションを追加することで、Userモデルの内容が設計通りになるようにする。(id
はRailsによって自動的に 主キー としてデータベースに追加されるため不要)
$ rails generate scaffold User name:string email:string
次に、rails db:migrate
を実行してデータベースをマイグレート(migrate)する必要がある。
下記のコマンドは、単にデータベースを更新し、users
データモデルを作成するためのもの。
$ rails db:migrate
マイグレーションを実行したことで、ローカルWebサーバを別タブで実行できるようになった。
下記のように変更を加え、Cloud9への接続を許可する。
Rails.application.configure do
.
.
.
# Cloud9 への接続を許可する
config.hosts.clear
end
次に、Railsサーバを実行する。
これで、ローカルサーバが動作するはず。
$ rails server
###ユーザページの探検
Usersリソースをscaffoldで生成したことで(2.2)、ユーザー管理用のページが多数追加される。
下記の表のように対応するURLを表示すれば関連するページを表示できる。
URL | アクション | 用途 |
---|---|---|
/users | index |
すべてのユーザーを一覧するページ |
/users/1 | show |
id=1のユーザーを表示するページ |
/users/new | new |
新規ユーザーを作成するページ |
/users/1/edit | edit |
id=1のユーザーを編集するページ |
まずはユーザーの一覧を表示するindex
アクションのURL(/users)を見てみる。
新規ユーザを登録するために、new
ページを表示する。
NameとEmailを入力して[Create User]ボタンを押すと、show
ページが表示される。
この時、URLが /users/1 となっているのは、このユーザのid
が1だから。
元のページに戻ってnew
ページに再度行けば、ユーザを追加できる。
また、ユーザの削除も[Destroy]リンクをクリックすれば実行できる。
###MVCの挙動
MVCパターンの観点から考察する。
例として、「/users にあるindexページをブラウザで開く」という操作をした時、内部で何が起こっているのかをMVCで見てみる。
- ブラウザから「/users」というURLのリクエストをRailsサーバーに送信する。
- 「/users」リクエストは、Railsのルーティング機構(ルーター)によってUsersコントローラ内の
index
アクションに割り当てられる。 -
index
アクションが実行され、そこからUserモデルに、「すべてのユーザーを取り出せ」(User.all
)と問い合わせる。 - Userモデルは問い合わせを受け、すべてのユーザーをデータベースから取り出す。
- データベースから取り出したユーザーの一覧をUserモデルからコントローラに返す。
- Usersコントローラは、ユーザーの一覧を
@users
変数(@はRubyのインスタンス変数を表す)に保存し、index
ビューに渡す。 - indexビューが起動し、ERB(Embedded RuBy: ビューのHTMLに埋め込まれているRubyコード)を実行して HTMLを生成(レンダリング)する。
- コントローラは、ビューで生成されたHTMLを受け取り、ブラウザに返す。
このリクエストは、アドレスバーにURLを入力したりリンクをクリックした時に発生する(①)。
リクエストはRailsルーティングに到達し(②)、ここでURLに基づいて適切なコントローラのアクションに割り当てられる(ディスパッチ)。
ユーザーからリクエストされたURLを、Usersリソースで使うコントローラのアクションに割り当てるためのコードは、下記のようになる。
このようなマッピングするコードはRailsのルーティング設定ファイル(config/routes.rb)に書く。
Rails.application.routes.draw do
resources :users # シンボル
root 'application#hello'
end
ルーティングファイルを変更して、サーバのルートURLにアクセスしたら、デフォルトのページの代わりにユーザ一覧を表示するようにする。
つまり、「/」にアクセスしたら /users を開くようにする。
よって、今回のような場合は、Appplicationコントローラ内のhello
アクションではなく、Usersコントローラ内のindex
アクションを使えるようにする。
Rails.application.routes.draw do
resources :users
root 'users#index'
end
各ページは、Usersコントローラ内のアクションにそれぞれ対応している。
下記は、scaffoldで生成したコントローラの骨格である。
class UsersController < ApplicationController
という記法では、ApplicationController
という親クラスから、UsersController
という子クラスへのクラス継承の文法を使っている。
class UsersController < ApplicationController
.
.
.
def index
...
end
def show
...
end
def new
...
end
def edit
...
end
def create
...
end
def update
...
end
def destroy
...
end
end
ページを表示するindex
,show
,new
,edit
以外にも、create
,update
,destroy
アクションがある。
下の表は、Railsにおける REST アーキテクチャを構成するすべてのアクションの一覧である。
RESTは、コンピュータ科学者Roy Fieldingによって提唱された「REpresentational State Transfer」という概念に基づいている。
HTTPリクエスト | URL | アクション | 用途 |
---|---|---|---|
GET |
/users | index |
すべてのユーザーを一覧するページ |
GET |
/users/1 | show |
id=1のユーザーを表示するページ |
GET |
/users/new | new |
新規ユーザーを作成するページ |
POST |
/users | create |
ユーザーを作成するアクション |
GET |
/users/1/edit | edit |
id=1のユーザーを編集するページ |
PATCH |
/users/1 | update |
id=1のユーザーを更新するアクション |
DELETE |
/users/1 | destroy |
id=1のユーザーを削除するアクション |
show
アクションと update
アクションは、どちらも /users/1 というURLに対応している。
これらのアクション同士の違いは、それらのアクションに対応するHTTP requestメソッドの違いでもある。
ここで、index
アクションについて整理する。
class UsersController < ApplicationController
.
.
.
def index
@users = User.all
end
.
.
.
end
index
アクションの@users = User.all
という行が、Userモデルからすべてのユーザーの一覧を取り出し(④)、@users
という変数に保存する(⑤)。
Userモデルの内容は下記にある。
継承によって多くの昨日が備わっており、特に、Active RecordというRubyライブラリのおかげで、User.all
というリクエストに対してDB上の全てのユーザを返すことができる。
class User < ApplicationRecord
end
@users
変数にユーザー一覧が保存されると、コントローラは下記のビューを呼び出す(⑥)。
@
記号で始まる変数をRubyではインスタンス変数と呼び、Railsのコントローラ内で宣言したインスタンス変数はビューでも使えるようになる。
この場合、下記のindex.html.erb
ビューは@users
の一覧を並べ、1行ごとにHTMLの行として出力する。
<p id="notice"><%= notice %></p>
<h1>Users</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New User', new_user_path %>
ビューはその内容をHTMLに変換し(⑦)、コントローラがブラウザにHTMLを送信して、ブラウザでHTMLが表示される(⑧).
###Userリソースの欠点
scaffoldで作成したUserリソースは以下のような問題点を抱えている。
-
データの検証が行われていない
空欄とか、メアドが変でも通ってしまう。 -
ユーザー認証が行われていない
ログイン・ログアウトの概念がないので、誰でも操作可能。 -
テストが書かれていない
scaffoldのテストコードは簡単なテストは含まれているが、データ検証やユーザ認証などの要求を満たしてない。 -
レイアウトやスタイルが整っていない
ダサいし、使いにくい。 -
理解が困難
まず、scaffoldの中身がわっっっかんない。
##Micropostsリソース
Userリソースと同様なことを行っていく
###マイクロポストを探検する
Userリソースと同様にMicropostsリソースもscaffoldでコードを生成し、新しいデータモデルでデータベースを更新するために、マイグレーションを行う。
$ rails generate scaffold Micropost content:text user_id:integer
$ rails db:migrate
これで、Micropostsを作成する準備ができた。
また、下記のようにRailsのrouteファイルが更新され、Micropostsリソース用のresources :microposts
というルーティングルールが追加された。
Rails.application.routes.draw do
resources :microposts
resources :users
root 'users#index'
end
HTTPリクエスト | URL | アクション | 用途 |
---|---|---|---|
GET |
/microposts | index |
すべてのマイクロポストを表示するページ |
GET |
/microposts/1 | show |
id=1 のマイクロポストを表示するページ |
GET |
/microposts/new | new |
マイクロポストを新規作成するページ |
POST |
/microposts | create |
マイクロポストを新規作成するアクション |
GET |
/microposts/1/edit | edit |
id=1 のマイクロポストを編集するページ |
PATCH |
/microposts/1 | update |
id=1 のマイクロポストを更新するアクション |
DELETE |
/microposts/1 | destroy |
id=1 のマイクロポストを削除する |
Micropostsコントローラの構造はapp/controllers/microposts_controller.rb
に書かれており、UsersController
がMicropostsController
に置き換わっている以外は、完全に同一。
これは、RESTアーキテクチャが2つのリソースに同じように反映されているから。
###マイクロにする
マイクロになるように文字数制限をかける。
Railsでは、バリデーション(validation)を使って簡単に実現できる。
下記のコードで、140文字に制限する。
class Micropost < ApplicationRecord
validates :content, length: { maximum: 140 }
end
###異なるデータモデルの関連付
一人のユーザに対し、複数のマイクロポストがあるとする。
UserモデルとMicropostモデルを以下のように更新することで、関連付を表現できる。
class User < ApplicationRecord
has_many :microposts
end
class Micropost < ApplicationRecord
belongs_to :user
validates :content, length: { maximum: 140 }
end
microposts
テーブルにはuser_id
カラムを作成してあったので、それによってRailsとActive Recordがマイクロポストとユーザーを関連付けることができるようになっている。
rails console
コマンドを用いて、ユーザとマイクロポストの関連付けを確認する。
下記のように、操作していく。
$ rails console
>> first_user = User.first
(0.5ms) SELECT sqlite_version(*)
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC
LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.org",
created_at: "2019-08-20 00:39:14", updated_at: "2019-08-20 00:41:24">
>> first_user.microposts
Micropost Load (3.2ms) SELECT "microposts".* FROM "microposts" WHERE
"microposts"."user_id" = ? LIMIT ? [["user_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Micropost id: 1, content:
"First micropost!", user_id: 1, created_at: "2019-08-20 02:04:13", updated_at:
"2019-08-20 02:04:13">, #<Micropost id: 2, content: "Second micropost",
user_id: 1, created_at: "2019-08-20 02:04:30", updated_at: "2019-08-20
02:04:30">]>
>> micropost = first_user.microposts.first
Micropost Load (0.2ms) SELECT "microposts".* FROM "microposts" WHERE
"microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?
[["user_id", 1], ["LIMIT", 1]]
=> #<Micropost id: 1, content: "First micropost!", user_id: 1, created_at:
"2019-08-20 02:04:13", updated_at: "2019-08-20 02:04:13">
>> micropost.user
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.org",
created_at: "2019-08-20 00:39:14", updated_at: "2019-08-20 00:41:24"
>> exit
###継承の階層
UserモデルとMicropostモデルはいずれも、ApplicationRecord
というクラスを継承している。
また、ApplicationRecord
クラスは、Active Recordが提供する基本クラスActiveRecord::Base
を継承している。
このActiveRecord::Base
という基本クラスを継承したことによって、作成したモデルオブジェクトはデータベースにアクセスできるようになり、データベースのカラムをあたかもRubyの属性のように扱えるようになる。
UsersコントローラとMicropostsコントローラはいずれもApplicationController
を継承している。
ApplicationController
がActionController::Base
というクラスを継承しており、このクラスは、RailsのAction Packというライブラリが提供しているコントローラの基本クラス。
ActionController::Base
という基本クラスを継承しているため、モデルオブジェクトの操作や、送られてくるHTTP requestのフィルタリング、ビューをHTMLとして出力などの多彩な機能を実行できるようになっている。
Railsのコントローラは必ずApplicationController
を継承しているので、Applicationコントローラで定義したルールは、アプリケーションのすべてのアクションに反映される。
###デプロイ
GitHubにpush
$ git status #追加の前にこうやって状態を確認するのはよい習慣です
$ git add -A
$ git commit -m "Finish toy app"
$ git push
Herokuに展開してもいい。
$ heroku create
$ git push heroku master
##本章のまとめ
-
Scaffold機能でコードを自動生成すると、Webのあらゆる部分からモデルデータにアクセスしてやりとりできるようになる
-
Scaffoldは何よりも手っ取り早いのがとりえだが、これを元にRailsを理解するには向いていない
-
RailsではWebアプリケーションの構成にMVC(Model-View-Controller)というモデルを採用している
-
Railsが解釈するRESTには、標準的なURLセットと、データモデルとやりとりするためのコントローラアクションが含まれている
-
Railsではデータのバリデーション(validation)がサポートされており、データモデルの属性の値に制限をかけることができる
-
Railsには、さまざまなデータモデル同士を関連付けを定義するための組み込み関数が多数用意されている
-
Railsコンソールを使うと、コマンドラインからRailsアプリケーションとやりとりすることができる
##個人的にわからなくて調べたキーワード
- マイグレート
SQLを書くことなくRubyでデータベース内にテーブルを作成することができる機能
-
レンダリング
Webページにおいて、HTML/CSSやスクリプトによる描画内容・動作の記述や、画像ファイルなど外部データなどを組み合わせ、ブラウザのウィンドウ内にページ内容を描画すること。 -
REST
RailsアプリケーションにおけるRESTとは、アプリケーションを構成するコンポーネント(ユーザーやマイクロポストなど)を「リソース」としてモデル化することを指します。
これらのリソースは、リレーショナルデータベースの作成/取得/更新/削除(Create/Read/Update/Delete: CRUD)操作と、4つの基本的なHTTP requestメソッド(POST/GET/PATCH/DELETE)の両方に対応しています。
##おわりに
この章の内容は図がないとわかりづらいと思ったので、かなり引用させていただいた。
これらの図のおかげで非常に理解がしやすい内容となっていた。