Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

【0】これから作るもの


Ruby on Rails チュートリアル に沿って、なんちゃって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もインストールされているはず。

# 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  

 

【1】アプリケーションの作成


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

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

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

 

【2】プロジェクトをGit管理する


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

# 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操作できる。

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

 

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


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

# プロジェクトのフォルダへ移動
$ 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関連のページへのルーティングが、この1行で表現されていると思われる。

 

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


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

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

「db/migrate/20170516021036_create_users.rb」

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」

# 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」というファイルとして、データベースが作成されると思われる。

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

# プロジェクトのフォルダへ移動
$ 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に接続して確認できる。

$ 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の削除とかされないようにするためのテーブルらしい

 

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

rails db:migrate

↓からの...

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

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

# バージョンは「schema_migrations」テーブルで管理されている
sqlite> select * from schema_migrations;
20170516021036
20170516031612

 

【5】作成されたUsersリソースの各画面へアクセスしてみる


① Puma を起動

$ 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」で削除できる

 

【6】もう一個のテーブルも作成する


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

$ 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 のときと全く同じになっている。

 

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


以下のファイルを確認する。
「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)」で、データを1件取得できる
  • @post = Post.new(post_params) 」→「 @post.save 」で、データをリクエストパラメータで新規作成している
  • 「 @post.update(post_params) 」で、データをリクエストパラメータで更新している。ここでの @post はさきほどの find() で取得している
  • 「 @post.destroy 」で、データを削除している。ここでの @post はさきほどの find() で取得している

 

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


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

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

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

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

 

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


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

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

■2つ1組のファイル( *.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」に追記する。

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」

class User < ApplicationRecord

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

end

「app/models/post.rb」

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」

  def index
    @users = User.all

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

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

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

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

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

〜 中略 〜
  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

今日はここまで!

prgseek
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした