らくだ🐫にもできるRailsチュートリアル|13.1

ユーザーが短いメッセージを投稿できる機能を作っていく
→「マイクロポストリソース」を追加していく

Micropostデータモデルを作成し、Userモデルとhas_manyおよびbelongs_toメソッドを使って関連付けを行い、さらに、結果を処理し表示するために必要なフォームとその部品を作成します (13.4で画像のアップロードも実装します)。

因みに(?)14章ではユーザーをフォローするという概念を導入してマイクロポストのフィードを受け取れるようにする(Twitterのミニクローンの完成!)

13.1 Micropostモデル

Micropostモデルを作成する

今回のマイクロポストモデルは完全にテストされ、デフォルトの順序を持ち、また親であるユーザーが破棄された場合には自動的に破棄されるようにします。

(2章のtoy-appと違って~的なお話)
ではブランチを作成して作業開始!

13.1.1 基本的なモデル

Micropostモデル → 内容を保存するcontent、特定のユーザーとマイクロポストを関連付けるuser_idを持っている
(ほかに、自動で付加されるidとcreated_atとupdated_atがある)
構造図はRoRT本文参照RoRT本文参照
上記の図で、マイクロポストの投稿内容にString型ではなくText型が使われている

Text型とはなんぞ

255文字までの上限があるString型と違い、Text型はある程度の量のテキストを格納する時に使われる
今回実装するマイクロポストは140文字の文字制限をつける(予定)のでString型でもいいんだけど
Text型ならではの利点があるので、あえてText型を指定している

投稿フォームにString用のテキストフィールドではなくてText用のテキストエリアを使うため、より自然な投稿フォームが実現できます。また、Text型の方が将来における柔軟性に富んでいて、例えばいつか国際化をするときに、言語に応じて投稿の長さを調節することもできます。

Micropostモデルを生成する

自動生成されたMicropostsモデル↓

Userモデルとの違い

Userモデルとの最大の違いはreferences型を利用している点

これを利用すると、自動的にインデックスと外部キー参照付きのuser_idカラムが追加され、UserとMicropostを関連付けする下準備をしてくれます

また、Userモデルの時と同じくt.timestampsという行 (マジックカラム) が自動的に生成されていることにより
created_atとupdated_atというカラムが追加される(created_atは13.1.4の実装を進めるうえで必要!)

自動生成されたマイグレーションファイル↓

インデックスを付与することでuser_idに関連付けられたすべてのマイクロポストを作成時刻の逆順で取り出しやすくしている

また、user_idとcreated_atの両方を1つの配列に含めている点にも注目です。こうすることでActive Recordは、両方のキーを同時に扱う複合キーインデックス (Multiple Key Index) を作成します。(訳注: 複合キーインデックスは、スタックオーバーフローのWhat is a multiple key index?で詳しく解説されています。)

データベースを更新

演習

  1. RailsコンソールでMicropost.newを実行し、インスタンスを変数micropostに代入してください。その後、user_idに最初のユーザーのidを、contentに “Lorem ipsum” をそれぞれ代入してみてください。この時点では、 micropostオブジェクトのマジックカラム (created_atとupdated_at) には何が入っているでしょうか?
  2. 先ほど作ったオブジェクトを使って、micropost.userを実行してみましょう。どのような結果が返ってくるでしょうか? また、micropost.user.nameを実行した場合の結果はどうなるでしょうか?
  3. 先ほど作ったmicropostオブジェクトをデータベースに保存してみましょう。この時点でもう一度マジックカラムの内容を調べてみましょう。今度はどのような値が入っているでしょうか?

1.
created_atとupdated_atはnil

2.

3.

13.1.2 Micropostのバリデーション

基本的なモデルが作成できたのでバリデーションを追加していく

マイクロポストは投稿したユーザーのid(user_id)を持たせるようにしたので
これを使って慣習的に正しくActive Recordの関連付けを実装する

まずはMicropostモデル単体を動くようにしていく→テスト駆動開発で!

Micropostの初期テスト

Micropostの初期テストはUserモデルの初期テストと似ている

