Ruby on Railsチュートリアル第6版 第13章まとめ

Ruby on Railsチュートリアル第6版の第13章のまとめです。

個人的に気になったところ、難しかったところ、わからなかったところを中心にまとめていきます。基本的に自分の理解を助けることと、復習しやすくすることを目的とした記事ですが、今Ruby on Railsチュートリアルをやってる人に役立つ情報があるかもしれません。

僕の学習の順番は、1周目はRailsチュートリアル第4版(Rails5.1)を使い、2周目で第6版を使うという感じになってます。これは1周目が終わったところで第6版のWebテキスト版がリリースされたからです。

ちなみに、2周目は解説動画を購入してやってます。オススメです。

第13章 ユーザーのマイクロポスト

ユーザーがマイクロポストを投稿できる機能を追加します。

13.1 Miropostモデル

これまでモデルはUserモデルだけでしたが、マイクロポストにもデータベースが必要なのでMicropostモデルを作ります。

いつものように、ブランチを作って進みます。

13.1.1 基本的なモデル

Micropostモデルの属性を考え、必要なものを作ります。マイクロポスト自体はcontent属性に入れ、データ型はText型です。String型との違いは文字数のようです。

MicropostモデルはUserモデルと関連付けをしたいため、「user:references」もつけてモデルを作ります。そうすることで、

belongs_to :user

がMicropostモデルに自動的に追加されます。belongs_toに関しては、第2章のところでまとめてあります。

さらに、references型を利用すると、

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

Ruby on Rails チュートリアル第6版 第13章

ということもやってくれるみたいです。外部キーについては第14章で出てくるみたいなので、ここでは調べないことにします。

マイグレーションをする前に、マイグレーションファイルにコードを追加します。

add_index :microposts, [:user_id, :created_at]

user_idとcreate_atにインデックスを付けるようです。その理由として、

こうすることで、user_idに関連付けられたすべてのマイクロポストを作成時刻の逆順で取り出しやすくなります。

Ruby on Rails チュートリアル第6版 第13章

と書かれてますが、なぜそうなのかは不明です。おそらくデータベースの方の話だと思うので、今は深入りしないでおきます。

さらに複合キーインデックス(Multiple Key Index)なる用語も出てきますが、これは一体何なんでしょうか?

訳注にある「What is a multiple key index?」を見てみると、

The multi-column index narrows down to the subset of rows that are associated with user_id 123, then within that subset, it further narrows down the selection to those with a recent created_at value.

Without the second column in the index, the RDBMS would have to load all the rows for user_id 123 into memory before it could determine if they pass the criteria.

What is a multiple key index?

と書かれてます。Google翻訳で日本語化すると、

複数列インデックスは、user_id 123に関連付けられている行のサブセットに絞り込み、そのサブセット内で、最近のcreated_at値を持つものにさらに選択を絞り込みます。

インデックスの2番目の列がない場合、RDBMSは、user_id 123のすべての行をメモリにロードてからでなければ、それらが基準に合格するかどうかを判断できません。

(Google翻訳による日本語訳)

What is a multiple key index?

こんな感じです。

いまいちよくわかりませんが、インデックスを付けると検索が早くなるということなので、複合キーインデックスにすると2つの属性で検索するのが早くなるという感じでしょうか。

そうしないと、1つ目の条件に合うデータすべてを取り出して、その中から2つ目の条件に合うデータを検索するという感じになるのかもしれません。

13.1.2 Micropostのバリデーション

Micropostモデルの単体テストを書きます。

ここで最初に出てくるテストコードだとバリデーションを追加する前からGREENになるという不思議なことが起こります。コード自体も慣習的に正しくないみたいです。

バリデーションを追加する前からGREENになる理由もあるようで、Rails 5だとそうなってしまうそうです。ただ、テストコードを慣習的に正しいものに書き換えるとバリデーションが期待通りに動くと説明されてます。

それ以外は以前にもやったところなので、特に問題ないと思います。

13.1.3 User/Micropostの関連付け

行動分析学的には「条件付け」ではなく、「条件づけ」なので、「関連付け」はちょっと気持ち悪いんですが、そういうものだと思いつつ進みました。

この関連付け自体は第2章でもやった、「belongs_to」と「has_many」なので、そこで理解できてれば平気だと思います。わからない場合は第2章に戻るか、下の記事で復習するといいかもしれません。

