Ruby on Railsチュートリアル第6章
第6章(ユーザーのモデルを作成する)
Userモデル
モデルの作成
rails g
でモデルを作成。モデルは単数形。db:migrate
も実行。
$ rails generate model User name:string email:string $ rails db:migrate
モデル生成の際には同時にマイグレーションファイルも生成される。
create_table
メソッドを呼び、テーブル名はデータの集まりなので複数形のusers。
class CreateUsers < ActiveRecord::Migration[7.0] def change create_table :users do |t| t.string :name t.string :email t.timestamps end end end
カラム名 | データ型 |
---|---|
id | integer |
name | string |
string | |
created_at | datetime |
updated_at | datetime |
Active Recordの操作(作成、保存、更新、削除)
User.new
で新規ユーザーオブジェクトをメモリ上に作成。Active Recordの設計で、引数無しの場合はnil。valid
メソッドで有効性の検証が可能。有効かどうかなだけで、DBへ保存されていることを確認する訳ではない。save
メソッドでデータベースへ保存。この際にモデルに対応するid属性とtimestampも変更される。戻り値は真偽値。create
はnew
とsave
を兼ねたメソッド。戻り値は真偽値ではなく、オブジェクト自身を返す。create
の逆はdestory
。削除されたオブジェクトはメモリ上には残っている。save
の前であれば、データベースの情報を元にreload
でオブジェクトを再読み込み可能。- 更新と保存を一括でおこなう操作が
update
。更にupdate_attribute
は特定の属性のみであればバリデーションにかからず更新が可能。
Active Recordの操作(検索)
検索で使用される、find、find_by、whereの違いについてまとめておきます。
User.find
は引数に指定したidのユーザーを探す。idが分かっている場合に使用。該当するidのユーザーがいない場合は例外ActiveRecord::RecordNotFoundが発生する。User.find_by
は引数に単一の属性を指定してユーザーを検索する。idもしくはid以外が分かっている場合(メールアドレスなど)で使用。返ってくる結果は最初の1件のみ。該当するユーザーがいない場合はnilが返ってくる。where
は引数に一つ以上の属性を指定してユーザーを検索する。また、該当する全ての結果。また、find_by
と微妙に違う点として戻り値の形式が異なっており、where
の結果に対して追加でクエリ出来る。
ユーザーの検証
有効性の検証
valid?
メソッドを使用。
[test/models/user_test.rb] require "test_helper" class UserTest < ActiveSupport::TestCase #@userを使えるようにsetupメソッドを実行 def setup @user = User.new(name: "Example User", email: "user@example.com") end #ユーザーの有効性を検証 test "should be valid" do assert @user.valid? end end
存在性の検証
名前が存在していることを検証するためのテストを先に書く。
[test/models/user_test.rb] #ユーザーの存在性を検証 test "name should be present" do @user.name = ' ' assert_not @user.valid? end
ユーザーモデルにnameの存在性バリデーションを追加。
validates :name, presence: true
の書き方は、validatesメソッドに対して属性として:name
、オプションとしてpresence: true
を渡している。presence: true
はハッシュであるが、最後の引数のため{}は省略が可能。
[app/models/user.rb] class User < ApplicationRecord validates :name, presence: true end
emailの存在性検証も全く同じ。
長さの検証
名前は50文字、メールアドレスはStringの上限の255文字としてバリデーションを設定。
まずはテストを記述する。
[test/models/user_test.rb] #ユーザー名の長さを検証 test "name length" do @user.name = 'a' * 51 assert_not @user.valid? end #メールアドレスの長さを検証 test "email length" do @user.email = 'a' * 256 assert_not @user.valid? end
モデルに長さのバリデーションを追加。
[app/models/user.rb] class User < ApplicationRecord validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 } end
ハッシュの省略について
ここの書き方がだいぶ混乱したのでまとめておきます。
ハッシュには、最後の引数である場合はハッシュの{}を省略可能という法則があるが、validates :name, presence: true, length: { maximum: 50 }
は、第1引数が:name
、第2引数がpresence: true
、第3引数がlength: { maximum: 50 }
となるため、第2引数のpresence: true
には{}が必要なのでは無いかと思いました。
ただ、よく考えるとvalidates( :name, {presence: true, length: { maximum: 50 }}
という記述になるので、第1引数は:name
の属性で、第2引数がオプションハッシュの{presence: true, length: { maximum: 50 }}
になるということですね。(多分)
この記述は4章のstylesheet_link_tag "application", "data-turbo-track": "reload"
の書き方と全く同じとなりますね。特にこれまで気にせず読んでいませんでしたが、改めて考えるとrubyの省略記法には慣れが必要そうです。
フォーマットの検証
まずはメールアドレスに対するテストを記述。
[test/models/user_test.rb] #メールアドレスのフォーマット検証、正しいメールアドレス test "email validation should accept valid addresses" do valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org first.last@foo.jp alice+bob@baz.cn] valid_addresses.each do |valid_address| @user.email = valid_address assert @user.valid?, "#{valid_address.inspect} should be valid" end end #メールアドレスのフォーマット検証、不正なメールアドレス test "email validation should reject invalid addresses" do invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com] invalid_addresses.each do |invalid_address| @user.email = invalid_address assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" end end
メールフォーマットに対するバリデーションを追加。正規表現を使用。VALID_EMAIL_REGEXは定数。
正規表現を試せるwebサイトのRubular(https://rubular.com/)を使用するとよい。
[app/models/user.rb] class User < ApplicationRecord validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } end
一意性の検証
メールアドレスの一意性検証に関する前提
一意性をテストする際は、メモリ上だけでなく、実際にデータベースへレコードを登録している必要がある。そのためメソッドにはsave
やupdate
を加える。
メールアドレスは大文字と小文字は区別せず、全て小文字として扱うことが通例。
メールアドレスの重複についてテスト。dup
メソッドではオブジェクトのコピーを作成可能。
test "email addresses should be unique" do duplicate_user = @user.dup #dupメソッドでコピーを作成。 @user.save #@userをDBへ保存。 assert_not duplicate_user.valid? end
モデルに一意性のバリデーションを追加。uniqueness: { case_sensitive: false }
とすることで、大文字と小文字を区別しない検証が可能。
[app/models/user.rb] class User < ApplicationRecord validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } end
データベースのレベルでは一意性を保証していない問題
モデルに一意性のバリデーションを追加した場合、基本的には重複したメールアドレスはバリデーションで弾かれるが、同時に複数のリクエストが来た場合に、複数のリクエストが同時に同じ値を返してしまいバリデーションエラーとならずに重複してデータベースに保存される可能性がある。
emailカラムにインデックス(index)を追加し、そのインデックスに一意性の制約を付けるだけで解決可能。
更にindexは索引機能のようなもので、データベースに対する全表スキャンの対策にもなる。
マイグレーションファイルを生成(rails g migration
)し、add_index
メソッドでユーザーのemailに対して一意性を制約する。
[db/migrate/[timestamp]_add_index_to_users_email.rb] class AddIndexToUsersEmail < ActiveRecord::Migration[7.0] def change add_index :users, :email, unique: true end end
これまでのテストの時点では、データベース間での一意性検証は無かったためテストパスしていたが、テスト用データfixtureを編集しておく。
また、実質的にデータベースレベルでメールアドレスの一意性を保つためには、そもそもメールアドレスは全て小文字として扱う方が理にかなっている。そのため、before_save
でdowncase
化しておく。
class User < ApplicationRecord before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true end
セキュアなパスワード
ユーザーの認証は、パスワードの送信→ハッシュ化→データベース内のハッシュ化された値との比較、という手順でおこなわれる。
ハッシュ化は、暗号化とが類似しているが明確に異なる。暗号化は元に戻す複合化が可能であるが、ハッシュ化は元に戻せず不可逆的な処理(∵ソルト化(ランダム)ハッシュを追加している)。
has_secure_password
- ハッシュ化したパスワードを、password_digest属性に保存できる。
- 2つの仮想的な属性(passwordとpassword_confirmation)が使える。(データベースカラムに存在はしていない)
- authenticateメソッド(引数のパスワードをハッシュ化した値とpassword_digestを比較)が使える。真偽値を返し、trueであればオブジェクトも返す。
*パスワードのハッシュ化には
bcrypt
gemを使用する必要がある。
password_digest
をカラムに追加するマイグレーションを生成する。末尾をto_usersとすることでテーブルが認識出来る。
$ rails generate migration add_password_digest_to_users password_digest:string
モデルにhas_secure_password
を追加する。
[app/models/user.rb] class User < ApplicationRecord before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true has_secure_password end
has_secure_password
の仮想的なデータ属性であるpasswordとpassword_confirmationをtest userに追加しておく。
[test/models/user_test.rb] def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end
パスワードの最小文字数設定
空で無いことと、6文字以上であることをテスト。ここの空白はただの空パスワードでは無く、空文字が数文字分ある。(has_secure_password自体が完全な空文字に対する存在性のバリデーションが付いている。)
#パスワードが空白でないことを検証 test "password should be present (nonblank)" do @user.password = @user.password_confirmation = " " * 6 assert_not @user.valid? end #パスワードが空6文字以上であることを検証 test "password should have a minimum length" do @user.password = @user.password_confirmation = "a" * 5 assert_not @user.valid? end
emailのときと同様に、存在性と文字数のバリデーションをモデルに追加。
[app/models/user.rb] validates :password, presence: true, length: { minimum: 6 }
第6章ー演習ー
6.1.1
①Railsはdb/ディレクトリの中にあるschema.rbというファイルを使っています。これはスキーマ(schema)と呼ばれるデータベースの構造を追跡するために使われます。そこで、自分の環境にあるdb/schema.rbの内容を調べ、その内容とマイグレーションファイル(リスト 6.2)の内容を比べてみてください。
[db/schema.rb] ActiveRecord::Schema[7.0].define(version: 2023_04_14_080953) do create_table "users", force: :cascade do |t| t.string "name" t.string "email" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end
②ほぼすべてのマイグレーションは、元に戻すことが可能です(少なくとも本チュートリアルにおいてはすべてのマイグレーションを元に戻すことができます)。元に戻すことを「ロールバック(rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。
$ rails db:rollback
上のコマンドを実行後、db/schema.rbの内容を調べてみて、ロールバックが成功したかどうか確認してみてください(コラム 3.1ではマイグレーションに関する他のテクニックもまとめているので、参考にしてみてください)。なお上のコマンドでは、データベースからusersテーブルを削除するためにdrop_tableコマンドを内部で呼び出しています。これがうまくいくのは、drop_tableとcreate_tableがそれぞれ対応していることをchangeメソッドが知っているからです。この対応関係を知っているため、ロールバック用の逆方向のマイグレーションを簡単に実現することができるのです。なお、あるカラムを削除するような不可逆なマイグレーションの場合は、changeメソッドの代わりに、upとdownのメソッドを別々に定義する必要があります。詳細については、Railsガイドの「Active Record マイグレーション」を参照してください。
[db/schema.rb] ActiveRecord::Schema[7.0].define(version: 0) do end
③もう一度rails db:migrateコマンドを実行し、db/schema.rbの内容が元に戻ったことを確認してください。
6.1.2
①Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください。(ヒント: 4.4.4で紹介したテクニックを使ってみてください。)
②同様の方法で、ApplicationRecordがActiveRecord::Baseを継承していることも確認してみてください。
irb(main):001:0> user = User.new => #<User:0x00007f014e8145d8 id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> irb(main):002:0> user.class => User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime) irb(main):003:0> user.class.superclass => ApplicationRecord(abstract) irb(main):004:0> user.class.superclass.superclass => ActiveRecord::Base
6.1.3
①user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください。
irb(main):005:0> user.name.class => String irb(main):007:0> user.email.class => String
②created_atとupdated_atは、どのクラスのインスタンスでしょうか?
irb(main):008:0> user.created_at.class => ActiveSupport::TimeWithZone irb(main):009:0> user.updated_at.class => ActiveSupport::TimeWithZone
6.1.4
①nameを使ってユーザーオブジェクトを検索してみてください。また、find_by_nameメソッドが使えることも確認してみてください(これはfind_byの古い書き方で、古いRailsアプリケーションでよく見かけられます)。
irb(main):012:0> User.find_by(name: 'Taro Yamda') User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "Taro Yamda"], ["LIMIT", 1]] => nil irb(main):013:0> User.find_by_name('Taro Yamda') User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "Taro Yamda"], ["LIMIT", 1]] => nil
②実用的な目的のため、User.allはまるで配列のように扱うことができますが、実際には配列ではありません。User.allで生成されるオブジェクトを調べ、ArrayクラスではなくUser::ActiveRecord_Relationクラスであることを確認してみてください。
irb(main):015:0> User.all.class => User::ActiveRecord_Relation
③User.allに対してlengthメソッドを呼び出すと、その長さ(データの件数)を求められることを確認してみてください(4.2.2)。なおRubyには、そのクラスを詳しく知らなくてもオブジェクトをどう扱えば良いか何となく見当がつく、という特徴があります。これはダックタイピング(duck typing)と呼ばれ、よく次のような格言で言い表されています「もしアヒルのような容姿で、アヒルのように鳴くのであれば、それはもうアヒルだろう」10 。
irb(main):016:0> User.all.length User Load (0.2ms) SELECT "users".* FROM "users" => 1
6.1.5
①userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください。
(解答)
irb(main):017:0> user.name = 'Ichiro Sato' => "Ichiro Sato" irb(main):018:0> user => #<User:0x00007fba0cbe4858 id: 1, name: "Ichiro Sato", email: "t.yamada@example.com", created_at: Fri, 14 Apr 2023 08:40:55.614896000 UTC +00:00, updated_at: Fri, 14 Apr 2023 08:40:55.614896000 UTC +00:00> irb(main):019:0> user.save TRANSACTION (0.1ms) SAVEPOINT active_record_1 User Update (0.3ms) UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ? [["name", "Ichiro Sato"], ["updated_at", "2023-04-14 09:17:36.928435"], ["id", 1]] TRANSACTION (0.2ms) RELEASE SAVEPOINT active_record_1 => true
②今度はupdateを使って、email属性を更新および保存してみてください。
(解答)
irb(main):020:0> user.update(email: 'i.sato@example.com') TRANSACTION (0.2ms) SAVEPOINT active_record_1 User Update (0.3ms) UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["email", "i.sato@example.com"], ["updated_at", "2023-04-14 09:18:59.317175"], ["id", 1]] TRANSACTION (0.2ms) RELEASE SAVEPOINT active_record_1 => true irb(main):021:0> user => #<User:0x00007fba0cbe4858 id: 1, name: "Ichiro Sato", email: "i.sato@example.com", created_at: Fri, 14 Apr 2023 08:40:55.614896000 UTC +00:00, updated_at: Fri, 14 Apr 2023 09:18:59.317175000 UTC +00:00>
③同様に、マジックカラムであるcreated_atも直接更新できることを確認してみてください。(ヒント: 1.year.agoで更新すると便利です。これはRails流の時間指定の1つで、現在の時刻から1年前の日時を算出してくれます。)
(解答)確かに1年前になった。
irb(main):022:0> user.update(created_at: 1.years.ago) TRANSACTION (0.1ms) SAVEPOINT active_record_1 User Update (0.2ms) UPDATE "users" SET "created_at" = ?, "updated_at" = ? WHERE "users"."id" = ? [["created_at", "2022-04-14 09:20:09.696234"], ["updated_at", "2023-04-14 09:20:09.703575"], ["id", 1]] TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 => true irb(main):023:0> user => #<User:0x00007fba0cbe4858 id: 1, name: "Ichiro Sato", email: "i.sato@example.com", created_at: Thu, 14 Apr 2022 09:20:09.696234000 UTC +00:00, updated_at: Fri, 14 Apr 2023 09:20:09.703575000 UTC +00:00>
6.2.1
①Railsコンソールを開いて、新しく生成したuserオブジェクトが有効(valid)であることを確認してみましょう。
(解答)
irb(main):024:0> user.valid? => true
②6.1.3で生成したuserオブジェクトについても、有効かどうかを確認してみましょう。
(解答) 作っていないのでパス。
6.2.2
①nameもemailも空の新しいユーザーuを作成し、作成した時点では有効ではない(invalid)ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。
(解答) nameもemailも空だとバリデーションエラー。
irb(main):001:0> u = User.new TRANSACTION (0.9ms) begin transaction => #<User:0x00007f3b35fdf3c8 id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> irb(main):002:0> u.valid? => false irb(main):003:0> u.errors.messages => {:name=>["can't be blank"], :email=>["can't be blank"]}
②u.errors.messagesを実行すると、ハッシュ形式でエラーが取得できることを確認してください。emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか?
(解答) シンボルで指定。
irb(main):003:0> u.errors.messages => {:name=>["can't be blank"], :email=>["can't be blank"]} irb(main):004:0> u.errors.messages[:email] => ["can't be blank"]
6.2.3
①長すぎるname属性とemail属性を持つuserオブジェクトを生成し、有効でないことを確認してみましょう。
(解答) 有効でない。
irb(main):001:0> u = User.new TRANSACTION (0.1ms) begin transaction => #<User:0x00007fdbca71a608 id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> irb(main):002:0> u.name = 'a' * 55 => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" irb(main):003:0> u.email = 'a' * 260 => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... irb(main):004:0> u.valid? => false
②長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。
(解答) 文字数制限のエラーメッセージが生成。
irb(main):005:0> u.errors.messages => {:name=>["is too long (maximum is 50 characters)"], :email=>["is too long (maximum is 255 characters)"]}
6.2.4
①リスト 6.18にある有効なメールアドレスのリストと、リスト 6.19にある無効なメールアドレスのリストをRubularのYour test string:に転記してみてください。その後、リスト 6.21の正規表現をYour regular expression:に転記して、有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
(解答) 確認作業なので飛ばします。
②先ほど触れたように、リスト 6.21のメールアドレスチェックする正規表現は、foo@bar..comのようにドットが連続した無効なメールアドレスを許容してしまいます。まずは、このメールアドレスをリスト 6.19の無効なメールアドレスリストに追加し、これによってテストが失敗することを確認してください。次に、リスト 6.23で示した、少し複雑な正規表現を使ってこのテストがパスすることを確認してください。
(解答)
追加すると確かに失敗する。=有効なメールアドレスとして認識されている
FAIL UserTest#test_email_validation_should_reject_invalid_addresses (0.06s) "foo@bar..com" should be invalid test/models/user_test.rb:55:in `block (2 levels) in <class:UserTest>' test/models/user_test.rb:53:in `each' test/models/user_test.rb:53:in `block in <class:UserTest>'
正規表現VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
を追加するとパスした。
③foo@bar..comをRubularのメールアドレスのリストに追加し、リスト 6.23の正規表現をRubularで使ってみてください。有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
(解答) 確認作業なので飛ばします。
6.2.5
①リスト 6.34のように、メールアドレスを小文字で保存するテストをリスト 6.26に追加してみましょう。ちなみに追加するテストコードでは、データベースの値に合わせて更新するreloadメソッドと、値が一致しているかどうか確認するassert_equalメソッドを使っています。リスト 6.34のテストがうまく動いているか確認するために、before_saveの行をコメントアウトすると red になり、コメントアウトを解除すると green になることも確認してみましょう。
(解答) before_saveを外すと以下のように小文字化されずにテスト失敗となった。
FAIL UserTest#test_email_addresses_should_be_saved_as_lowercase (0.07s) Expected: "foo@example.com" Actual: "Foo@ExAMPle.CoM" test/models/user_test.rb:71:in `block in <class:UserTest>'
②テストスイートの実行結果を確認しながら、before_saveコールバックをemail.downcase!に書き換えてみましょう(リスト 6.35)。(ヒント: メソッドの末尾に!を追加すると、email属性が直接変更されます。)
(解答) この記述でも変わらずうまくいく。
6.3.2
①この時点では、userオブジェクトに有効な名前とメールアドレスを与えても、valid?で失敗してしまうことを確認してみてください。
②なぜ失敗してしまうのでしょうか? エラーメッセージを確認してみてください。
(解答) ①、②合わせて。パスワードが未設定のため失敗する。
irb(main):001:0> user = User.new(name: 'Jiro Tanaka', email: 'j.tanaka@example.com') TRANSACTION (0.5ms) begin transaction => #<User:0x00007fb861334a70 ... irb(main):002:0> user.valid? User Exists? (2.3ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "j.tanaka@example.com"], ["LIMIT", 1]] => false irb(main):003:0> user.errors.messages => {:password=>["can't be blank"]}
6.3.3
①名前とメールアドレスは有効でも、パスワードが短すぎるとuserオブジェクトが有効にならないことを確認してみましょう。
②上で失敗した時、どんなエラーメッセージになるでしょうか? 確認してみましょう。
(解答) パスワード短すぎ。
irb(main):001:0> user = User.new( name: 'Jiro Tanaka', email: 'j.ranaka@example.com', password: 'foo') TRANSACTION (0.1ms) begin transaction => #<User:0x00007f5f50f93678 ... irb(main):002:0> user.valid? User Exists? (1.2ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "j.ranaka@example.com"], ["LIMIT", 1]] => false irb(main):003:0> user.errors.messages => {:password=>["is too short (minimum is 6 characters)"]}
6.3.4
①userオブジェクトを消去するためにコンソールを一度再起動し、本節で作ったuserオブジェクトを検索してみてください。
(解答)
irb(main):001:0> User.all User Load (0.2ms) SELECT "users".* FROM "users" => [#<User:0x00007fe5bbbcaae0 id: 1, name: "Michael Hartl", email: "michael@example.com", created_at: Fri, 14 Apr 2023 16:18:32.698774000 UTC +00:00, updated_at: Fri, 14 Apr 2023 16:18:32.698774000 UTC +00:00, password_digest: "[FILTERED]">]
②オブジェクトを検索できたら、名前を別の文字列に置き換え、saveメソッドで更新してみてください。うまくいきませんね...、なぜうまくいかなかったのでしょうか?
(解答) パスワードが必要。
irb(main):003:0> user.name = 'Taro Yamada' => "Taro Yamada" irb(main):004:0> user.save TRANSACTION (0.2ms) begin transaction User Exists? (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? AND "users"."id" != ? LIMIT ? [["email", "michael@example.com"], ["id", 1], ["LIMIT", 1]] TRANSACTION (0.1ms) rollback transaction => false irb(main):005:0> user.errors.messages => {:password=>["can't be blank", "is too short (minimum is 6 characters)"]}
③今度は6.1.5で紹介したテクニックを使って、userの名前を更新してみてください。
(解答) これならtrueで変更出来た。
irb(main):007:0> user.update_attribute(:name, 'Taro Yamada') TRANSACTION (0.2ms) begin transaction User Update (1.8ms) UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ? [["name", "Taro Yamada"], ["updated_at", "2023-04-14 16:30:58.847096"], ["id", 1]] TRANSACTION (13.6ms) commit transaction => true
第6章ーまとめー
- ActiveRecordモデルのnew操作はあくまで新しいオブジェクトをメモリ上に作成するだけ。データベースには保存されない。
- ActiveRecordの検索は、
find
、find_by
、where
の3種類。 - メールアドレスは大文字と小文字は区別せず、全て小文字として扱うのが通例。
- ActiveRecordは通常データベースのレベルでの一意性保証はしていない。そのため、indexを付けてindexに一意性を持たせる。indexは索引機能としても役立つ。
- has_secure_passwordで安全なパスワード(不可逆的にハッシュ化されたパスワード)が追加される。
2周目時点では曖昧理解で飛ばしていたデータベースの一意性制約などがやっとクリアになった。。。
第6章終わり🐻