前回の続き。
今回はRailsで簡単なモデルを作ったり、モデルクラスから情報を取得、編集、削除したりする。
手順
データベースを作成する
ターミナル上で直接データベースを作成してもよいのだが、Railsではデータベースを作成するコマンドが用意されている。
rails db:create
結果
Database 'db/development.sqlite3' already exists Created database 'db/test.sqlite3'
本来ならdb/development.sqlite3
もcreatedとなるのだが、私の環境ではすでに作成されていたようす。
ORMでプログラムからDBへアクセスする
- ORM(Object Relation Mapping)とは、オブジェクト指向言語からRDBにアクセスするための仕組み
- ORMの概念に基づいたライブラリを「O/Rマッパー」と呼ぶ
- RailsやSinatraのO/RマッパーはActiveRecord
- テーブルの情報をモデルクラスとして取得する
ActiveRecordのルール
- モデルクラスは先頭が大文字の単数形、テーブル名は小文字で複数形
- 例:モデルクラス「Memo」、テーブル「memos」
- 2つ以上の単語を組み合わせた場合、テーブル名は小文字でアンダースコアでつなげて最後の単語が複数形
- モデルクラス「UserItem」、テーブル「user_items」
- レコードを識別するためのカラム名はデフォルトで id となる
- ActiveRecordでテーブルを新規作成すると、idカラムは指定する必要がなく、自動で追加される
- created_at, updated_atも自動で追加される
モデルクラスを作成
モデルクラスを作るには、次のコマンドを使う。
rails g model モデル名 カラム名1:データ型 カラム名2:データ型…
これを実行すると、モデルクラスとマイグレーションファイルが作られる。
マイグレーションファイルとは、テーブルの新規作成や構造変更を簡単に管理してくれるファイルのこと。マイグレーションを使うことで、DBMSによって異なるテーブル定義のSQLを、DBMSの種類に左右されず、同じ定義方法で記述できる。
作成例
rails g model Diary title:string body:text
意味
- Diaryというモデルクラスを作成する
- titleカラムを用意し、データ型はstringにする
- bodyカラムを用意し、データ型はtextにする
結果
Running via Spring preloader in process 9912 invoke active_record create db/migrate/20200116062630_create_diaries.rb create app/models/diary.rb invoke test_unit create test/models/diary_test.rb create test/fixtures/diaries.yml
最初の
db/migrate/20200116062630_create_diaries.rb
がマイグレーションファイル。 db/migrate下に作成され、ファイル名は作成された日時が接頭辞(prefix)として付与される。Diaryモデルクラスに対応し、自動でdiariesというテーブル名になっている。
app/models/diary.rb
これはモデルクラスのファイル。app/models配下に作られる。
test/models/diary_test.rb
はモデルクラスのテストコードの雛形。
test/fixtures/diaries.yml
はフィクスチャと呼ばれる、テストデータを作成するための雛形。
マイグレーションファイルの中身
マイグレーションファイルである、db/migrate/20200116062630_create_diaries.rb
を見てみる。
class CreateDiaries < ActiveRecord::Migration[6.0] def change create_table :diaries do |t| t.string :title t.text :body t.timestamps end end end
このマイグレーションファイルを実行すると、テーブル作成の処理が記述されているchangeメソッドが実行される。
def change create_table :diaries do |t| t.string :title t.text :body t.timestamps end
t.timestamps
という記述は、作成するテーブルにcreated_atとupdated_atというカラムをつくる。
モデルクラス
app/models/diary.rb
を見てみる。
class Diary < ApplicationRecord end
- ApplicationRecordクラスを継承している
- このモデルクラスがあるだけでRailsアプリ上からdiariesテーブルにアクセスできるようになる
同一ディレクトリにあるapp/models/application_record.rb
も見てみる。
class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end
- ApplicationRecordクラスは、モデルクラス間でアプリ共通の設定を記述するためのクラス
- ActiveRecord::Baseクラスという、ActiveRecordの基本機能を提供するクラスを継承している
- ApplicationRecordクラスを継承することで、ActiveRecordの基本機能がすべて使える
self.abstract_class = true
は、あるモデルクラスを継承して別のモデルクラスを定義した場合に、元のモデルクラスが存在しない場合でもエラーが発生しないようにするための設定
マイグレーションを実行してファイルを作成
rails g model Diary title:string body:text
これをした時点では、モデルクラスとマイグレーションファイルが作成されるだけで、データベース上に必要なテーブルはまだ作成されていない。マイグレーションコマンドを実行する必要がある。
rails db:migrate
これでデータベースにテーブルが新規作成される。
Railsのデータベース用コンソールを起動して確かめる。
rails dbconsole
こんな画面が出てsqliteにアクセスできる。
SQLite version 3.22.0 2018-01-22 18:45:57 Enter ".help" for usage hints.
ここでは開発環境用のデータベースであるdevelopment.sqlite3
に接続される。
テーブル確認。
.tables
diariesテーブルが作られていることがわかる。
ar_internal_metadata diaries schema_migrations
ar_internal_metadata
: データベースを誤って削除してしまわないようにするために参照されるテーブルschema_migrations
: マイグレーションのバージョン管理のためのテーブル
これらはRailsが自動生成している。
テーブル構造を確認するにはこちら。
.schema diaries CREATE TABLE IF NOT EXISTS "diaries" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar, "body" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
idが自動連番で生成されてる点に注目。
テストデータを準備
- Railsアプリを開発中に、動作確認をするために必要最低限なデータを準備する
- そんなデータを定義する仕組みとしてフィクスチャがある
test/fixtures/diaries.yml
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: id: 1 title: '今日は筋トレをした' body: 'たくさんベンチプレスをした' two: id: 2 title: '今日食べたもの' body: '今日は鶏肉を食べた'
デフォルトファイルにはMystringなどと書かれているが、オリジナルに変更した。idカラムは自動連番で勝手にデータが入力されるが、その場合、振られる番号がわからないため、自分で付与しておいたほうが動作確認時には便利。
テストデータを取り込む
上記で作ったテストデータを取り込む。
rails db:fixtures:load
テーブルを見てみる。
select * from diaries; 1|今日は終日休暇|今日は仕事を休んで、公園で読書を楽しんだ。|2020-01-16 06:37:16.275065|2020-01-16 06:37:16.275065 2|今日の天気|今日は鶏肉を食べた|2020-01-16 06:37:16.275065|2020-01-16 06:37:16.275065
ちなみにこれでテーブルの出力結果が変わる。
.headers on .mode column select * from diaries; id title body created_at updated_at ---------- ---------- --------------------- -------------------------- -------------------------- 1 今日は終日休暇 今日は仕事を休んで、公園で読書を楽しんだ。 2020-01-16 06:37:16.275065 2020-01-16 06:37:16.275065 2 今日の天気 今日は鶏肉を食べた 2020-01-16 06:37:16.275065 2020-01-16 06:37:16.275065
.headers on
でヘッダー情報が表示。
.mode column
でレコードの長さに対応した横幅になる。
モデルクラスにアクセス
rails c Running via Spring preloader in process 10814 Loading development environment (Rails 6.0.2.1) irb(main):001:0>
レコードを参照する。
Diary.all
(1.0ms) SELECT sqlite_version(*) Diary Load (0.6ms) SELECT "diaries".* FROM "diaries" LIMIT ? [["LIMIT", 11]] => #<ActiveRecord::Relation [#<Diary id: 1, title: "今日は筋トレをした", body: "たくさんベンチプレスをした", created_at: "2020-01-16 11:41:40", updated_at: "2020-01-16 11:41:40">, #<Diary id: 2, title: "今日食べたもの", body: "今日は鶏肉を食べた", created_at: "2020-01-16 11:41:40", updated_at: "2020-01-16 11:41:40">]>
id指定する。
Diary.find(2)
Diary Load (0.7ms) SELECT "diaries".* FROM "diaries" WHERE "diaries"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] => #<Diary id: 2, title: "今日食べたもの", body: "今日は鶏肉を食べた", created_at: "2020-01-16 11:41:40", updated_at: "2020-01-16 11:41:40">
内部では自動でSQLが実行されているのがわかる。すげぇ。
変数に入れる。
d = Diary.find(2) d => #<Diary id: 2, title: "今日食べたもの", body: "今日は鶏肉を食べた", created_at: "2020-01-16 11:41:40", updated_at: "2020-01-16 11:41:40">
titleを取得。
d.title => "今日食べたもの"
bodyを取得。
d.body => "今日は鶏肉を食べた"
モデルクラスからレコードを作成する
irb(main):007:0> d1 = Diary.new => #<Diary id: nil, title: nil, body: nil, created_at: nil, updated_at: nil>
代入する。
d1.id = 3 => 3 d1.title = 'title3' => "title3" d1.body = 'body3' => "body3"
save
d1.save (0.1ms) begin transaction Diary Create (5.0ms) INSERT INTO "diaries" ("id", "title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["id", 3], ["title", "title3"], ["body", "body3"], ["created_at", "2020-01-16 11:47:18.696422"], ["updated_at", "2020-01-16 11:47:18.696422"]] (2.9ms) commit transaction => true
INSERT INTOが実行されている。
確認すると、
Diary.all Diary Load (1.6ms) SELECT "diaries".* FROM "diaries" LIMIT ? [["LIMIT", 11]] => #<ActiveRecord::Relation [#<Diary id: 1, title: "今日は筋トレをした", body: "たくさんベンチプレスをした", created_at: "2020-01-16 11:41:40", updated_at: "2020-01-16 11:41:40">, #<Diary id: 2, title: "今日食べたもの", body: "今日は鶏肉を食べた", created_at: "2020-01-16 11:41:40", updated_at: "2020-01-16 11:41:40">, #<Diary id: 3, title: "title3", body: "body3", created_at: "2020-01-16 11:47:18", updated_at: "2020-01-16 11:47:18">]>
ちゃんと入ってる。
削除するには。
d1.destroy
確認。
Diary.all Diary Load (0.9ms) SELECT "diaries".* FROM "diaries" LIMIT ? [["LIMIT", 11]] => #<ActiveRecord::Relation [#<Diary id: 1, title: "今日は筋トレをした", body: "たくさんベンチプレスをした", created_at: "2020-01-16 11:41:40", updated_at: "2020-01-16 11:41:40">, #<Diary id: 2, title: "今日食べたもの", body: "今日は鶏肉を食べた", created_at: "2020-01-16 11:41:40", updated_at: "2020-01-16 11:41:40">]>
ないことがわかる。
レコード作成の別の書き方
createメソッドだとレコード挿入とsaveが同時にできる。
Diary.create(id: 3, title: 'title3', body: 'body3')
Diary.find(3) Diary Load (1.4ms) SELECT "diaries".* FROM "diaries" WHERE "diaries"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]] => #<Diary id: 3, title: "title3", body: "body3", created_at: "2020-01-16 11:51:18", updated_at: "2020-01-16 11:51:18">
newメソッドの結果をブロックに渡して値をセット。
irb(main):021:0> d4 = Diary.new do |d| irb(main):022:1* d.id = 4 irb(main):023:1> d.title = 'title4' irb(main):024:1> d.body = 'body4' irb(main):025:1> end
Diary.find(4) Diary Load (1.2ms) SELECT "diaries".* FROM "diaries" WHERE "diaries"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]] => #<Diary id: 4, title: "title4", body: "body4", created_at: "2020-01-16 11:53:14", updated_at: "2020-01-16 11:53:14">