このような関連付けをすると、新しいメソッドが使えるようになるそうです。そのメソッドを使ってテストコードの慣習的に正しくない部分を直します。

Railsチュートリアルには書かれてないメリットもあるようで、詳しくは解説動画をご覧ください。

36時間以上の解説動画でオンライン学習 - Railsチュートリアル
図やイラストを使ってコーディングしながら解説する36時間の動画です。早く効率的に学びたい、動画を観ながら勉強したい、といった場面でお役立てください。

ただ、13.1.2のところでも書いたように、バリデーションと慣習的に正しい・正しくないコードとの関係だと、解説動画の説明が間違ってそうな感じです。Railsチュートリアルの方には慣習的に正しいコードだとバリデーションが期待通りに動くと書かれてるので。

ということで、慣習的に正しいコードに書き換えた後に、バリエーションをコメントアウトしてテストしてみたらREDでした。戻したらGREENだったので、Railsチュートリアルの説明が正しいと判断できそうです。

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

マイクロポストを改良します。1つはマイクロポストの表示順の変更、もう1つはユーザーの削除と連動してそのユーザーのマイクロポストを削除することです。

マイクロポストの表示順を変えるためには、デフォルトスコープというものを使います。これは「Railsガイド」によると、

あるスコープをモデルのすべてのクエリに適用したい場合、モデル自身の内部でdefault_scopeメソッドを使用することができます。

14.3 デフォルトスコープを適用する – Active Recordクエリインターフェイス – Railsガイドv6.0

というものだそうです。

そもそもここで言うスコープとは何か?という問題がありますよね。Railsチュートリアルで出てきたスコープは、記憶が正しければform_withメソッドで出てきたものだけだったと思います。

第8章で出てきたモデルがないときのあれですね。

それとの関係はよくわかりませんが、「【Rails】モデルのスコープ機能(scope)を基礎から応用までマスターしよう!」によると、

モデルのスコープ機能とは、モデル側であらかじめ特定の条件式に対して名前をつけて定義し、その名前でメソッドの様に条件式を呼び出すことが出来る仕組みのことです。

【Rails】モデルのスコープ機能(scope)を基礎から応用までマスターしよう!

ということのようです。基本構文は、

class モデル名 < ApplicationRecord
  scope :スコープの名前, -> { 条件式 }
end

と書かれてます。

Railsチュートリアルのデフォルトスコープは、

default_scope -> { order(created_at: :desc) }

となってるので、scopeがdefault_scopeになって、スコープ名がないことを除けば、基本的に同じ構成になってます。デフォルトなのでスコープ名は必要ないということかなと思います。

おそらくですが、default_scopeを設定することで、データベースにアクセスするときにそこで設定した条件式がデフォルトで追加されるということなんだと思います。

つまり、「created_atを降順で並べて!」と常に指示が入る感じですね。

この書き方はProcオブジェクトを作成する文法のようですが、少し高度なトピックらしく、すぐにわからなくても大丈夫なようです。

どういうときに使うかの具体例は解説動画で説明されてます。

36時間以上の解説動画でオンライン学習 - Railsチュートリアル
図やイラストを使ってコーディングしながら解説する36時間の動画です。早く効率的に学びたい、動画を観ながら勉強したい、といった場面でお役立てください。

次はユーザーが削除されたときに、そのユーザーのマイクロポストも削除されるようにします。この実装は簡単です。

Userモデルのhas_manyにちょっと追加するだけです。おそらく、意味としては「関連付けられたMicropostに対して、Userに依存してdestroyメソッドを実行して」みたいな感じかなと思います。

テストは今までに出てきたものなので、大丈夫だと思います。

13.2 マイクロポストを表示する

マイクロポストを表示できるようにします。Railsチュートリアルではユーザーのプロフィールページ(showページ)に表示させるようにします。

13.2.1 マイクロポストの描画

Micropostsコントロールを作って、実装を進めていきます。

基本的には第10章でやったことをマイクロポストでやるという感じだと思います。パーシャルを使ったショートカットなどもそうですね。

render @users

で、いろいろ実験したんですが、何やら理解が間違ってた様子です。詳しくは第10章のまとめに書いておきました。

基本的にはこのまとめに書いた通りですが、第13章で出てくるコード的に気になるところもあります。

マイクロポスト関係のパーシャル(_micropost.html.erb)で当然のように「micropost」という変数名が使われてます。これはどこから来たのかがわかりません。

