[Ruby入門 Rails5編] 03. scaffold でモデル関連のリソースを一括作成する


前回まででRailsアプリケーションを新規作成する方法を学んだので、ここからはサンプルアプリの作成を通してRailsを学んでいく。
今回は主に、scaffold でモデルとその周辺のリソース群を一括作成する方法を学ぶ。
ちょっと長くなるが、基本的には自動生成して中身を確認、の繰り返しなので、作業は楽なはず！

### 【０】これから作るもの
---

[Ruby on Rails チュートリアル](https://railstutorial.jp/?version=5.0) に沿って、なんちゃってTwitterのようなものを作る。
超々シンプルなアプリの作成を通して、Railsの肝となる要素をおおまかに体験してみる感じになる。
Rails体験ツアーって感じかな。

●アプリ名
「minitter」
　　どっかにありそうな名前だけど、まあ気にしない。みなさんも好きに命名してください。

●登場するモデルたち
「users」
　　文字通り、minitterのユーザを表すモデル
　　属性は以下
　　　id: integer型。主キー
　　　name: string型。ユーザ名

「posts」
　　ツイートに当たる、ユーザの投稿データ
　　属性は以下
　　　id: integer型。主キー
　　　body: text型。投稿の本文。10文字に制限する想定（みじか！）
　　　user_id: integer型。投稿したユーザのID（usersテーブルのid）

※データベースについて
今回はデータベースとして、とりあえずRailsの標準であるSQLiteを利用する。
Railsをインストールできている時点で、SQLiteもインストールされているはず。

```bash
# yum list installed | grep -i sql
sqlite.x86_64                     3.7.17-8.el7                       @CentOS    
sqlite-devel.x86_64               3.7.17-8.el7                       @base  
```

　
### 【１】アプリケーションの作成
---

まずは新規に「minitter」というアプリケーションを作成する。

```bash
# Macと共有しているフォルダに移動
cd /docker-host/share/webapps/

# Railsのバージョンを指定して、「minitter」というアプリをnewする
rails _5.1.1_ new minitter
```

　
### 【２】プロジェクトをGit管理する
---

これは必須ではないが、Gitだと差分が見れる → scaffoldとかで自動生成した時、なにが作成されたか確認しやすい、ので、ここでGit管理しておくことにする。
この連載の手順で環境を構築していればgitコマンドはインストール済みのはず。まだの場合は「 yum intall -y git 」（←CentOSの場合）などでインストールしておく

```bash
# gitユーザを設定
$ git config --global user.name "miro"
$ git config --global user.email miro@localhost

# プロジェクトのフォルダへ移動
$ cd /docker-host/share/webapps/minitter

# 初期化してみる
$ git init
Reinitialized existing Git repository in /docker-host/share/webapps/minitter/.git/
   # ↑ 既にあると。Railsのプロジェクトは最初からGit的に初期化された状態

# 状況を確認
$ git status
〜 中略 〜
nothing added to commit but untracked files present (use "git add" to track)
   # ↑ git add しろと。

# add → commit する
$ git add .
$ git commit -m "とりあえず初期コミット！"

# ふたたび状況を確認
$ git status
On branch master
nothing to commit, working directory clean
```

ちなみに今回はMac側との共有フォルダにアプリケーションを作っているので、Mac側からもGit操作できる。

```bach
$ cd /Users/miro/docker/cent72/share/webapps/minitter
$ git log --oneline
e4d2efb とりあえず初期コミット！
```

　
### 【３】Users モデル関連のリソースを scaffold で生成してみる
---

今回の肝となる scaffold によるリソースの一括生成を試してみる

```bash
# プロジェクトのフォルダへ移動
$ cd /docker-host/share/webapps/minitter

# 生成！ リソース名とカラム定義を指定。id は勝手に生成されるので指定不要
$ rails generate scaffold User name:string
Running via Spring preloader in process 14504
〜 中略 〜


# 差分を詳しく見るために、いったん .gitignore をリネーム
$ mv .gitignore .gitignore_bk


# 差分を表示。なにが作られた(または編集された)のか？
$ git status -s
 D .gitignore
 M config/routes.rb
?? .gitignore_bk
?? app/assets/javascripts/users.coffee
?? app/assets/stylesheets/scaffolds.scss
?? app/assets/stylesheets/users.scss
?? app/controllers/users_controller.rb
?? app/helpers/users_helper.rb
?? app/models/user.rb
?? app/views/users/
?? db/migrate/
?? log/development.log
?? test/controllers/users_controller_test.rb
?? test/fixtures/users.yml
?? test/models/user_test.rb
?? test/system/users_test.rb


# .gitignore を忘れずに元に戻す
$ mv .gitignore_bk .gitignore


# フォルダ app/views/users/ の中には何が作られたのか
$ ls -1 app/views/users/
_form.html.erb
_user.json.jbuilder
edit.html.erb
index.html.erb
index.json.jbuilder
new.html.erb
show.html.erb
show.json.jbuilder


# フォルダ db/migrate/ の中には何が作られたのか
$ ls -1 db/migrate/
20170516021036_create_users.rb


# routes.rb はどう編集されたのか？
$ git diff config/routes.rb
〜 中略 〜
+  resources :users
〜 中略 〜


# 差分を確認して気が済んだら、いったんコミットする
$ git add .
$ git commit -m "scaffold で Usersリソースを生成"
```

ざっと以下のリソースが生成されているようだ。
・コントローラ
・モデル
・ビュー
・ヘルパー
・テーブル作成等のマイグレーション
・JSやCSSなどのassets関連
・テスト関連

いわゆるMVC以外に、ヘルパーやJS/CSS、テスト関連などもセットで生成されているのが特徴かな。

あと、ルーティングの設定ファイルである「routes.rb」には、以下の設定が追加されている。
「resources :users」
生成されたCRUD関連のページへのルーティングが、この１行で表現されていると思われる。

　
### 【４】データベースの初期化とマイグレーション
---

「rails db:migrate」を実行することで、前述の scaffold で生成されたマイグレーションファイルから、テーブルの作成が行われる。
初回のマイグレーションでは、SQLiteのデータベース（実体はファイル）の作成も行われる。

① 生成されたマイグレーションファイルの中身を確認してみる

「db/migrate/20170516021036_create_users.rb」

```ruby
class CreateUsers < ActiveRecord::Migration[5.1]
  def change
    create_table :users do |t|
      t.string :name

      t.timestamps
    end
  end
end
```

scaffold のコマンドで指定した通り「users」テーブルの定義が記述されれている。
マイグレーションでは、このスクリプトが実行されるはず。

　
② データベースの接続設定も確認しておく

マイグレーションではデータベースの作成とテーブルの作成を行うのだから、DBへの接続設定が記述されているはず。
DBへの接続設定ファイルは、「config/database.yml」だと思われる

「config/database.yml」

```yaml
# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
#
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test.sqlite3

production:
  <<: *default
  database: db/production.sqlite3
```

デフォルトのSQLiteの設定が記述されている。
「db/ほにゃらら.sqlite3」というファイルとして、データベースが作成されると思われる。

　
③ んで、マイグレーションの実行

```bash
# プロジェクトのフォルダへ移動
$ cd /docker-host/share/webapps/minitter

# マイグレーションを実行！
$ rails db:migrate
== 20170516021036 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0087s
== 20170516021036 CreateUsers: migrated (0.0092s) =============================

# 差分を詳しく見るために、いったん .gitignore をリネーム（SQLiteのDB本体はgitで無視される設定のため）
$ mv .gitignore .gitignore_bk

# んで、差分を見てみる
$ git status -s
 D .gitignore
?? .gitignore_bk
?? db/development.sqlite3
?? db/schema.rb
?? log/development.log

# .gitignore を忘れずに元に戻す
$ mv .gitignore_bk .gitignore

# 差分を確認して気が済んだら、いったんコミットする
$ git add .
$ git commit -m "マイグレーション で DB作成、usersテーブルを作成"
```

db/development.sqlite3 というファイルが、SQLiteのデータベースの本体。
db/schema.rb は、マイグレーションが反映されたスキーマ生成ファイルと思われる。現時点では users の分だけが反映されている状態。

　
④ DBに接続して確認してみる

「sqlite3」コマンドで、直接DBに接続して確認できる。

```bash
$ sqlite3 db/development.sqlite3 
〜 中略 〜
sqlite> .tables
ar_internal_metadata  schema_migrations     users               
sqlite> .schema users
CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
sqlite> .quit
$
```

・users テーブルが作成されていることが分かる
・マイグレーションの管理用と思われるテーブル「schema_migrations」も作成されている
・「ar_internal_metadata」は、本番環境でうっかりDBの削除とかされないようにするためのテーブルらしい

　
#### [補足] マイグレーションしたけど戻したい

```bash
rails db:migrate

↓からの...

↓ひとつ前の状態に戻す
rails db:rollback

↓指定のバージョンに戻す。0なら最初に戻す。
rails db:migrate VERSION=0

# バージョンは「schema_migrations」テーブルで管理されている
sqlite> select * from schema_migrations;
20170516021036
20170516031612
```
　
### 【５】作成されたUsersリソースの各画面へアクセスしてみる
---

① Puma を起動

```bash
$ cd /docker-host/share/webapps/minitter
$ rails server
```

　
② 作成されたUsersリソースの各画面を開いてみる。

いわゆる CRUD に沿った機能が一式作成されているので確認してみる。

[ユーザの一覧画面]
http://localhost:3000/users
　登録済みユーザが表示され、それぞれに「Show」「Edit」「Destroy」のリンクが表示される
　最初はまだユーザがいないので、一覧にはなにも出ない

[ユーザの新規作成画面]
http://localhost:3000/users/new
　ユーザの一覧画面の「New User」から遷移できる
　登録すると、ユーザの詳細画面に遷移し、完了メッセージが表示される

[ユーザの詳細画面]
http://localhost:3000/users/ユーザID
　ユーザの一覧画面の「Show」から遷移できる

[ユーザの編集画面]
http://localhost:3000/users/1/edit
　ユーザの一覧画面の「Edit」から遷移できる
　更新すると、ユーザの詳細画面に遷移し、完了メッセージが表示される

[ユーザの削除]
　ユーザの一覧画面の「Destroy」で削除できる

　
### 【６】もう一個のテーブルも作成する
---

ここで満足して忘れそうになったが、「posts」テーブルのほうも作成する。

```bash
$ cd /docker-host/share/webapps/minitter

# 生成！
$ rails generate scaffold Post body:text user_id:integer
〜 中略 〜

# マイグレーション！
$ rails db:migrate
〜 中略 〜

# コミット！
$ git add .
$ git commit -m "postsのリソース生成とマイグレーション"

# 確認！
$ sqlite3 db/development.sqlite3 
〜 中略 〜
sqlite> .tables
ar_internal_metadata  schema_migrations   
posts                 users               
sqlite> .schema posts
CREATE TABLE "posts" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "body" text, "user_id" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
sqlite> .quit
$
```

ここで、先ほどusersで試したのと同様に各画面を確認しておく。
・テーブル定義どおり、Body と User(user_idを表す数値) を登録可能となっている
・まだ posts テーブルの user_id は users テーブルに紐付いていないので、存在しない User のIDも指定できる。
あとは users のときと全く同じになっている。

　
### 【７】コントローラのソースをざっくり見てみる
---

以下のファイルを確認する。
「app/controllers/users_controller.rb」
「app/controllers/posts_controller.rb」

ポイントは以下。（詳しくはのちのち学ぶので、ざっくり概要をとらえる程度）

■継承関係

+ ApplicationController という基底クラスを継承している
+ ApplicationController は、ActionController::Base を継承している

■アクションメソッド

+ 先ほど確認したCRUDに沿った、各アクションメソッドが定義されている

■リクエストパラメータの利用

+ 「 params[キー名] 」で、生のリクエストパラメータを取得できる
+ 「post_params」というメソッドを定義して、特定のリクエストパラメータだけを取り出している

■モデルクラスの利用

+ 「User」「Post」等の、各モデルクラスを利用している
+ 「User.all」「Post.all」で、データを全件取得できる
+ 「Post.find(ID)」で、データを１件取得できる
+ 「 @post = Post.new(post_params) 」→「 @post.save 」で、データをリクエストパラメータで新規作成している
+ 「 @post.update(post_params) 」で、データをリクエストパラメータで更新している。ここでの @post はさきほどの find() で取得している
+ 「 @post.destroy 」で、データを削除している。ここでの @post はさきほどの find() で取得している

　
### 【８】モデルのソースをざっくり見てみる
---

以下のファイルを確認する。
「app/models/user.rb」
「app/models/post.rb」

ポイントは以下。（詳しくはのちのち学ぶので、ざっくり概要をとらえる程度）

■継承関係
・ApplicationRecord という基底クラスを継承している
・ApplicationRecord は、ActiveRecord::Base を継承している
Postクラス/Userクラスも、親クラスのApplicationRecordクラスも、実装は空っぽ。
すなわちActiveRecord::Base クラスで基本的な機能は網羅されているらしい。

■抽象クラス？
ApplicationRecordクラスに「 self.abstract_class = true 」という記述がある。
インスタンス化できない抽象クラスをRails的にエミュレートしているらしい（Rubyの言語仕様としては抽象クラスというのはないので）
さらに「このクラスは実際のテーブルには対応していませんよ。アプリケーションのモデル共通の基底クラスですよ」というマークでもあるらしい。

　
### 【９】ビューのソースをざっくり見てみる
---

ビュー関連のファイルは、app/views/リソース名/ の下に、ページごとに、
「ほにゃらら.html.erv」
「ほにゃらら.json.jbuilder」
のセットが生成されているようだ。

ポイントは以下。（詳しくはのちのち学ぶので、ざっくり概要をとらえる程度）

■２つ１組のファイル（ *.html.erv , *.json.jbuilder ）

+  「ほにゃらら.html.erv」が、いわゆるページのテンプレート。erb は Embedded Ruby（埋め込みRuby） の略
+  「ほにゃらら.json.jbuilder」は、jsonのテンプレートらしい。具体的にどんな場面で使われるのかは今はわからない。

■erbテンプレートの記述

+ <% 〜 %> を使って、Rubyのロジックを書く（ループ処理とか）
+ <%= 〜 %> で、値の出力ができる
+ コントローラ側で用意したインスタンス変数「%変数」は、ビュー側でも使える
+ 「form_with」でHTMLのformを出力し、「form.text_area」などでformの要素を出力している
+ テンプレートから別のテンプレートの読み込みは「<%= render 'form', post: @post %>」などで可能で、変数も渡せる

　
### 【10】ちょこっとバリデーションを入れてみる
---

バリデーションの詳細はのちのち学ぶ予定ので、ここではお試しで、投稿の文字数を制限してみる。

バリデーションはモデルクラスに記述することができる。

「app/models/post.rb」に追記する。

```ruby
class Post < ApplicationRecord
    # 「body」属性を10文字制限にしてみる
    validates :body, length: { maximum: 10 }
end
```

んで、さきほど確認したPostの新規投稿ページから、10文字を超えるBodyで登録を試みる。

http://localhost:3000/posts/new
↓
Body is too long (maximum is 10 characters)

エラーが表示された！

　
### 【11】モデル（テーブル）の関連を定義する
---

今回の例だと、users と posts は 1:n の関係になるから、モデルクラスにそのように定義してみる。

① モデルクラスに、関係性を記述する

「app/models/user.rb」

```ruby
class User < ApplicationRecord

    # ユーザ(User)は 複数の投稿(Post)を持っている
    has_many :posts

end
```

「app/models/post.rb」

```ruby
class Post < ApplicationRecord

    # 投稿(Post)は、ひとりのユーザ(User)に紐付いている
    belongs_to :user

    # 「body」属性を10文字制限にしてみる
    validates :body, length: { maximum: 10 }

end
```

　
② CRUDの挙動にどう影響するか確認する

ここで、さきほど確認したPostの新規投稿ページから、存在しないUserのID（999とか）を指定して投稿してみる

http://localhost:3000/posts/new
↓
User に 999 とか存在しないUserのID入力して Create Post を押下
↓
User must exist

「そんな奴おらん！」エラーが表示された！

　
③ モデルクラスの検索にどう影響するか確認する

③-a. コントローラの任意のアクションに、デバッグコードを仕込む

「app/controllers/users_controller.rb」

```ruby
  def index
    @users = User.all

    # 1件目のユーザの、１件目の投稿の「body」属性を、ログファイルに出力する
    #  User オブジェクトから Postオブジェクトを引いているところがポイント
    logger.debug(User.first.posts.first.body)
  end
```

　
③-b. ログファイルを「tail -f」で開いておく

```bash
$ cd /docker-host/share/webapps/minitter
$ tail -f log/development.log
ログファイルが開かれ、出力の待ち状態になる...
```

　
③-c. んで、ブラウザから上記a.でログを仕込んだアクションを開いてみる
http://localhost:3000/users

　
③-d. ログファイルに投稿が出力される

```bash
〜 中略 〜
  Post Load (4.3ms)  SELECT  "posts".* FROM "posts" WHERE "posts"."user_id" = ? ORDER BY "posts"."id" ASC LIMIT ?  [["user_id", 2], ["LIMIT", 1]]
test
〜 中略 〜
```

↑ user_id で posts を検索して投稿を取得し、そのbody（ここでは test ）を取得していることが分かる

---
　
今回は思いのほか長くなってしまった。。。
Railsアプリケーションの核となる部分を、駆け足でツアーした感じでお腹いっぱい。 ε= o(´〜｀;)o

今日はここまで！