概要
初めて Ruby on Rails で Web アプリ開発するための、入門編の記事。
Rails の基礎部分をなるべく絞って解説する。
手順 | 記事 |
---|---|
#1 | Rails の開発環境構築 |
#2 | コントローラ・ビューの基本 |
#3 | モデルとマイグレーションの基本(今回) |
#4 | DB のデータを画面に表示する |
#5 | 画面から DB にデータを登録する |
#6 | 画面から DB のデータをの更新・削除する |
対象
- Ruby on Rails で開発をしてみたい(しなければならない状況になった)方
- HTML/CSS で簡単な Web ページを書いたことがある方
- 「DB」「SQL」という言葉の意味を何となく理解できる方
- 「REST API」や「GET」「POST」などを聞いたことがある方
- Ruby もしくは、その他のオブジェクト指向のプログラミング言語に触れた方
- 変数、四則演算、if 文、for 文、などは書いたことある
- クラス、メソッド、インスタンス、などは聞いたことある
前提
macOS で作業する前提で書いてます。Windows の方は適宜、読み替えてください🙏
- OS: macOS 13.12.1 "Ventura"
- CPU: Intel
- Ruby: v3.1.3
- SQLite3: v3.39.5
- Bundler: v2.4.9
- Rails: v7.0.4
手順(データベースとテーブルを作る)
データベース(DB)の設定ファイル
Rails のDB設定は config/database.yml
で設定する。
# デフォルトの DB 接続設定
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
# 開発環境の設定
development:
<<: *default # デフォルトを引き継ぐ
database: db/development.sqlite3
# テスト実行時の設定
test:
<<: *default # デフォルトを引き継ぐ
database: db/test.sqlite3
# 本番環境の設定
production:
<<: *default # デフォルトを引き継ぐ
database: db/production.sqlite3
👉 今回は、SQLite3 を利用するので、このままで OK。MySQL や PostgreSQL など別の DB を利用する場合は、ホスト名やユーザ名などの設定を追加する(具体的な設定内容は割愛)。
👉 デフォルトで設定している値は、各環境の箇所で同じキーを指定すれば値を上書き設定できる。
モデルを追加する
モデルとは、データ取得・新規追加・更新・削除など、DBのデータを使った直接的な処理はまとめる部分。
Rails ジェネレートコマンドで、モデル生成時の基本的な書き方
# 「カラム:データ型」はスペース区切りで複数指定できる。
rails g model モデル名 [カラム名:データ型]
👉 注意点は、この時の「モデル名」を必ず英語の "単数系" で指定すること(尚、後で出来あがるテーブル名は "複数形" になる)。
今回は、ユーザの「名前」と「年齢」を管理する User モデル を作成してみる。
# User モデル生成、カラムは「名前(文字列型)」「年齢(数値型)」
rails g model User name:string age:integer
すると、app 配下にファイル等が自動生成される。
プロジェクトディレクトリ/
├ app
│ ├ models/
+ │ │ └ user.rb # モデル
├ db/
| └ migrate/
+ | └ 20230325081745_create_users.rb # マイグレーションファイル
└ test/
├ fixtures/
+ | └ users.yml # テスト用サンプルデータを定義するフィクスチャファイル
└ models/
+ └ user_test.rb # モデルの単体テストファイル
👉 マイクレーションファイルは先頭に生成日時を表すタイムスタンプ(YYYYMMDDHHmmss
形式)が付与される。これは、万が一、複数のマイグレーションを再実行する時に、時系列にクエリが実行されるようにするため。
👉 上記の場合、2023年3月25日8時17分45秒(UTC:協定世界時)に作られたファイルことになる(日本だと9時間ズレている時間になる)。
👉 「フィクスチャ」「テスト」については今回割愛する
モデルの中身
現在は、モデルクラスのみが存在し、中身が空の状態。
class User < ApplicationRecord
end
👉 基本的にすべてのモデルは、既に用意されている便利機能を利用するために ApplicationRecord
を継承する。
マイグレーションとは
マイグレーションは、アプリのDB構造を変更するときに実行する機能で、Rails の場合は、db/migrate
配下にあるマイグレーションファイルを順次実行することで、それを実現する。
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name
t.integer :age
t.timestamps
end
end
end
👉 基本的にすべてのマイグレーションファイルは、既に用意されている便利機能を利用するために ActiveRecord
を継承する。また、create_table
と t.timestamps
を自動付与する。
👉 create_table
メソッドは、「id」というカラムを「オートインクリメントの主キー」として自動で追加してくれる。
👉 t.timestamps
メソッドは、「created_at(レコード作成日)」「updated_at(レコード更新日)」カラムを自動で追加してくれる。
マイグレーション実行で DB スキーマ作成
マイグレーションファイルはあくまで設計図なので、まだ実際に DB の中身が出来たわけではない。
マイグレーションコマンドで、実際に DB にテーブルを生成する。
rails db:migrate
すると、今回は初回なので、以下のファイルが生成される。また、この時点で DB 内には 「users」 テーブルが生成されている
db/
+ └ development.sqlite3 # SQLite の DB ファイル
+ └ schema.rb # スキーマファイル
👉 development.sqlite3
は、SQLite3 の DB そのもの。schema.rb
は、最新状態の「スキーマ」を記載したスキーマファイル。いずれも、初回のみ自動生成され、次回からは中身の更新になる。
スキーマとは
「スキーマ」は、DB のテーブルやカラム・データ型などの構造を定義するもの(レコードデータ自体は含まれない)。
Rails は、SQLite でも MySQL でも DB の種類に関係なく同じ DB 構造を持てるように、このスキーマファイル .rb
形式で持っている。
マイグレーションを順番に実行していった結果、出来上がる最新状態のスキーマが、schema.rb
となるイメージ。
DB の中身を確認する(SQLite3)
尚、SQLite3 の DB の中身を確認する場合は、以下のコマンドで確認できる
# DB 接続コマンド(今回の database.yml 設定の場合)
sqlite3 db/development.sqlite3
👉 接続を終了する場合は、.quit
、ヘルプは .help
で表示できる。
テーブル一覧表示してみると、users テーブル が出来ている。
-- テーブル一覧表示
.tables
-- 結果(users が生成されている)
ar_internal_metadata schema_migrations users
テーブル定義を確認すると、「id」「created_at」「updated_at」も追加されている。
-- users テーブルのスキーマ確認
.schema users
-- 結果
CREATE TABLE IF NOT EXISTS "users" (
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"name" varchar,
"age" integer,
"created_at" datetime(6) NOT NULL,
"updated_at" datetime(6) NOT NULL
);
確認したら、接続終了する。
手順(Rails コンソールで DB にデータを追加する)
DB に直接データを投入するのも良いが、今回は 「Rails コンソール」 を使って、DB を操作する。まずは、以下のコマンドを実行。
# Rails コンソールを開く
rails console
# もしくは短縮系
rails c
すると、irbプロンプト というものが表示され、irb(main):001:0>
という状態で待機する。
irb(main):001:0>
👉 Rails コンソール内では、直接 Ruby を書いて実行できる。つまり、ここで作成したモデルを使って DB にデータを登録したり削除したりもできる。
👉 irb(main):
の後の 「001」 部分は "行数" なので、改行したり、処理を実施する度にカウントアップしていく。
👉 コンソールを終了する場合は、exit
を実行
作成した User モデルを使って、現在の users テーブルの中身を見てみる。
# "モデル名.all" でテーブルのデータ全件取得(SELECT 文の発行)を実行できる
irb(main):001:0> User.all
User Load (0.1ms) SELECT "users".* FROM "users"
=> [] # 結果は空の状態
👉 マイグレーションで継承していた ActiveRecord
が .all
などの DB 操作メソッドを持っている(モデルも、継承先の奥でこのモジュールを継承している)。
次に、レコードを1件追加してみる。
# "モデル名.create" でデータを追加
irb(main):002:0> User.create(name: "佐藤", age: 17)
TRANSACTION (0.1ms) begin transaction
User Create (1.4ms) INSERT INTO "users" ("name", "age", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "佐藤"], ["age", 17], ["created_at", "2023-03-25 13:25:12.192639"], ["updated_at", "2023-03-25 13:25:12.192639"]]
TRANSACTION (0.6ms) commit transaction
=> #<User:0x000000010c88c090 id: 1, name: "佐藤", age: 17, created_at: Sat, 25 Mar 2023 13:25:12.192639000 UTC +00:00, updated_at: Sat, 25 Mar 2023 13:25:12.192639000 UTC +00:00>
👉 create
メソッドによって、SQL の INSERT 文 が発行され、レコードを一件追加した。
次に、create
メソッド以外の方法で、もう1件データを追加してみる。まず、new
メソッドで、モデルのインスタンスを生成する。
# User モデルのインスタンスを生成し、user という名前にする
irb(main):003:0> user = User.new(name: "鈴木", age: 17)
=> #<User:0x0000000106b2ba90 id: nil, name: "鈴木", age: 17, created_at: nil, updated_at: nil>
save
メソッドを実行すると、新規作成したインスタンスで、新しいレコードを挿入する SQL が発行される
# save メソッドで INSERT 文が自動で実行される
irb(main):004:0> user.save
TRANSACTION (0.1ms) begin transaction
User Create (0.4ms) INSERT INTO "users" ("name", "age", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "鈴木"], ["age", 17], ["created_at", "2023-03-25 13:46:32.717614"], ["updated_at", "2023-03-25 13:46:32.717614"]]
TRANSACTION (1.0ms) commit transaction
=> true
👉 最初の方法との違いは、create
メソッドだと INSERT 文を即実行するが、今回の場合は、インスタンスを作っただけでは SQL が実行されず save
メソッドを実行して初めて INSERT が行われる点。
たった今作成した id: 2
のデータの年齢を、17 から 21 に変更してみる。
# インスタンスの "age" を指定して、21 を代入
irb(main):005:0> user.age = 21
=> 21
# save メソッドを実行すると、自動で UPDATE 文が発行される
irb(main):006:0> user.save
TRANSACTION (0.1ms) begin transaction
User Update (0.4ms) UPDATE "users" SET "age" = ?, "updated_at" = ? WHERE "users"."id" = ? [["age", 21], ["updated_at", "2023-03-25 13:51:41.434868"], ["id", 2]]
TRANSACTION (1.1ms) commit transaction
=> true
👉 save
メソッドは、INSERT 専用の処理ではなく、既にあるデータなら UPDATE を自動で実行してくれる。
最終的に、2つのデータを生成したことを確認する。
# 全データ取得
irb(main):007:0> User.all
User Load (0.2ms) SELECT "users".* FROM "users"
=>
[#<User:0x000000010820c340 id: 1, name: "佐藤", age: 17, created_at: Sat, 25 Mar 2023 13:25:12.192639000 UTC +00:00, updated_at: Sat, 25 Mar 2023 13:25:12.192639000 UTC +00:00>,
#<User:0x000000010820c1b0 id: 2, name: "鈴木", age: 21, created_at: Sat, 25 Mar 2023 13:46:32.717614000 UTC +00:00, updated_at: Sat, 25 Mar 2023 13:51:41.434868000 UTC +00:00>]
ここで、Rails コンソールを終了しておく。
おまけ
間違って余計なデータ(レコード)を作成してしまった場合は、対象のレコードを一度インスタンス化し、destroy
メソッドを実行すると削除できる
# find メソッドで "id" を指定して、削除したいデータをインスタンス化する
irb(main):008:0> user = User.find(3)
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
=> #<User:0x000000010ceabb10 id: 3, name: "間違い", age: 1, created_at: Sat, 25 Mar 2023 14:09:10.052578000 UTC +00:00, updated_at: Sat, 25 Mar 2023 14:09:10.052578000 UTC +00:00>
# destroy メソッドで DELETE 文を発行し、データ削除
irb(main):009:0> aaa.destroy
TRANSACTION (0.1ms) begin transaction
User Destroy (1.6ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 3]]
TRANSACTION (0.7ms) commit transaction
=> #<User:0x000000010ceabb10 id: 3, name: "間違い", age: 1, created_at: Sat, 25 Mar 2023 14:09:10.052578000 UTC +00:00, updated_at: Sat, 25 Mar 2023 14:09:10.052578000 UTC +00:00>
DB を完全削除してやり直したい場合は、Rails コンソールを抜けて、rails コマンドで実施
# DB を削除する (SQLite なら、DB ファイルが消える)
rails db:drop
👉 また rails db:migrate
を実行すれば、データが空の状態で DB が生成される。
まとめ
- モデルは、DB周辺処理を担当する部分
- マイグレーションファイルを生成し、マイグレーションを実行すると DB が構築される
- スキーマは DB の構造を定義するもの
- スキーマファイルは、最新状態の DB スキーマを表す
- Rails コンソールでは Ruby が実行でき、
ActiveRecord
のメソッドを利用することで DB 操作ができる