##概要
この記事は私の知識をより確実なものにするためにRailsチュートリアル解説記事を書くことで理解を深め
勉強の一環としています。稀にとんでもない内容や間違えた内容が書いてあるかもしれませんので
ご了承ください。
できればそれとなく教えてくれますと幸いです・・・
##この章でやること
toy_appの作成を通してscaffoldの強力な機能を知る
RESTアーキテクチャとは何か大まかに把握する。
##Toy_appの生成
①hello_appと同じようにRailsのバージョンを指定して生成する
$ cd ~/environment
$ rails _6.0.3_ new toy_app
$ cd toy_app/
②Gemfileも新しくなったのでToyアプリ用に書き換える
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '6.0.3'
gem 'puma', '4.3.4'
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]
③そして以前と同じく
bundle install
を実行、
You have requested:
listen = 3.1.5
The bundle currently has listen locked at 3.2.1.
Try running `bundle update listen`
If you are updating multiple gems in your Gemfile at once,
try passing them all to `bundle update`
このようなエラーが出たらGemfile.lockで引っかかっているのでGemfile.lockを一度削除するか
bundle update
を実行してから
bundle install
する。
④最後にhello_appと同じくGitのバージョン管理下に置く。
$ git init
$ git add -A
$ git commit -m "Initialize repository"
ローカルのリポジトリはできたので、リモートのリポジトリをToyアプリ用に新規作成して
プッシュする。(リモートリポジトリ作成は第1章と同じ方法でGithubのサイト上で行う)
リポジトリは依然と同じくプライベートで。
$ git remote add origin https://github.com/<あなたのGitHubアカウント名>/toy_app.git
$ git push -u origin master
⑤とりあえずデプロイもしてみる。
今の状態だと中身がないのでとりあえずhello_appと同じように
hello,worldを出力するように追記しておく
class ApplicationController < ActionController::Base
def hello
render html: "hello, world!"
end
end
Rails.application.routes.draw do
root 'application#hello'
end
つづいてこの変更をコミット&Github/Herokuにプッシュ
$ git commit -am "Add hello"
$ heroku create
$ git push && git push heroku master
###Userモデルを作成する
######ユーザーのモデル設計
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#sec-modeling_demo_users
id,name,emailの属性がそれぞれテーブルのカラムに相当する。
######マイクロポストのモデル設計
マイクロポストはTwitterのツイートと同じような140文字程度の短い投稿が前提となる。
投稿のid、投稿のテキスト内容を格納するtext型のcontent、誰の投稿かを記録するためのuser_idも追加すると
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#sec-modeling_demo_microposts
このようなデータモデルになる。
######Usersリソース
さっそくUsersリソースからRailsをscaffoldジェネレータで生成する。
$ rails generate scaffold User name:string email:string
書き方はrails generate scaffold リソース名 カラム名:データ型 ~ となる。
id属性は必ず1つのモデルに1つ主キーとして付加されるので書く必要はない
scaffoldでUserモデルを作成したがこのままでは使えない。
ジェネレータで作成したデータベースはマイグレート(migrate)する必要がある。
データベースをマイグレートする↓
$ rails db:migrate
これでローカルWebサーバーを実行できるようになったので、Cloud9を使っている場合は
第1章と同じくdevelopment.rbを編集してCloud9への接続を許可しておく。
Rails.application.configure do
.
.
.
config.hosts.clear
end
あとは別タブでターミナルを起動し、rails server
を実行。
これでローカルサーバーが立ち上がり、hello,world!が表示される。
###ユーザーページを探検する
先ほどの一連の流れでUsersリソースをscaffoldで生成したことで自動でユーザー管理用のページが多数追加されている。
ページのURLと内容は以下の通り。
URL | アクション | 用途 |
---|---|---|
/users | index | すべてのユーザーを一覧するページ |
/users/1 | show | id=1のユーザーを表示するページ |
/users/new | new | 新規ユーザーを作成するページ |
/users/1/edit | edit | id=1のユーザーを編集するページ |
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#sec-a_user_tour
ユーザーページの探検はRailsチュートリアルを参照。
###演習
- F12キーなどでブラウザのデベロッパーツールを起動し、該当の箇所を調べると、リロード時に内容が削除されている。
- emailを入力しないで登録しても登録できてしまう。これはまずいので3章から作るアプリではこのような問題の対策もしていく。
- 間違えたメールアドレスを入力されても登録できてしまう。2と同じでこれも対策することになる。
- Are you sure?と警告メッセージが表示されるようになっている。こういったユーザーの使いやすいように工夫された機能も実装していく。
###MVCの挙動
/usersのindexページを開くと内部でどういった処理をしているのか、MVCで図解するとこのようになる。
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#sec-mvc_in_action
ブラウザからのリクエスト→ルーターが該当するアクションに割り当て→必要なデータをUserモデルに問い合わせる
→データベースからモデルがデータを取り出す→モデルがデータをコントローラに渡す
→インスタンス変数に受け取ったデータを代入してビューに渡す→ビューはHTMLを生成し、コントローラに渡す
→コントローラがブラウザに受け取ったHTMLを返す
という流れ。
Toyアプリのとりあえずの形ができたのでルートURLをToyアプリのインデックスページに変更してみる。
Rails.application.routes.draw do
resources :users
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
root "users#index"
end
今回も書き方は同じで "コントローラ名#アクション名"で記述する
ここでscaffoldで生成されたusers_controllerをのぞいてみる
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
# GET /users.json
def index
@users = User.all
end
# GET /users/1
# GET /users/1.json
def show
end
# GET /users/new
def new
@user = User.new
end
# GET /users/1/edit
def edit
end
このような記述がされている。
1行目のUsersController < ApplicationControllerという記述は
Rubyの文法で継承を意味する。ここでは
ApplicationControllerを継承したUserControllerクラスという意味になる。
以下の表はRESTに対応するアクションの一覧で、
URLが一部重複するものがあるがこれらはHTTPリクエストの種類が別なため別なルートとカウントされる。
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のユーザーを削除するアクション |
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#table-demo_RESTful_users
users_controllerのindexアクションをみてみると
def index
@users = User.all
end
このような記述になっている。
これは@users変数にUserデータベースのすべてのデータを取り出して代入する処理を行っている。
Userモデル自体は何も記述がなく非常にシンプルだが
Userクラスが継承している"ActiveRecordクラス"がDB操作に関する非常に多くの機能を持っているため
今回の場合はUser.allでデータベースからUserのデータを取り出すことができた。
この@users変数はindexアクションに対応するindexビューで使われている。
該当の場所をみてみる。
<% @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 %>
この記述の<% @users.each do |user| %>
の部分だが
この段階で簡潔に説明すると、@users変数に入っているデータ(レコード)を一つずつ引っ張り出して
userに代入し、endで囲まれた部分をそのレコードの数分繰り返す処理。
ユーザーが3人(A、B、C)といたら
Aのデータをuserに入れてhtmlを生成
Bのデータをuserに入れてhtmlを生成
Cのデータをuserに入れてhtmlを生成
という繰り返し処理を行っている。
現時点では理解しなくてもOK
###演習
1.
①ブラウザからリクエストがルーターに送られる
②ルーターがリクエストに対応するコントローラのeditアクションに割り当てる。
③editアクションが必要とするデータをモデルにリクエストする。
④モデルがリクエストされたデータをデータベースから読み出し、コントローラに返す
⑤editアクションが対応するビューedit.html.erbを呼ぶ(この時ビューには読み出したデータを渡す。)
⑥ビューがHTMLを生成し、コントローラに返す。
⑦コントローラがブラウザに渡されたHTMLを返す。
users_controllerのprivateメソッドに記述されている。
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
private
# Use callbacks to share common setup or constraints between actions.
def set_user
@user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:name, :email)
end
end
before_actionという記述でeditアクションを実行する前にset_userメソッドを実行するよう指定されている。
ser_userアクションでは@user変数にUserデータベースのリクエストされたIDのテーブルを読み出すという文が書いてある。
3.edit.html.erb
######Usersリソースの欠点について
前回の演習でも挙げたがユーザー名やメールアドレスがめちゃくちゃでも登録できてしまう。
またログイン機構がないので誰でもユーザーを削除したり編集したりできてしまう。権限もくそもない。
そしてアプリの動作を確認するテストがほぼない。上記の内容が正しいか。アプリの機能が正しく動作するかなどの
テストが全くかかれていない。→不都合があっても見つからない
UIがダサい、レイアウトもめちゃくちゃこんなレイアウトのWebアプリはだれも使わない(断言)
理解が難しい。これに尽きる
Railsチュートリアルを1週読破した自分でさえも完全にコードを理解するのは難しい。
コードは基本簡潔にわかりやすく書かないとNG
###マイクロポストを作成する
MicropostsもUsersと同様scaffoldで生成してみる。
$ rails g scaffold Micropost content:text user_id:integer
Running via Spring preloader in process 3794
invoke active_record
create db/migrate/20200601141608_create_microposts.rb
create app/models/micropost.rb
invoke test_unit
create test/models/micropost_test.rb
create test/fixtures/microposts.yml
invoke resource_route
route resources :microposts
invoke scaffold_controller
create app/controllers/microposts_controller.rb
invoke erb
create app/views/microposts
create app/views/microposts/index.html.erb
create app/views/microposts/edit.html.erb
create app/views/microposts/show.html.erb
create app/views/microposts/new.html.erb
create app/views/microposts/_form.html.erb
invoke test_unit
create test/controllers/microposts_controller_test.rb
create test/system/microposts_test.rb
invoke helper
create app/helpers/microposts_helper.rb
invoke test_unit
invoke jbuilder
create app/views/microposts/index.json.jbuilder
create app/views/microposts/show.json.jbuilder
create app/views/microposts/_micropost.json.jbuilder
invoke assets
invoke scss
create app/assets/stylesheets/microposts.scss
invoke scss
identical app/assets/stylesheets/scaffolds.scss
Usersの生成時と同じようにまずはデータベースをマイグレートして使えるようにする。
$ rails db:migrate
== 20200601141608 CreateMicroposts: migrating =================================
-- create_table(:microposts)
-> 0.0041s
== 20200601141608 CreateMicroposts: migrated (0.0048s) ========================
MicropostsもUsersの時と変わらず、ルーターが更新され、resourcesでRESTアーキテクチャに対応した
各ルーティングが割り当てられる。
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 | id1のマイクロポストを削除する |
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#code-demo_microposts_resource
scaffoldで生成されるコードはクラス名が自分でつけた名前に代わるだけで他は変わらない。
つまり同じ構造である。
MicropostsもUsersと同じように/micropostsでインデックスページにアクセスでき、New micropostで投稿ができる。
###演習
- Usersの時と同じく、中身が消える。
- これもUsersの時と同じで、現状対策されていないため空のユーザーで空の投稿ができてしまう。
- 文字数を制限するコードが生成されていないため、投稿できてしまう。(これも欠点の一つ)
- indexページからdestroyリンクを踏むことで削除できる。これも確認メッセージが表示されてから削除される。
######マイクロポストをマイクロなポストに
前回の演習の3番目の項目で文字数制限がないため、長い文章でも投稿できてしまいマイクロでない問題が発覚した。
この問題を修正していく。
データベースの内容に制限(バリデーション)を加えることでこの問題を修正する。
具体的なコードは下のとおり
class Micropost < ApplicationRecord
validates :content, length:{maximum: 140}
end
データベースを扱うモデルにバリデーションを書くことでcontentを140字に制限する。
このコードは
contentのlengthのmaximumを140に制限するという、非常に単純なコード。
投稿の文字数の最大を140文字とも言い換えられる。
バリデーションを追加することでRailsがエラーを吐くようになった。
実際にMicropostを140文字以上で投稿してみた画面がこちら。
超優秀。
###演習
- 上の画面のようにエラーを吐くようになり、indexページに戻っても投稿は追加されない。(データベースに登録されない)
- error_explanationクラスの中にulリストでエラーの内容が格納されている。エラーが増えればliタグのコンテンツがエラーとして追加される。
######ユーザーとマイクロポストの関連付け
TwitterやQiitaなどでもそうだが
”一人のユーザーは複数の投稿を持っている”
具体的な言い方に換えると
ユーザIDが1の投稿は複数存在しうる。
こういった1対多のデータの関係性をRailsは超簡単に実装できる。
そのコードがこちら
class User < ApplicationRecord
has_many :microposts
end
class Micropost < ApplicationRecord
belongs_to :user
validates :content, length:{maximum: 140}
end
Userはmicropostを複数持っている。
micropostsは一つのユーザーに属する。
この関係性をこのコードで実装している。
このコードを書くことでMicropostsのuser_idカラムの値からユーザーを表示することができる。
実際にやってみよう。
まずはRailsの機能を使えるコンソールrails console
を起動する。
rails console内ではデータベースのデータやコードを自由に試せるので非常に便利。
まずはrails console内でユーザーを作成し、そのユーザーのIDをもったMicropostも作成する。
user1 = User.first
(0.4ms) SELECT sqlite_version(*)
User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "takemo", email: "take.webengineer@gmail.com", created_at: "2020-05-31 14:00:23", updated_at: "2020-05-31 14:00:23">
2.6.3 :002 > user1.microposts
user1にUserデータベースの一番最初に登録されているデータを代入
ローカルサーバーで以前ユーザーをお試しで作ったのでそのデータが代入されている。
ここで先ほどの関連付けが役立つシーンがこちら
> user1.microposts
Micropost Load (0.1ms) SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? LIMIT ? [["user_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Micropost id: 1, content: "taskemodsjfa jl", user_id: 1, created_at: "2020-06-01 14:24:23", updated_at: "2020-06-01 14:24:23">, #<Micropost id: 3, content: "Ruby(ルビー)は、まつもとゆきひろ(通称: Matz)により開発されたオブジェクト指向スクリプト...", user_id: 1, created_at: "2020-06-01 14:27:03", updated_at: "2020-06-01 14:27:03">, #<Micropost id: 5, content: "異なるデータモデル同士の関連付けは、Railsの強力な機能です。ここでは、1人のユーザーに対し複数の...", user_id: 1, created_at: "2020-06-01 14:41:51", updated_at: "2020-06-01 14:41:51">]>
2.6.3 :003 >
関連付けによってUserには複数のMicropostが結びついていることになったので、user1.micropostsとすると
user1に結びついたMicropostをすべて取り出すことができる。(ユーザーが何を投稿したか調べられる。)
逆方向の使い方を試してみる(投稿から誰が投降したのかを調べる。)
> micropost = user1.microposts.first
Micropost Load (0.1ms) SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ? [["user_id", 1], ["LIMIT", 1]]
=> #<Micropost id: 1, content: "taskemodsjfa jl", user_id: 1, created_at: "2020-06-01 14:24:23", updated_at: "2020-06-01 14:24:23">
micropost変数にuser1(user_id=1)の最初のmicropostを代入した。
つまりmicropostからuser_idが1のユーザーを取り出せればいい。
> micropost.user
=> #<User id: 1, name: "takemo", email: "take.webengineer@gmail.com", created_at: "2020-05-31 14:00:23", updated_at: "2020-05-31 14:00:23">
2.6.3 :004 >
micropost.user
とするだけでmicropostが属するユーザーを取り出すことができた。
1対多の関連性をhas_manyとbelongs_toを使って実装するだけでこのような便利な使い方ができる。
###演習
- @userのmicropostsの中でfirstのmicropostのcontentを表示すればいいのでそのままほかの行からコピペして書き換えるとこうなる。
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= @user.name %>
</p>
<p>
<strong>Email:</strong>
<%= @user.email %>
</p>
<p>
<strong>Micropost_first</strong>
<%= @user.microposts.first.content %>
</p>
<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>
2.コードを追加したら実際に空投稿してみると
バリデーションに引っかかってエラーを吐いた。
3.FILL_INにはバリデーションを適用したいカラムを指定する。(nameとemail)
class User < ApplicationRecord
has_many :microposts
validates :name, presence: true
validates :email, presence: true
end
バリデーションを追加したのでこちらも空登録するとエラーを吐くようになった。
###Railsの継承について
ここは何となくの理解でもとりあえずOK
Railsのモデルは User < ApplicationRecord
というRuby特有の文法で継承されている。
モデルの継承の構造は図の通り
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#fig-demo_model_inheritance
UserやMicropostといったRailsのモデルはすべてApplicationRecordクラスを継承している。
そしてApplicationRecordはActiveRecord::Baseという基本クラスを継承している。
このActiveRecordがデータベースを扱う機能を提供している。
User.firstのfirstメソッドなどもこのActiveRecordを継承していることにより使える。
コントローラーも似たような構造でできている。
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#fig-demo_controller_inheritance
各コントローラーはApplicationControllerを継承しておりApplicationControllerは
ActionController::Baseという基本クラスを継承していることにより
様々なコントローラーで等しくコントローラーの基本機能を使うことができる。
またApplicationControllerを継承しているのでApplicationControllerで定義したルールは
継承先の各コントローラーで適用され、定義したメソッドなども同じように使用できる。
###演習
1.1行目
class ApplicationController < ActionController::Base
def hello
render html:"hello,world!"
end
end
2.モデルが格納されているmodelsフォルダのapplication_record.rbの1行目
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
###最後に
この章で作ったToyアプリをHerokuにデプロイする
$ git add -A
$ git commit -m "Finish toy app"
$ git push
$ git push heroku
このままだとHeroku上でエラーが発生して本番環境でサイトが表示されない。
heroku logsでHeroku上のエラーログを追ってみる。
するとこのような行がある。
2020-06-01T15:52:35.330114+00:00 app[web.1]: [3a72d85c-e21f-41d2-92ce-40c241660d8f]
ActionView::Template::Error (PG::UndefinedTable: ERROR: relation "users" does not exist
PGとはPostgreSQLのことである。
意味はPGでusersテーブルが存在しないと怒られている。
本番環境のデータベースと開発環境のデータベースは違うため
マイグレーションを本番環境でも行わなければならない。
つまりマイグレーションをしていないから怒られている。
heroku run rails db:migrate
で本番環境のデータベースをマイグレートする。
これでしっかり動作するはず。
###演習
1.ここで先ほどの演習2.3.3.1をやっていると本番環境のデータベースにはマイクロポストが一つも登録されておらず
エラーを吐いてしまうため、演習で追加したコード部は削除して、コミット、プッシュしなおしておく。
うまくいけばこのようにユーザーを作成できる。
2.本番環境でも同じくマイクロポストを作成できることが確認できた。