おそらく、コレクションのレンダリング(render @usersとか)に関係すると思って調べてみました。「Railsガイド」によると、

パーシャルを呼び出す時に指定するコレクションが複数形の場合、パーシャルの個別のインスタンスから、出力するコレクションの個別のメンバにアクセスが行われます。このとき、パーシャル名に基づいた名前を持つ変数が使用されます。上の場合、パーシャルの名前は_productであり、この_productパーシャル内でproductという名前の変数を使用して、出力されるインスタンスを取得できます。

3.4.5 コレクションをレンダリングする – レイアウトとレンダリング – Railsガイドv6.0

こんな感じの挙動だそうです。Railsガイドの例では、@productsが使われてます。略記法を引用しておきます。

<%= render @products %>

コレクションとメンバの定義が見つからなかったので正確なところはわかりませんが、今のところの理解を書いておきます。

Railsチュートリアルの関連するところのコレクションは、@usersと@micropostsで、言い換えれば@usersはユーザーの集合、@micropostsはマイクロポストの集合という感じだと思います。

メンバはコレクション(集合)の1要素ということだと思います。

使用するパーシャル名は、コレクション内のモデル名に基いて決定されます。

3.4.5 コレクションをレンダリングする – レイアウトとレンダリング – Railsガイドv6.0

こうも書かれてるので、パーシャル名はモデル名にする必要がありそうです。そして、パーシャル内で使われるローカル変数はパーシャル名になるということですね。

このような制約を変更することもできます。

パーシャルの中で使う変数(ローカル変数)名を「:asオプション」で指定することもできるようです。試してみましたが、この場合は略記法は使えないようです。

<%= render partial: "product", collection: @products, as: :item %>

この正式な書き方を見ると、パーシャル名、コレクション名、ローカル変数をそれぞれ指定できそうです。@usersを使ってパーシャル名を「use」、ローカル変数名を「u」にして試してみたところ、正常に動きました。テストもGREENです。

ちなみに、パーシャル名とローカル変数名は削除するだけで変更できるという理由で決めました。

まとめると、略記法の場合は、「コレクション(インスタンス変数)を複数形にして、パーシャル名とローカル変数名はモデル名にする」という感じだと思います。

第10章まとめと第13章まとめに分かれてしまったので、Railsチュートリアルの2周目が終わったらまとめ直す予定です。

あと気になるのは、will_paginateの使い方ですね。Usersコントローラから@micropostsを呼び出す場合(別のコントローラのインスタンス変数を呼び出す場合)は、インスタンス変数を明示する必要があるそうです。

まだマイクロポストがないので、表示させてもがっかりな感じです。

13.2.2 マイクロポストのサンプル

マイクロポストのサンプルを作ります。ユーザーのサンプルを作ったのと基本的には同じ感じですね。

メソッドをちゃんと調べてませんが、

User.order(:created_at).take(6)

これは、「usersテーブルにあるデータをcreated_atで並び替え、1つ目から順番に6個のデータを取り出す」みたいな感じかなと思います。

作られた順番でIDがふってあるはずなので、orderメソッドがなくても同じ結果になりそうな気がしますが。Railsコンソールで試した結果は、orderメソッドがあってもなくても、同じユーザーが出てきました。

13.2.3 プロフィール画面のマイクロポストをテストする

マイクロポストの表示のテストを書きます。

新しいものも出てきますが、指示通りで問題ないと思います。

13.3 マイクロポストを操作する

マイクロポストの投稿、削除を実装します。ルーティングを設定する作業も慣れてきましたね。

13.3.1 マイクロポストのアクセス制御

マイクロポストの投稿と削除に対してアクセス制御をします。ログインしてるユーザーだけができるようにするということですね。

指示通りで問題ないと思います。

13.3.2 マイクロポストを作成する

マイクロポストの投稿フォームとcreateアクションを作ります。

重要なのはエラーメッセージ関連の書き換えだと思います。

<%= render 'shared/error_messages', object: f.object %>

この部分の「object: f.object」ですね。Railsチュートリアルでの説明だとちょっとわかりにくいんですが、解説動画だとよくわかります。

36時間以上の解説動画でオンライン学習 - Railsチュートリアル
図やイラストを使ってコーディングしながら解説する36時間の動画です。早く効率的に学びたい、動画を観ながら勉強したい、といった場面でお役立てください。

解説動画によると、「object: f.object」は「foo: f.object」でも問題ないようです。

「f.object」はフォームで参照してるデータを引っ張ってくることができ、「object: f.object」と書くことで変数objectにそれを入れることができます。

で、その変数objectはパーシャルの方でローカル変数として扱うことができるので、フォームで参照したデータのオブジェクトが変数objectに渡されるということになります。

こう書いていて、自分でもよくわからない気がしたので、図にしてみました。

f.objectの動き

これが正しいか、いまいち自信はありませんが、動きとしては変数である「object」にmodelで指定した「@hoge」が入ってるという感じかなと思います。

こうすることで、複数のモデルを参照するフォームに関するエラーメッセージを1つのパーシャルで使いまわすことができます。

あとは関連するところの書き換えるだけです。

13.3.3 フィードの原型

Homeページにもマイクロポストが表示させるようにします。

ここでfeedメソッドを作りますが、これは第14章で完成します。

Micropost.where("user_id = ?", id)

feedメソッドではこんな感じのコードが出てきますが、こうすることでSQLインジェクションを避けることができるようです。Wikipediaによると、SQLインジェクションは想定してないSQL文を実行させてる攻撃方法だそうです。

「?」を使うことで代入される文字列がエスケープされるそうです。

またコレクションのレンダリングが出てきますが(「render @feed_items」のこと)、上で説明した通りです。便利ですが、馴染むまで混乱しそうですね。

あとはwill_paginateの使い方という感じだと思うので、大丈夫だと思います。

13.3.4 マイクロポストを削除する

マイクロポストを削除できるようにします。

指示通りで問題ないと思います。

13.3.5 フィード画面のマイクロポストをテストする

マイクロポストに関するテストを追加します。

ここも大丈夫だと思います。統合テストは相変わらず長いですが、読めばわかると思います。

13.4 マイクロポストの画像投稿

マイクロポストで画像を投稿できるようにします。本番環境でも画像投稿できるようにしますが、スキップすることもできるそうです。

13.4.1 基本的な画像アップロード

画像投稿にはRailsの機能であるActive Storageを使います。1周目となんか違うなと思ったら、Railsチュートリアル第4版ではCarrierWaveというのを使ってました。

ここは指示通りで大丈夫かなと思います。

13.4.2 画像の検証

投稿できる画像に制限をつけます。

画像サイズやフォーマットに対するバリデーションを実装するためにGemを追加します。

基本的には指示通りで大丈夫ですが、ここでjQueryが出てきます。興味がある人は詳しく調べてみてもいいかもしれません。僕はコピペで終わりにしました。

ここで一番困ったのはファイルサイズ制限ができてるかの検証です。

画像のファイルサイズに対するバリデーションをかけますが、Railsチュートリアルでは最大5MBに制限をする設定になってます。1周目のときは5MB以上の画像ファイルを手に入れることができず、うまく動いてるかわからないままでした。

解説動画で制限を変更してたのを見て、その方法を使うことにしました。制限は解説動画と同じ1MBにしました。

探すのが面倒な人は、ここからダウンロードできます。

13.4.3 画像のリサイズ

次は画像のリサイズです。大きい画像を投稿してみたとしたらわかると思いますが、画像サイズによってはレイアウトが崩れてしまいます。

クラウドIDEとかを使ってない場合はやり方が少し変わるようですが、僕はCloud9を使ってるので詳細は不明です。

13.4.4 本番環境での画像アップロード

最後に本番環境でも画像をアップロードできるようにします。

Herokuではデプロイするたびにアップロードした画像が削除されるようで、別のクラウドストレージサービスを使う必要があるとのことです。RailsチュートリアルではAWSのS3を使います。

先にGemを入れて、AWSの設定に入ります。

Railsチュートリアルに書いてある通りでもいいですが、もう少し詳しい解説が欲しい場合は、下の記事を参考にするといいと思います。

【Railsチュートリアル】S3に画像をアップロードする設定【13章課題】 - Qiita
S3に画像をアップロードする 13.4.4 本番環境での画像アップロード 13.4.3で実装した画像アップローダーは、開発環境で動かす分には問題ないのですが、本番環境には適していません。これはリスト 13.67のstorag...

一通り終わらせて、デプロイしたらエラーが出ました。ログとかを残すのを忘れたので記憶を頼りに書いてみます。

エラーのスクショを撮っとけばよかったなと思いますが、出たのは下の記事にあるような画面です。

HerokuにデプロイしたアプリがApplication error | 定年後にWeb開発者目指す
手元で動作するアプリケーションをHerokuにデプロイしました。 特にデプロイでエラーはなかったと思ったのですが、実際にアクセスすると次のエラーとなりました。 HerokuでAp...

Herokuさんにログを見ろと言われたので、見てみたらstatusが503となってました。

そのエラーで検索してたどり着いたのが下の記事です。

Herokuでデプロイした際にApplication error(エラーコードH10 (App crashed))が出た時の対処法 - Qiita
環境 Ruby 2.6.3 Rails 5.2.3 現象 Railsアプリケーションを作成後、Herokuへデプロイした際に以下画像のエラーが発生。 その際の対処法を備忘録として残しておきます。  【対処方法】エラ...

そこに書いてあるようにHerokuをリスタートしてみましたが、変わりません。

他にも、

HerokuでApplicationErrorが発生したときの対処法 - Qiita
heroku で運用を行っていると、原因不明の ApplicationError が発生することがあります。 30秒ルール以外で発生する場合、経験上はエラーコード H10 が発生していることが多いです。 Heroku Error C...

に書いてあることをやってみたりしましたが、ダメでした。

簡単には解決できなそうだったので、2番目の記事「Herokuでデプロイした際にApplication error(エラーコードH10 (App crashed))が出た時の対処法」に書いてあるように、HerokuでRailsコンソールを開いてみました。

そこでログを見てみると、aws-sdk-s3でエラーが起こってることがわかりました。ArgumentErrorです。引数のエラーですね。

Cannot load `Rails.config.active_storage.service`: (ArgumentError) 
missing required option :name

検索履歴から取り出したエラーメッセージはこんな感じで、config.active_storage.serviceがロードできなかったようです。ということで、その辺がおかしいのかなとコピペでやり直してみましたが、ダメでした。

config.active_storage.service = :amazon

解説動画ではこれを一番下にコピペしてましたが、Railsチュートリアルでは途中に入れることになってました。そこで検索したところ、同じのがあったのでこれが原因だと喜び、書き換えましたが、ダメでした。

aws-sdk-s3のコードがわかれば何かヒントが得られるんじゃないかと思って、GitHubにあったコードを見てみましたが、よくわかりませんでした。

とりあえず諦めて先に進むことも考えながら、間違ってる可能性のあるところを探してたところ、S3のアクセスキーとかの設定で試してないことがあると気づきました。

入力のし直しはしましたが、それは履歴を使ってキーなどを変えるだけでした。もしかしたら、コマンドそのものを間違えてる可能性を考え、コマンドをコピペしてやってみたところ成功です。

成功したから良しとせず、何が間違ってたか調べたところ、

heroku config:set AWS_BUCKET=<bucket名>

としなければいけないのに、

heroku config:set AWS_BUKET=<bucket名>

となってました。「C」が抜けてたんです。

aws-sdk-s3でどう動いてるかはわかりませんが、渡す変数名が間違ってるということは、bucket名だけ入ってない状態で実行してたということですよね。

ArgumentErrorとかエラーメッセージとかとの整合性を考えても、納得できる感じですね。

単純なミスなのに、解決にすごく時間がかかってしまいました。でも、解決できたから良しとします。

まとめ

第13章は、複合キーインデックスやコレクションのレンダリング、default_scope、f.object、AWSのS3の使い方など、追加で調べたり、難しかったり、苦労したりすることが多かった印象です。

さらに、マイクロポストの投稿がバリデーションに引っかかった後にリロードするとエラーになるという問題も発見しました。

最初はこのまとめに入れようと考えてましたが、内容的に第13章から離れすぎてしまうので、別にすることにしました。どこかで間違ってて、エラーが出てる可能性もありますが。

ちょっと調べてみたら、ユーザー登録やパスワード再設定でも同じリロード問題が出ることがわかり、2周目が終わってから直すことにしました。

残すは最終章の第14章のみです。1周目でほとんど理解できなかったところなので、気合を入れて臨みたいと思います。

Ruby on Rails チュートリアル:実例を使って Rails を学ぼう
SNS 開発を題材にした大型チュートリアル。プロダクト開発の 0→1 を創りながら学びます。電子書籍や解説動画、質問対応、法人向けサービスも提供しています。