第6章 ユーザーのモデルを作成する
本章では、一番重要なステップであるユーザー用のデータモデルの作成と、データを保存する手段の確保について学んできます。
6.1 Userモデル
ユーザーの情報を保存するためのデータ構造を作成する。
-
モデル(Model)
Railsでは、データモデルとして扱うデフォルトのデータ構造のことをモデルと呼ぶ。MVCのM。 -
Active Record
データベースとやりとりをするデフォルトのRailsライブラリ。データオブジェクトの作成/保存/検索のためのメソッドを持っている。 -
マイグレーション(Migration)
データの定義をRubyで記述することができるRailsの機能。
6.1.1 データベースの移行
nameとemailの2つの属性からなるユーザーをモデリングする。
$ rails generate model User name:string email:string
Controller名には複数形、Model名には単数形を使う習慣がある。
name:stringやemail:stringオプションのパラメータを渡すことによって、データベースで使いたい2つの属性をRailsに伝える。
generateコマンドによってマイグレーションという新しいファイルが生成され、nameとemailの2つのカラムを持つusersテーブルが作られる。
マイグレーションの適用を忘れずに。
$ rails db:migrate
演習 1
Railsはdb/ディレクトリの中にあるschema.rbというファイルを使っています。これはデータベースの構造(スキーマ(schema)と呼びます)を追跡するために使われます。さて、あなたの環境にあるdb/schema.rbの内容を調べ、その内容とマイグレーションファイル(リスト 6.2)の内容を比べてみてください。
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps # マジックカラム(created_at, updated_at)を作成
end
end
end
ActiveRecord::Schema.define(version: 2021_02_02_064711) do
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", precision: 6, null: false # t.timestampsから作成されたマジックカラム
t.datetime "updated_at", precision: 6, null: false # 同上
end
end
演習 2
ほぼすべてのマイグレーションは、元に戻すことが可能です(少なくとも本チュートリアルにおいてはすべてのマイグレーションを元に戻すことができます)。元に戻すことを「ロールバック(rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。
ActiveRecord::Schema.define(version: 0) do
end
演習 3
もう一度rails db:migrateコマンドを実行し、db/schema.rbの内容が元に戻ったことを確認してください。
元に戻りました!
6.1.2 modelファイル
演習 1 & 2
Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください(ヒント: 4.4.4で紹介したテクニックを使ってみてください)。
同様にして、ApplicationRecordがActiveRecord::Baseを継承していることについて確認してみてください。
$ rails console
> user = User.new # オブジェクトの生成
(0.4ms) SELECT sqlite_version(*)
#<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
> user.class
User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
> user.class.superclass # 継承を確認
ApplicationRecord(abstract)
> user.class.superclass.superclass # 継承を確認
ActiveRecord::Base
6.1.3 ユーザーオブジェクトを作成する
DBを変更したくない場合はコンソールをサンドボックスモードで起動する。データベースへの変更をコンソールの終了時にすべて “ロールバック”(取り消し)される。
$ rails console --sandbox
新しいユーザーオブジェクトを生成(引数が無いとすべての属性がnilオブジェクトを返す)
> User.new
(0.1ms) begin transaction
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
引数にオブジェクトの属性を設定する。
> user = User.new(name: "Michael Haetl", email:"michael@example.com")
=> #<User id: nil, name: "Michael Haetl", email: "michael@example.com", created_at: nil, updated_at: nil>
ユーザーオブジェクトの有効性(Validity)を確認する
> user.valid?
=> true
データベースにUserオブジェクトを保存する
> user.save
(0.1ms) SAVEPOINT active_record_1
User Create (2.6ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Michael Haetl"], ["email", "michael@example.com"], ["created_at", "2021-02-02 07:08:01.765858"], ["updated_at", "2021-02-02 07:08:01.765858"]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
user.saveするまではDBにデータは格納されていない。メモリ上でオブジェクトを作成しただけで、user.valid?という行はただオブジェクトが有効かどうかを確認しただけ。
saveメソッドは、成功: true, 失敗: false を返す。
saveメソッドの実行したあと
> user
=> #<User id: 1, name: "Michael Haetl", email: "michael@example.com", created_at: "2021-02-02 07:08:01", updated_at: "2021-02-02 07:08:01">
# id, created_at, updated_at が代入されている。
# created_atは作成した日時、updated_atは更新した日時
# ドット記法でアクセスができる。
> user.name
=> "Michael Haetl"
> user.email
=> "michael@example.com"
> user.updated_at
=> Tue, 02 Feb 2021 07:08:01 UTC +00:00
User.create でモデルの生成と保存を同時に行うことができる
> User.create(name:"A Nother", email: "another@example.org")
(0.1ms) SAVEPOINT active_record_1
User Create (0.1ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "A Nother"], ["email", "another@example.org"], ["created_at", "2021-02-02 07:09:49.917999"], ["updated_at", "2021-02-02 07:09:49.917999"]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> #<User id: 2, name: "A Nother", email: "another@example.org", created_at: "2021-02-02 07:09:49", updated_at: "2021-02-02 07:09:49">
foo = User.create(name: "Foo", email: "foo@bar.com")
(0.1ms) SAVEPOINT active_record_1
User Create (0.1ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Foo"], ["email", "foo@bar.com"], ["created_at", "2021-02-02 07:10:18.637568"], ["updated_at", "2021-02-02 07:10:18.637568"]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2021-02-02 07:10:18", updated_at: "2021-02-02 07:10:18">**
trueかfalseを返す代わりに、ユーザーオブジェクト自身を返す。
また、返されたユーザーオブジェクトは変数に代入できる。
destroy: オブジェクトを削除する。まだメモリ上には残っている。
> foo.destroy
(0.1ms) SAVEPOINT active_record_1
User Destroy (0.1ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 3]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2021-02-02 07:10:18", updated_at: "2021-02-02 07:10:18">
> foo
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2021-02-02 07:10:18", updated_at: "2021-02-02 07:10:18">
演習 1
user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください。
user.name.class
=> String
> user.email.class
=> String
演習 2
> user.created_at.class
=> ActiveSupport::TimeWithZone
> user.created_at.class.superclass
=> Object
> user.updated_at.class
=> ActiveSupport::TimeWithZone
> user.updated_at.class.superclass
=> Object
6.1.4 ユーザーオブジェクトを検索する
Userモデルの、id = 1のユーザーを探す(find)
> User.find(1)
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Haetl", email: "michael@example.com", created_at: "2021-02-02 07:08:01", updated_at: "2021-02-02 07:08:01">
DBから見つけることができなかった場合
User.find(3)
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
Traceback (most recent call last):
1: from (irb):25
ActiveRecord::RecordNotFound (Couldn't find User with 'id'=3)
# ActiveRecordは id=3 のユーザーを見つけることができない。
特定の属性で検索する
**> User.find_by(email: "michael@example.com")
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "michael@example.com"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Haetl", email: "michael@example.com", created_at: "2021-02-02 07:08:01", updated_at: "2021-02-02 07:08:01">**
ユーザーを検索する一般的な方法
# DBの最初のユーザー
> User.first
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "Michael Haetl", email: "michael@example.com", created_at: "2021-02-02 07:08:01", updated_at: "2021-02-02 07:08:01">
# DBすべてのUserオブジェクト
> User.all
User Load (0.2ms) SELECT "users".* FROM "users" LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "Michael Haetl", email: "michael@example.com", created_at: "2021-02-02 07:08:01", updated_at: "2021-02-02 07:08:01">, #<User id: 2, name: "A Nother", email: "another@example.org", created_at: "2021-02-02 07:09:49", updated_at: "2021-02-02 07:09:49">]>
# ActiveRecord::Relationは、各オブジェクトを配列として効率的にまとめてくれるクラス
演習 1
nameを使ってユーザーオブジェクトを検索してみてください。また、find_by_nameメソッドが使えることも確認してみてください(古いRailsアプリケーションでは、古いタイプのfind_byをよく見かけることでしょう)。
> user.name
=> "Michael Haetl"
User.find_by(name: "Michael Haetl")
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "Michael Haetl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Haetl", email: "michael@example.com", created_at: "2021-02-02 08:03:32", updated_at: "2021-02-02 08:03:32">
演習 2
実用的な目的のため、User.allはまるで配列のように扱うことができますが、実際には配列ではありません。User.allで生成されるオブジェクトを調べ、ArrayクラスではなくUser::ActiveRecord_Relationクラスであることを確認してみてください。
> User.all.class
=> User::ActiveRecord_Relation
2.6.3 :006 > User.all.class.superclass
=> ActiveRecord::Relation
演習 3
User.allに対してlengthメソッドを呼び出すと、その長さを求められることを確認してみてください(4.2.2)。Rubyの性質として、そのクラスを詳しく知らなくてもなんとなくオブジェクトをどう扱えば良いかわかる、という性質があります。これをダックタイピング(duck typing)と呼び、よく次のような格言で言い表されています「もしアヒルのような容姿で、アヒルのように鳴くのであれば、それはもうアヒルだろう」。(訳注: そういえばRubyKaigi 2016の基調講演で、Ruby作者のMatzがダックタイピングについて説明していました。2〜3分の短くて分かりやすい説明なので、ぜひ視聴してみてください!)
> User.all.length
User Load (0.1ms) SELECT "users".* FROM "users"
=> 2
6.1.5 ユーザーオブジェクトを更新する
> user
=> #<User id: 1, name: "Michael Haetl", email: "michael@example.com", created_at: "2021-02-02 08:03:32", updated_at: "2021-02-02 08:03:32">
# userオブジェクトが持つ情報を見る
> user.email = "mhartl@example.com"
=> "mhartl@example.com"
# user.email に新しい情報を代入
> user.save
(0.1ms) SAVEPOINT active_record_1
User Update (0.1ms) UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["email", "mhartl@example.com"], ["updated_at", "2021-02-02 08:10:09.777234"], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
# DBに保存
> user.created_at
=> Tue, 02 Feb 2021 08:03:32 UTC +00:00
> user.updated_at
=> Tue, 02 Feb 2021 08:10:09 UTC +00:00
# マジックカラムも更新されている
updateを使う更新
> user.update(name: "moutoon", email: "moutoon@example")
(0.1ms) SAVEPOINT active_record_1
User Update (0.1ms) UPDATE "users" SET "name" = ?, "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["name", "moutoon"], ["email", "moutoon@example"], ["updated_at", "2021-02-02 08:16:40.831345"], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
# 属性のハッシュを受け取り、trueのときは更新と保存を行う。
> user.name
=> "moutoon"
> user.email
=> "moutoon@example"
> user.update_attribute(:name, "Moooooo!")
(0.1ms) SAVEPOINT active_record_1
User Update (0.1ms) UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ? [["name", "Moooooo!"], ["updated_at", "2021-02-02 08:19:02.068531"], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
> user.name
=> "Moooooo!"
# 特定の属性のみを更新したいときはupdate_attributeを使う。
演習 1
userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください。
> user.name = "moutoon"
=> "moutoon"
> user.save
(0.1ms) SAVEPOINT active_record_1
User Update (0.1ms) UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ? [["name", "moutoon"], ["updated_at", "2021-02-02 08:20:47.547952"], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
演習 2
今度はupdateを使って、email属性を更新および保存してみてください。
> user.update_attribute(:email, "moooo@example.com")
(0.1ms) SAVEPOINT active_record_1
User Update (0.1ms) UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["email", "moooo@example.com"], ["updated_at", "2021-02-02 08:22:17.581047"], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
> user.email
=> "moooo@example.com"
演習 3
同様にして、マジックカラムであるcreated_atも直接更新できることを確認してみてください。ヒント: 更新するときは「1.year.ago」を使うと便利です。これはRails流の時間指定の1つで、現在の時刻から1年前の時間を算出してくれます。
> user.update_attribute(:created_at, 1.year.ago)
(0.1ms) SAVEPOINT active_record_1
User Update (0.2ms) UPDATE "users" SET "created_at" = ?, "updated_at" = ? WHERE "users"."id" = ? [["created_at", "2020-02-02 08:24:02.803323"], ["updated_at", "2021-02-02 08:24:02.803822"], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
> user.created_at
=> Sun, 02 Feb 2020 08:24:02 UTC +00:00
ひとやすみ
まとめながらだと長くなってしまったので、ここで区切ります。
続きは②へ!