【146日目】【1日20分のRailsチュートリアル】【第11章】UserモデルとMicropostモデルの関連付け
今日は「11.1.3 User/Micropostの関連付け」から。
11.1.3 User/Micropostの関連付け
それぞれのマイクロポストは1人のユーザーと関連付けられ、それぞれのユーザーは (潜在的に) 複数のマイクロポストと関連付けられます。
ふむふむ。
UserとそのMicropostは1対多の関係、と。
これらのメソッドは使うと、紐付いているユーザーを通してマイクロポストを作成することができます (慣習的に正しい方法です)。
なんか不思議…!でもWebアプリケーションの作りには合ってる感じする。
前回の記事の「慣習的に間違っている」行はこのことらしい。
Micropostモデルの方では、belongs_to :userというコードが必要になるのですが、これは リスト11.1のマイグレーションによって自動的に生成されているはずです (リスト11.9)。 一方、Userモデルの方では、has_many :micropostsと追加する必要があります。ここは自動的に生成されないので、手動で追加してください (リスト11.10)。
Micropostモデルは下記コードが元々生成されていることを確認。
app/models/micropost.rb
class Micropost < ActiveRecord::Base belongs_to :user : end
Userモデルは自動的に生成されないので下記コードを追加。
app/models/user.rb
class User < ActiveRecord::Base has_many :microposts : end
正しく関連付けができたら、リスト11.2のsetupメソッドを修正して、慣習的に正しくマイクロポストを作成してみます (リスト11.11)。
慣習的云々のコードを下記のように修正。
test/models/micropost_test.rb
@micropost = @user.microposts.build(content: "Lorem ipsum")
テストも問題なし。
$ bundle exec rake test 52 tests, 221 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【17分】。
次は「11.1.4 マイクロポストを改良する」から。
【145日目】【1日20分のRailsチュートリアル】【第11章】Micropostモデルのバリデーション
今日は「11.1.2 Micropostのバリデーション」から。
11.1.2 Micropostのバリデーション
Micropostの初期テストはUserモデルの初期テスト (リスト6.7) と似ています。
まずはMicropostモデル単体のテストを作成する。
test/models/micropost_test.rb
require 'test_helper' class MicropostTest < ActiveSupport::TestCase def setup @user = users(:michael) # 次の行は慣習的に間違っている @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id) end test "should be valid" do assert @micropost.valid? end test "user id should be present" do @micropost.user_id = nil assert_not @micropost.valid? end end
慣習的に間違っているコードは後で修正するそう。
また、有効性に対するテストは成功しますが、存在性に対するテストは失敗するはずです。これは、Micropostモデルのバリデーションがまだ何もないことが原因です。
"test_user_id_should_be_present"
のテストで失敗しますね。
$ bundle exec rake test:models FAIL["test_user_id_should_be_present", MicropostTest, 2017-05-26 15:23:17 +0000] test_user_id_should_be_present#MicropostTest (1495812197.48s) Expected true to be nil or false test/models/micropost_test.rb:17:in `block in <class:MicropostTest>' 14 tests, 23 assertions, 1 failures, 0 errors, 0 skips
これを修正するためには、ユーザーidに対するバリデーションを追加する必要があります (リスト11.4)。
Micropostモデルにバリデーションを追加。
app/models/micropost.rb
class Micropost < ActiveRecord::Base belongs_to :user validates :user_id, presence: true end
これでテストが通るようになりました。
$ bundle exec rake test:models 14 tests, 23 assertions, 0 failures, 0 errors, 0 skips
user_id属性と同様に、content属性も存在する必要があり、さらにマイクロポストが140文字より長くならないよう制限を加えます。
content属性とマイクロポストの制限に関するテストを追加。
test/models/micropost_test.rb
test "content should be present" do @micropost.content = " " assert_not @micropost.valid? end test "content should be at most 140 characters" do @micropost.content = "a" * 141 assert_not @micropost.valid? end
バリデーション追加してないのでテストは失敗する。
$ bundle exec rake test:models FAIL["test_content_should_be_at_most_140_characters", MicropostTest, 2017-05-26 15:23:17 +0000] test_content_should_be_at_most_140_characters#MicropostTest (1495812197.13s) Expected true to be nil or false test/models/micropost_test.rb:27:in `block in <class:MicropostTest>' FAIL["test_content_should_be_present", MicropostTest, 2017-05-26 15:23:17 +0000] test_content_should_be_present#MicropostTest (1495812197.13s) Expected true to be nil or false test/models/micropost_test.rb:22:in `block in <class:MicropostTest>' 16 tests, 25 assertions, 2 failures, 0 errors, 0 skips
これに対応するアプリケーション側の実装は、Userのname用バリデーション (リスト6.16) と全く同じです。
Userモデルと同じようなバリデーションをMicropostモデルに追加。
app/models/micropost.rb
validates :content, presence: true, length: { maximum: 140 }
これでテストは通るようになりました。
$ bundle exec rake test:models 16 tests, 25 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【18分】。
次は「11.1.3 User/Micropostの関連付け」から。
【144日目】【1日20分のRailsチュートリアル】【第11章】Micropostモデルを生成する
今日は「第11章 ユーザーのマイクロポスト」から。ようやく第11章…!
第11章 ユーザーのマイクロポスト
全ての準備が整った今、ユーザーが短いメッセージを投稿できるようにするためのリソース「マイクロポスト」を追加していきます。
Twitterみたいなアプリケーションにするそう。
11.1 Micropostモデル
Git をバージョン管理に使っている場合は、いつものようにトピックブランチを作成しておきましょう。
作成しておきましょう。
$ git checkout master $ git checkout -b user-microposts
11.1.1 基本的なモデル
Micropostモデルは、マイクロポストの内容を保存するcontent属性と、特定のユーザーとマイクロポストを関連付けるuser_id属性の2つの属性だけを持ちます。
基本的な属性はこの2つらしい。
図11.1のモデルでは、マイクロポストの投稿にString型ではなくtext型を使っている点に注目してください。
文字列を扱うにも色んな型があるんだな…。
リスト6.1でUserモデルを生成したときと同様に、Railsのgenerate modelコマンドを使ってMicropostモデルを生成してみます。
Micropostモデルを生成してみました。
$ rails generate model Micropost content:text user:references invoke active_record create db/migrate/20170525023044_create_microposts.rb create app/models/micropost.rb invoke test_unit create test/models/micropost_test.rb create test/fixtures/microposts.yml
このgenerateコマンドはmicropostsテーブルを作成するためのマイグレーションファイルを生成します。
マイグレーションファイル生成されてますね。
user_idとcreated_at両方のカラムを1つの配列に含めることで、Active Recordで両方のキーを同時に使用する複合キーインデックスを作成できます。
うーん、データベースから検索しやすくするため、って感じかな。。。たぶん
下記をマイグレーションファイルに追加することでuser_idとcreated_atカラムにインデックスが付与できる?っぽい。
db/migrate/[timestamp]_create_microposts.rb
add_index :microposts, [:user_id, :created_at]
マイグレーションしてデータベースを更新。
$ bundle exec rake db:migrate
:
-- create_table(:microposts)
-- add_index(:microposts, [:user_id, :created_at])
:
今日の学習時間は【23分】。
次は「11.1.2 Micropostのバリデーション」から。
【143日目】【1日20分のRailsチュートリアル】【第10章】演習の3.と期限切れの比較の証明
今日は「10.5 演習」の3.から。
10.5 演習
3.リスト10.42では、activateメソッドとcreate_reset_digestメソッドの両方でupdate_attributeを呼び出しており、それぞれのアクセスによってデータベーストランザクションが個別に発生してしまう点が残念です。
リスト10.59のテンプレートに記入することで、個別のupdate_attribute呼び出しを単一のupdate_columns呼び出しに統合し、データベースアクセスが1回で済むようにしてください。
変更後にテストを実行し、GREENになることを確認してください。
2行で書いてるupdate_attribute(=2回データベースアクセスが発生する)をupdate_columnsで1回のデータベースアクセスにしよう、ってことか。
まとめるだけだから引数は変えなくて問題ないかな。
app/models/user.rb
class User < ActiveRecord::Base : # アカウントを有効にする def activate update_columns(activated: true, activated_at: Time.zone.now) end : # パスワード再設定の属性を設定する def create_reset_digest self.reset_token = User.new_token update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now) end
テスト実行して問題ないことを確認。
$ bundle exec rake test 48 tests, 217 assertions, 0 failures, 0 errors, 0 skips
演習終わり!
10.6 証明: 期限切れの比較
この説では、10.2.4で用いたパスワード期限切れの期間の比較が正しいことを証明します。
私の中では納得できている話なのでさらっと読むだけ。
数式で書くと順を追って理解できるから分かりやすいね。
今日の学習時間は【14分】。
これにて第10章終わり!
次は「第11章 ユーザーのマイクロポスト」から。
【142日目】【1日20分のRailsチュートリアル】【第10章】演習の2.の/users/:idの統合テストを作成する
今日は「10.5 演習」の2.の/users/:idの統合テストを作成するところから。
10.5 演習
応用問題: /usersと/users/:id両方の統合テストを作成してください。
昨日の続き。/users/:idの統合テスト作成について考えます。
とりあえずテストが通ったコードはコチラ。
テスト追加するファイルがコレジャナイ感が拭えない。。。
test/integration/users_index_test.rb
test "user_path as non-activated" do log_in_as(@admin) # 無効なユーザー @admin.toggle!(:activated) get user_path(@admin) assert_redirected_to root_path @admin.toggle!(:activated) end
$ bundle exec rake test 48 tests, 217 assertions, 0 failures, 0 errors, 0 skips
あーでもないこーでもないと考えてたら時間切れ。
今日の学習時間は【18分】。
次は「10.5 演習」の3.から。
【141日目】【1日20分のRailsチュートリアル】【第10章】演習の2.
今日は「10.5 演習」の2.から。
10.5 演習
2.現在は、/usersのユーザーインデックスページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。
しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト10.58のテンプレートに記入して、この動作を変更してください。
なお、ここで使用するActive Recordのwhereメソッドについては、11.3.3でもう少し詳しく説明します。応用問題: /usersと/users/:id両方の統合テストを作成してください。
whereメソッドはよく分からないけど、SQLでSELECT文使うときのWHERE句みたいなものかな。。。また後で。
穴埋めするとこんな感じかなぁ。。。
app/controllers/users_controller.rb
def index @users = User.where(activated: true).paginate(page: params[:page]) end def show @user = User.find(params[:id]) redirect_to root_url and return unless @user.activated? end
応用問題: /usersと/users/:id両方の統合テストを作成してください。
なんて難しい。。。。
/usersについて統合テスト作成してみたけどこんなのでいいのだろうか。。。ファイルもこれでいいのかよく分かってない…。
とりあえずテストは通った。
まぁまずは自分で書いてみるのも勉強だよね。答え合わせはまたいつか。
test/integration/users_index_test.rb
def setup @admin = users(:michael) @non_admin = users(:archer) end : test "index as non-activated" do log_in_as(@admin) # 無効なユーザー @admin.toggle!(:activated) get users_path assert_template 'users/index' assert_select 'a', text: @admin.name, count: 0 @admin.toggle!(:activated) end
$ bundle exec rake test 47 tests, 215 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【35分】。
次は「10.5 演習」の2.の/users/:idの統合テストを作成するところから。
【140日目】【1日20分のRailsチュートリアル】【第10章】10章のまとめと演習の1.
前回からだいぶ間が空いてしまったけど復活。
今日は「10.4 最後に」から。
10.4 最後に
アカウント有効化機能とパスワード再設定機能が追加されたことで、ついにサンプルアプリケーションの登録、ログイン、ログアウト機能がすべて本格的に実装完了しました。
アプリケーションの本機能ではないけど、アカウント管理には必須な機能がこれで実装完了したのか…!
10.4.1 本章のまとめ
そういえばGitへのコミットは…!?って思いかけたけど、前回してました。そうだった。
10.5 演習
演習とチュートリアル本編の食い違いを避ける方法については、3.6のトピックブランチの演習に追加したメモをご覧ください。
演習用にブランチ作成しておきます。
$ git checkout account-activation-password-reset $ git checkout -b account-activation-password-reset-exercises
1.リスト10.57のテンプレートを埋めて、期限切れのパスワード再設定のブランチ (リスト10.52) の統合テストを作成してください (10.57 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです)。
期限切れのテスト方法はさまざまですが、リスト10.57でおすすめした手法 (大文字小文字は区別されません) を使えば、レスポンスの本文に「expired」という語があるかどうかをチェックできます。
"Password reset has expired."
っていうエラーメッセージの全文でなくてもいいのかな。。。まぁ一部でいいか。
リスト10.57をほぼコピペして一部埋めてみた。
assert_match /expired/i, response.body
でIDEのワーニング出てるんだよね…合ってると思うんだけどな。。。
正規表現の/i
については下記記事が詳しかった。/i
が大文字小文字区別されないための記法か。
test/integration/password_resets_test.rb
test "expired token" do get new_password_reset_path post password_resets_path, password_reset: { email: @user.email } @user = assigns(:user) @user.update_attribute(:reset_sent_at, 3.hours.ago) patch password_reset_path(@user.reset_token), email: @user.email, user: { password: "foobar", password_confirmation: "foobar" } assert_response :redirect follow_redirect! assert_match /expired/i, response.body end
テストは通ったのでワーニングは無視します。気になるけど…
$ bundle exec rake test 46 tests, 213 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【25分】。
次は「10.5 演習」の2.から。