前回までで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
今日はここまで!