LoginSignup
4
5

More than 3 years have passed since last update.

Rails Tutorial 第6版 学習まとめ 第2章

Last updated at Posted at 2020-06-02

概要

この記事は私の知識をより確実なものにするためにRailsチュートリアル解説記事を書くことで理解を深め
勉強の一環としています。稀にとんでもない内容や間違えた内容が書いてあるかもしれませんので
ご了承ください。
できればそれとなく教えてくれますと幸いです・・・

出典
Railsチュートリアル第6版

この章でやること

toy_appの作成を通してscaffoldの強力な機能を知る
RESTアーキテクチャとは何か大まかに把握する。

Toy_appの生成

①hello_appと同じようにRailsのバージョンを指定して生成する

$ cd ~/environment
$ rails _6.0.3_ new toy_app
$ cd toy_app/

②Gemfileも新しくなったのでToyアプリ用に書き換える

Gemfile
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を出力するように追記しておく

application_controller.rb
class ApplicationController < ActionController::Base

  def hello
    render html: "hello, world!"
  end
end
routes.rb
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への接続を許可しておく。

development.rb
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チュートリアルを参照。

演習

  1. F12キーなどでブラウザのデベロッパーツールを起動し、該当の箇所を調べると、リロード時に内容が削除されている。
  2. emailを入力しないで登録しても登録できてしまう。これはまずいので3章から作るアプリではこのような問題の対策もしていく。
  3. 間違えたメールアドレスを入力されても登録できてしまう。2と同じでこれも対策することになる。
  4. Are you sure?と警告メッセージが表示されるようになっている。こういったユーザーの使いやすいように工夫された機能も実装していく。

MVCの挙動

/usersのindexページを開くと内部でどういった処理をしているのか、MVCで図解するとこのようになる。
image.png
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#sec-mvc_in_action

ブラウザからのリクエスト→ルーターが該当するアクションに割り当て→必要なデータをUserモデルに問い合わせる
→データベースからモデルがデータを取り出す→モデルがデータをコントローラに渡す
→インスタンス変数に受け取ったデータを代入してビューに渡す→ビューはHTMLを生成し、コントローラに渡す
→コントローラがブラウザに受け取ったHTMLを返す
という流れ。

Toyアプリのとりあえずの形ができたのでルートURLをToyアプリのインデックスページに変更してみる。

routes.rb
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をのぞいてみる

users_controller.rb
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ビューで使われている。
該当の場所をみてみる。

index.html.erb



<% @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を返す。

2.
users_controllerのprivateメソッドに記述されている。

users_controller.rb
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で投稿ができる。

演習

  1. Usersの時と同じく、中身が消える。
  2. これもUsersの時と同じで、現状対策されていないため空のユーザーで空の投稿ができてしまう。
  3. 文字数を制限するコードが生成されていないため、投稿できてしまう。(これも欠点の一つ)
  4. indexページからdestroyリンクを踏むことで削除できる。これも確認メッセージが表示されてから削除される。
マイクロポストをマイクロなポストに

前回の演習の3番目の項目で文字数制限がないため、長い文章でも投稿できてしまいマイクロでない問題が発覚した。
この問題を修正していく。

データベースの内容に制限(バリデーション)を加えることでこの問題を修正する。
具体的なコードは下のとおり

micropost.rb
class Micropost < ApplicationRecord
  validates :content, length:{maximum: 140}

end

データベースを扱うモデルにバリデーションを書くことでcontentを140字に制限する。
このコードは
contentのlengthのmaximumを140に制限するという、非常に単純なコード。
投稿の文字数の最大を140文字とも言い換えられる。

バリデーションを追加することでRailsがエラーを吐くようになった。
実際にMicropostを140文字以上で投稿してみた画面がこちら。
image.png

超優秀。

演習

  1. 上の画面のようにエラーを吐くようになり、indexページに戻っても投稿は追加されない。(データベースに登録されない)
  2. error_explanationクラスの中にulリストでエラーの内容が格納されている。エラーが増えればliタグのコンテンツがエラーとして追加される。
ユーザーとマイクロポストの関連付け

TwitterやQiitaなどでもそうだが
”一人のユーザーは複数の投稿を持っている”
具体的な言い方に換えると
ユーザIDが1の投稿は複数存在しうる。
こういった1対多のデータの関係性をRailsは超簡単に実装できる。
そのコードがこちら

user.rb
class User < ApplicationRecord
  has_many :microposts
end
micropost.rb
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を使って実装するだけでこのような便利な使い方ができる。

演習

  1. @userのmicropostsの中でfirstのmicropostのcontentを表示すればいいのでそのままほかの行からコピペして書き換えるとこうなる。
show.html.erb
<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 %>

↓結果
image.png

2.コードを追加したら実際に空投稿してみると
バリデーションに引っかかってエラーを吐いた。
image.png

3.FILL_INにはバリデーションを適用したいカラムを指定する。(nameとemail)

user.rb
class User < ApplicationRecord
  has_many :microposts
  validates :name, presence: true
  validates :email, presence: true
end

バリデーションを追加したのでこちらも空登録するとエラーを吐くようになった。
image.png

Railsの継承について

ここは何となくの理解でもとりあえずOK
Railsのモデルは User < ApplicationRecordというRuby特有の文法で継承されている。
モデルの継承の構造は図の通り
image.png

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を継承していることにより使える。

コントローラーも似たような構造でできている。
image.png
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#fig-demo_controller_inheritance

各コントローラーはApplicationControllerを継承しておりApplicationControllerは
ActionController::Baseという基本クラスを継承していることにより
様々なコントローラーで等しくコントローラーの基本機能を使うことができる。
またApplicationControllerを継承しているのでApplicationControllerで定義したルールは
継承先の各コントローラーで適用され、定義したメソッドなども同じように使用できる。

演習

1.1行目

application_controller.rb
class ApplicationController < ActionController::Base
  def hello
    render html:"hello,world!"
  end
end

2.モデルが格納されているmodelsフォルダのapplication_record.rbの1行目

application_record.rb
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をやっていると本番環境のデータベースにはマイクロポストが一つも登録されておらず
エラーを吐いてしまうため、演習で追加したコード部は削除して、コミット、プッシュしなおしておく。
image.png
うまくいけばこのようにユーザーを作成できる。


2.本番環境でも同じくマイクロポストを作成できることが確認できた。

image.png

3.もちろんちゃんと動く
image.png

←前の章へ

次の章へ→

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5