1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Railsチュートリアル】第6章 ユーザーのモデルを作成する①

Posted at

第6章 ユーザーのモデルを作成する

本章では、一番重要なステップであるユーザー用のデータモデルの作成と、データを保存する手段の確保について学んできます。

6.1 Userモデル

ユーザーの情報を保存するためのデータ構造を作成する。

  • モデル(Model)
    Railsでは、データモデルとして扱うデフォルトのデータ構造のことをモデルと呼ぶ。MVCのM。

  • Active Record
    データベースとやりとりをするデフォルトのRailsライブラリ。データオブジェクトの作成/保存/検索のためのメソッドを持っている。

  • マイグレーション(Migration)
    データの定義をRubyで記述することができるRailsの機能。

6.1.1 データベースの移行

nameとemailの2つの属性からなるユーザーをモデリングする。

terminal
$ rails generate model User name:string email:string

Controller名には複数形、Model名には単数形を使う習慣がある。

name:stringやemail:stringオプションのパラメータを渡すことによって、データベースで使いたい2つの属性をRailsに伝える。

generateコマンドによってマイグレーションという新しいファイルが生成され、nameとemailの2つのカラムを持つusersテーブルが作られる。

マイグレーションの適用を忘れずに。

terminal
$ rails db:migrate

演習 1

Railsはdb/ディレクトリの中にあるschema.rbというファイルを使っています。これはデータベースの構造(スキーマ(schema)と呼びます)を追跡するために使われます。さて、あなたの環境にあるdb/schema.rbの内容を調べ、その内容とマイグレーションファイル(リスト 6.2)の内容を比べてみてください。

_create_user.rb

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
schema.rb

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というコマンドで実現できます。

schema.rb

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を継承していることについて確認してみてください。

terminal
$ rails console
terminal
> 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を変更したくない場合はコンソールをサンドボックスモードで起動する。データベースへの変更をコンソールの終了時にすべて “ロールバック”(取り消し)される。

terminal
$ rails console --sandbox

新しいユーザーオブジェクトを生成(引数が無いとすべての属性がnilオブジェクトを返す)

terminal
> User.new
(0.1ms)  begin transaction
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

引数にオブジェクトの属性を設定する。

terminal
> 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)を確認する

terminal
> user.valid?
 => true

データベースにUserオブジェクトを保存する

terminal
> 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メソッドの実行したあと

terminal
> 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は更新した日時
# ドット記法でアクセスができる。
terminal
> user.name
 => "Michael Haetl" 
> user.email
 => "michael@example.com" 
> user.updated_at
 => Tue, 02 Feb 2021 07:08:01 UTC +00:00

User.create でモデルの生成と保存を同時に行うことができる

terminal
> 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: オブジェクトを削除する。まだメモリ上には残っている。

terminal
> 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クラスのインスタンスであることを確認してみてください。

terminal
user.name.class
 => String

> user.email.class
 => String

演習 2

terminal
> 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)

terminal
> 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

ひとやすみ

まとめながらだと長くなってしまったので、ここで区切ります。
続きは②へ!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?