まずはsetupのステップで、fixtureのサンプルユーザーと紐付けた新しいマイクロポストを作成しています。次に、作成したマイクロポストが有効かどうかをチェックしてます。最後に、あらゆるマイクロポストはユーザーのidを持っているべきなので、user_idの存在性のバリデーションに対するテストも追加します。

ひとまずこれをまとめたコードがこちら

上記のテストをパスさせるために存在性のバリデーションを追加する

GREEN!

ちなみにRails 5では、リスト 13.5のバリデーションを追加しなくてもリスト 13.4のテストが成功してしまいます。しかしこれは、リスト 13.4でハイライトした「慣習的な意味で正しくない」というコードを書いた場合でのみ発生します。この部分を「慣習的に正しい」コードで実装すると、user_idに対する存在性のバリデーションが期待通りに動きます (リスト 13.12)。この部分を説明しておきたかったので、先ほどのコメントでハイライトしておきました。

次にcontent属性に対するバリデーションを追加
テスト開発駆動!(テスト→機能の実装)
※141文字(大きくて無効な値)だけじゃなくて139文字(小さくて有効な値)、140文字(設定値と同じな有効な値)もテストするべきって指摘をいただきまして
 なるほどちゃんと動くテストねってなったので追加してみました。

バリデーションを追加

演習

  1. Railsコンソールを開き、user_idとcontentが空になっているmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?
  2. コンソールを開き、今度はuser_idが空でcontentが141文字以上のmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?

1.

2.

m = Micropost.new(content: “a” * 141)でよくね?って言われたけどホンソレですね!

13.1.3 User/Micropostの関連付け

モデル間の関連付けを十分に考えておくことはとても重要

今回の場合は、 2.3.3でも示したように、それぞれのマイクロポストは1人のユーザーと関連付けられ、それぞれのユーザーは (潜在的に) 複数のマイクロポストと関連付けられます。

RoRT本文に図解があるよ
これらの関連付けを実装するための一環として
Micropostモデルに関するテストを作成&Userモデルのテストに追加をしていく

belongs_to/has_many

belongs_to/has_manyの関連付けを定義することによって
紐付いたユーザーを通してマイクロポストを作成することが出来るようになる
Micropostモデル単体ではこうだったメソッドが

こうなる

新規のマイクロポストが上記のメソッドで作成されると
user_idに 自動的 に 正しい値 が設定される
user/micropost関連メソッドのまとめ

(newメソッドと同様に、buildメソッドはオブジェクトを返しますがデータベースには反映されません。) 一度正しい関連付けを定義してしまえば、@micropost変数のuser_idには、関連するユーザーのidが自動的に設定されます。

Micropostモデルに必要なbelongs_to :userというコードは自動的に生成されている
Userモデルに必要なhas_many :micropostsは手動で追加する

Micropostモデルのテストを修正

演習

  1. データベースにいる最初のユーザーを変数userに代入してください。そのuserオブジェクトを使ってmicropost = user.microposts.create(content: “Lorem ipsum”)を実行すると、どのような結果が得られるでしょうか?
  2. 先ほどの演習課題で、データベース上に新しいマイクロポストが追加されたはずです。user.microposts.find(micropost.id)を実行して、本当に追加されたのかを確かめてみましょう。また、先ほど実行したmicropost.idの部分をmicropostに変更すると、結果はどうなるでしょうか?
  3. user == micropost.userを実行した結果はどうなるでしょうか? また、user.microposts.first == micropost を実行した結果はどうなるでしょうか? それぞれ確認してみてください。

1.

2.

3.

13.1.4 マイクロポストを改良する

UserとMicropostの関連付けを改良していく
→ユーザーのマイクロポストを特定の順序で取得できるようにする
→マイクロポストをユーザーに依存させて、ユーザーが削除されたらマイクロポストも削除されるようにする

デフォルトのスコープ

user.micropostsメソッドはデフォルトでは呼び出しの順序に何の保証もない(決まっていない)
→一般的な表示順として新しい順に表示されるようにする
これを実装するためにdefault scopeというテクニックを使う!

マイクロポストの順序のテスト

この機能のテストは、見せかけの成功に陥りやすい部分で、「アプリケーション側の実装が本当は間違っているのにテストが成功してしまう」という罠があります。正しいテストを書くために、ここではテスト駆動開発で進めていきます。

まず、DB上の最初のマイクロポストがfixture内のマイクロポスト (most_recent) と同じであるか検証する
順序のテスト

→マイクロポスト用のfixtureファイルからサンプルデータを読み出すのでデータを用意

→埋め込みRubyを使ってcreated_atカラムに値をセットしている
本来はRailsによって自動的に更新される値の為手動更新はできないが
fixtureファイルの中では更新可能になっているので、意図的に生成時間の順序を変更している
→ 現状では第一引数と第二引数は等しくない(4番目と1番目)なのでfalseでRED

テストを成功させるコード

Railsのdefault_scopeメソッドを使ってこのテストを成功させる

このメソッドは、データベースから要素を取得したときの、デフォルトの順序を指定するメソッドです。特定の順序にしたい場合は、default_scopeの引数にorderを与えます。


created_atカラムの順にしたい場合の引数はこう↓

ただしこれだと昇順なのでcreated_atの値が最も古いものから順にソートされてしまう
→さらに降順を指定する場合の引数はこう

このコードを使ってMicropostモデルを更新する

ラムダ式 (Stabby lambda) とは何ぞ

Procやlambda (もしくは無名関数)と呼ばれるオブジェクトを作成する文法の事。

->というラムダ式は、ブロック (4.3.2) を引数に取り、Procオブジェクトを返します。このオブジェクトは、callメソッドが呼ばれたとき、ブロック内の処理を評価します。

ちょっと何言ってるかわからないですね🤔


うーん

(ProcはRubyの中でも少し高度なトピックなので、今すぐわからなくても心配する必要はありません。)

という事なので追々ます(ってことでいいかな?)

さておき、テストはGREENになりました!

Dependent: destroy

ユーザーが破棄された場合、ユーザーのマイクロポストも一緒に破棄されるべき!
→has_manyメソッドにオプションを渡すことで実装できる

※「マイクロポストは削除しない」指定をすることもできる

これは、管理者がシステムからユーザーを削除したとき、持ち主の存在しないマイクロポストがデータベースに取り残されてしまう問題を防ぎます。

テストを書いて動作確認!→Userモデルを検証

(idを紐づけるための) ユーザーを作成することと、そのユーザーに紐付いたマイクロポストを作成する必要があります。その後、ユーザーを削除してみて、マイクロポストの数が1つ減っているかどうかを確認します。

演習

  1. Micropost.first.created_atの実行結果と、Micropost.last.created_atの実行結果を比べてみましょう。
  2. Micropost.firstを実行したときに発行されるSQL文はどうなっているでしょうか? 同様にして、Micropost.lastの場合はどうなっているでしょうか? ヒント: それぞれをコンソール上で実行したときに表示される文字列が、SQL文になります。
  3. データベース上の最初のユーザーを変数userに代入してください。そのuserオブジェクトが最初に投稿したマイクロポストのidはいくつでしょうか? 次に、destroyメソッドを使ってそのuserオブジェクトを削除してみてください。削除すると、そのuserに紐付いていたマイクロポストも削除されていることをMicropost.findで確認してみましょう。

1.
(下記参照)
Micropost.first.created_atの方がMicropost.last.created_atより新しい投稿(降順に並び替えているため)
2.

3.

因みにuser.destroyした時のログで紐付いたマイクロポストも削除されているのが読み取れる

まとめとか感想

SQLもやっておくといいよを実感する章
Progateとかでざっくりでいいので、やっておくだけでも全然違うと思いました。

らくだ🐫にもできるRailsチュートリアルとは

「ド」が付く素人のらくだ🐫が勉強するRailsチュートリアルの学習記録です。
自分用に記録していますが、お役に立つことがあれば幸いです。

調べたとはいえらくだ🐫なりの解釈や説明が含まれます。間違っている部分もあるかと思います。そんな所は教えて頂けますと幸いなのですが、このブログにはコメント機能がありません💧お手数おかけしますがTwitterなどでご連絡いただければ幸いです