##6.1.1 演習
1.Railsはdb/ディレクトリの中にあるschema.rbというファイルを使っています。これはデータベースの構造 (スキーマ (schema) と呼びます) を追跡するために使われます。さて、あなたの環境にあるdb/schema.rbの内容を調べ、その内容とマイグレーションファイル (リスト 6.2) の内容を比べてみてください。
省略
2.ほぼすべてのマイグレーションは、元に戻すことが可能です (少なくとも本チュートリアルにおいてはすべてのマイグレーションを元に戻すことができます)。元に戻すことを「ロールバック (rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。
省略
3.もう一度rails db:migrateコマンドを実行し、db/schema.rbの内容が元に戻ったことを確認してください。
省略
##6.1.2 演習
1.Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください (ヒント: 4.4.4で紹介したテクニックを使ってみてください)。
>> user = User.new
=> #<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)
2.同様にして、ApplicationRecordがActiveRecord::Baseを継承していることについて確認してみてください。
>> user.class.superclass.superclass
=> ActiveRecord::Base
##6.1.3 演習
1.user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください。
>> user.name.class
=> String
>> user.email.class
=> String
2.created_atとupdated_atは、どのクラスのインスタンスでしょうか?
>> user.created_at.class
=> ActiveSupport::TimeWithZone
>> user.updated_at.class
=> ActiveSupport::TimeWithZone
##6.1.4 演習
1.nameを使ってユーザーオブジェクトを検索してみてください。また、 find_by_nameメソッドが使えることも確認してみてください (古いRailsアプリケーションでは、古いタイプのfind_byをよく見かけることでしょう)。
>> User.find_by(name: "Michael Hartl")
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "Michael Hartl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2020-07-04 11:43:14", updated_at: "2020-07-04 11:43:14">
>> User.find_by_name("Michael Hartl")
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "Michael Hartl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2020-07-04 11:43:14", updated_at: "2020-07-04 11:43:14">
2.実用的な目的のため、User.allはまるで配列のように扱うことができますが、実際には配列ではありません。User.allで生成されるオブジェクトを調べ、ArrayクラスではなくUser::ActiveRecord_Relationクラスであることを確認してみてください。
>> User.all.class
=> User::ActiveRecord_Relation
3.User.allに対してlengthメソッドを呼び出すと、その長さを求められることを確認してみてください (4.2.3)。Rubyの性質として、そのクラスを詳しく知らなくてもなんとなくオブジェクトをどう扱えば良いかわかる、という性質があります。これをダックタイピング (duck typing) と呼び、よく次のような格言で言い表されています「もしアヒルのような容姿で、アヒルのように鳴くのであれば、それはもうアヒルだろう」。(訳注: そういえばRubyKaigi 2016の基調講演で、Ruby作者のMatzがダックタイピングについて説明していました。2〜3分の短くて分かりやすい説明なので、ぜひ視聴してみてください!)
>> User.all.length
User Load (0.2ms) SELECT "users".* FROM "users"
=> 2
##6.1.5 演習
1.userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください。
>> user.name = "Muramako"
=> "Muramako"
>> user.save
(0.1ms) SAVEPOINT active_record_1
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
2.今度はupdate_attributesを使って、email属性を更新および保存してみてください。
特定の属性のみを更新したい場合は、次のようにupdate_attributeを使います。の文があるので、こちらを使います。
>> user.update_attribute(:email, "Muramako@example.com")
(0.1ms) SAVEPOINT active_record_1
SQL (0.1ms) UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["email", "Muramako@example.com"], ["updated_at", "2020-07-04 11:59:33.683197"], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
3.同様にして、マジックカラムであるcreated_atも直接更新できることを確認してみてください。ヒント: 更新するときは「1.year.ago」を使うと便利です。これはRails流の時間指定の1つで、現在の時刻から1年前の時間を算出してくれます。
>> user.update_attribute(:created_at, 1.year.ago)
(0.1ms) SAVEPOINT active_record_1
SQL (0.1ms) UPDATE "users" SET "created_at" = ?, "updated_at" = ? WHERE "users"."id" = ? [["created_at", "2019-07-04 12:00:26.106554"], ["updated_at", "2020-07-04 12:00:26.107328"], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
##6.2.1 演習
1.コンソールから、新しく生成したuserオブジェクトが有効 (valid) であることを確認してみましょう。
>> User.new.valid?
=> true
2.6.1.3で生成したuserオブジェクトも有効であるかどうか、確認してみましょう。
>> user.valid?
Traceback (most recent call last):
1: from (irb):2
NameError (undefined local variable or method `user' for main:Object)
Did you mean? super
##6.2.2 演習
1.新しいユーザーuを作成し、作成した時点では有効ではない (invalid) ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。
>> u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
>> u.invalid?
=> true
>> u.errors.full_messages
=> ["Name can't be blank", "Email can't be blank"]
2.u.errors.messagesを実行すると、ハッシュ形式でエラーが取得できることを確認してください。emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか?
自力で分からず。無念。
u.email.errors.messages
u.errors.messages(:email)
等試したけどダメ。
ググった結果正解はu.errors.messages[:email]
惜しい!
find_byの時なんかは、()だと思うんだけど、エラー調べるときは[]なのね(何でだろう)。
##6.2.3 演習
1.長すぎるnameとemail属性を持ったuserオブジェクトを生成し、有効でないことを確認してみましょう。
>> user = User.new(name: "Muramakooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", email: "Muramakooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo@example.com")
=> #<User id: nil, name: "Muramakooooooooooooooooooooooooooooooooooooooooooo...", email: "Muramakooooooooooooooooooooooooooooooooooooooooooo...", created_at: nil, updated_at: nil>
>> user.invalid?
=> true
2.長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。
>> user.errors.messages=> {:name=>["is too long (maximum is 50 characters)"], :email=>["is too long (maximum is 255 characters)"]}
##6.2.4 演習
1.リスト 6.18にある有効なメールアドレスのリストと、リスト 6.19にある無効なメールアドレスのリストをRubularのYour test string:に転記してみてください。その後、リスト 6.21の正規表現をYour regular expression:に転記して、有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
省略
2.先ほど触れたように、リスト 6.21のメールアドレスチェックする正規表現は、foo@bar..comのようにドットが連続した無効なメールアドレスを許容してしまいます。まずは、このメールアドレスをリスト 6.19の無効なメールアドレスリストに追加し、これによってテストが失敗することを確認してください。次に、リスト 6.23で示した、少し複雑な正規表現を使ってこのテストがパスすることを確認してください。
test "email validation should reject invalid addresses" do
invalid_addresses = %w[foo@bar..com 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
REDになった。リスト6.23を反映したらGREENになった。
3.foo@bar..comをRubularのメールアドレスのリストに追加し、リスト 6.23の正規表現をRubularで使ってみてください。有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
省略
##6.2.5 演習
1.リスト 6.33のように、メールアドレスを小文字にするテストをリスト 6.26に追加してみましょう。ちなみに追加するテストコードでは、データベースの値に合わせて更新するreloadメソッドと、値が一致しているかどうか確認するassert_equalメソッドを使っています。リスト 6.33のテストがうまく動いているか確認するためにも、before_saveの行をコメントアウトして redになることを、また、コメントアウトを解除すると greenになることを確認してみましょう。
省略
2.テストスイートの実行結果を確認しながら、before_saveコールバックをemail.downcase!に書き換えてみましょう。ヒント: メソッドの末尾に!を付け足すことにより、email属性を直接変更できるようになります (リスト 6.34)。
省略
##6.3.2 演習
1.この時点では、userオブジェクトに有効な名前とメールアドレスを与えても、valid?で失敗してしまうことを確認してみてください。
>> user = User.new(name: "Muramako", email: "Muramako@example.com")
=> #<User id: nil, name: "Muramako", email: "Muramako@example.com", created_at: nil, updated_at: nil, password_digest: nil>
>> user.valid?
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "Muramako@example.com"], ["LIMIT", 1]]
=> false
2.なぜ失敗してしまうのでしょうか? エラーメッセージを確認してみてください。
>> user.errors.messages
=> {:password=>["can't be blank"]}
##6.3.3 演習
1.有効な名前とメールアドレスでも、パスワードが短すぎるとuserオブジェクトが有効にならないことを確認してみましょう。
>> user = User.new(name: "Muramako", email: "Muramako@example.com", password: "mmm")
=> #<User id: nil, name: "Muramako", email: "Muramako@example.com", created_at: nil, updated_at: nil, password_digest: "$2a$10$/pcBPDq8e44IQ/8vLxMR3e3xVyMOOk.G35K.yCFFEWO...">
>> user.invalid?
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "Muramako@example.com"], ["LIMIT", 1]]
=> true
2.上で失敗した時、どんなエラーメッセージになるでしょうか? 確認してみましょう。
>> user.errors.messages
=> {:password=>["is too short (minimum is 6 characters)"]}
##6.3.4 演習
1.コンソールを一度再起動して (userオブジェクトを消去して)、このセクションで作ったuserオブジェクトを検索してみてください。
user = User.find_by(id:1)
2.オブジェクトが検索できたら、名前を新しい文字列に置き換え、saveメソッドで更新してみてください。うまくいきませんね...、なぜうまくいかなかったのでしょうか?
>> user.email = "mhartl@example.jp"
=> "mhartl@example.jp"
>> user.save
(0.1ms) begin transaction
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ? [["email", "mhartl@example.jp"], ["id", 1], ["LIMIT", 1]]
(0.0ms) rollback transaction
=> false
>> user.errors.messages
=> {:password=>["can't be blank", "is too short (minimum is 6 characters)"]}
3.今度は6.1.5で紹介したテクニックを使って、userの名前を更新してみてください。
>> user.update_attribute(:email, "mhartl@example.jp")
(0.1ms) begin transaction
SQL (2.0ms) UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["email", "mhartl@example.jp"], ["updated_at", "2020-07-04 13:29:40.208038"], ["id", 1]]
(6.7ms) commit transaction
=> true
##謎のエラー
$ rails test (GREEN)
$ git add -A
$ git commit -m "Make a basic User model (including secure passwords)"
$ git checkout master
$ git merge modeling-users
$ git push
$ rails test
ここでRED。さっきはGREENだったのに、なぜ?
You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle instal
Gemfileに、gem 'bcrypt', '3.1.12'
はあるのに。
bundle install
→rails t
でもやっぱりRED。
ググってみたら、一度Gemfileのgem 'bcrypt', '3.1.12'
をコメントアウト→bundle install
→コメント解除→bundle install
これでGREENに。なぜなのかは分からないけれど。
無事GREENになったのでherokuへpush!
ここでまたエラー!
$ heroku run rails db:migrate
bash: heroku: command not found
ググった。
heroku command not found の対処に載っているコードを入力。
うまくいきました。ありがたや。
##メモ
- コントローラ名には複数形を使い、モデル名には単数形を用いるという慣習がある。(例)コントローラはUsersでモデルはUser。
-
dup
は、同じ属性を持つデータを複製するためのメソッド。 -
case_sensitive: false
は大文字小文字を区別するかどうかを指定するオプション。 - マイグレーション名に
to_users
をつけると、usersテーブルにカラムを追加するマイグレーションがRailsによって自動的に作成される。 -
has_secure_password
を追加すると、そのオブジェクト内でauthenticate
メソッドが使えるようになる。このメソッドは、引数に渡された文字列 (パスワード) をハッシュ化した値と、データベース内にあるpassword_digestカラムの値を比較する。