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

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

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

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

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

第11章 アカウントの有効化

第11章 アカウントの有効化

「登録 > メールが送られる > URLをクリック > 登録完了」という普通にある手順の実装です。

難しいという話ですが、1周目もあまり苦労した記憶がなかったりします。終わらせることを優先してて、難しさに気づかなかっただけかもしれませんが。

全体としては第9章の発展的なログイン機構でやったことをメール版にした感じです。なので、第9章の理解がいまいちだと苦労すると思います。

11.1 AccountActivatonsリソース

これまで作ってきたリソースとは別物なので新しいリソースを作ります。

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

11.1.1 AccountActivationsコントローラ

コントローラの生成はもう大丈夫だと思います。ここで使うリソースはeditアクションのみなので、ルーティングでeditだけを設定します。

11.1.2 AccountActivationsのデータモデル

アカウントの有効化に必要な認証を実装するために、データベースに新しいカラムを追加します。

どうやって認証するかという流れがわかってれば問題ないと思いますが、その流れを理解するのが難しいかもしれません。

2周目は解説動画を見ながらだったので、それについてはあまり困らずに進めました。もし難しいと感じるのであれば、解説動画を見ることをオススメします。

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

途中で勘違いに気づいたのが、before_createコールバックです。その名前からcreateアクションの直前で実行されると思いこんでたら、違ったようです。

ActiveRecordのコールバック早見表 | Rails」によるとbefore_createコールバックは、

オブジェクトがDBに新規保存(INSERT)される直前で実行されます

ActiveRecordのコールバック早見表 | Rails

と書かれてます。データベースに保存される直前ということは、before_saveと何が違うのか気になるところです。

before_saveは、

オブジェクトがDBに保存される直前で実行。INSERTUPDATE両方で実行

ActiveRecordのコールバック早見表 | Rails

とあるので、INSERT(新規保存)の直前だけに実行されるbefore_createと、INSERT(新規保存)とUPDATE(更新)の直前で実行されるbefore_saveということですね。

ちなみに、UPDATE(更新)の直前だけに実行されるのはbefore_updateだそうです。

11.2 アカウント有効化のメール送信

ここからメール送信に必要なものを追加していきます。

11.2.1 送信メールのテンプレート

rails generateを使ってメイラーを作ります。モデルとかコントローラと同じ感じでやれば、メールの文面も自動生成されます。

メールのfromアドレスなどを変更しますが、指示通りで大丈夫だと思います。

11.2.2 送信メールのプレビュー

development環境(開発環境)のメール設定をします。

コードに「ここをコピペすると失敗します」というコメントが入ってましたが、1周目のときはその意味がよくわかりませんでした。やってみれば簡単なんですが、「こういうこと?」と疑問に思いながら進めた記憶があります。

そこに書くのは、開発環境で使ってるトップページのURLの「https://」あるいは「http://」と、最後の「/」を除いたところです。その2つの間と言った方がわかりやすいかもしれませんね。

設定が終わってメールのプレビューを見たときは少し感動しました。

11.2.3 送信メールのテスト

送信メールのテストを書きます。テスト用のドメインホストを設定しないと、何をやってもテストはREDになるようです。

ドメイン名自体は何でもいいみたいです。

11.2.4 ユーザーのcreateアクションを更新

ユーザー登録時にメールを送信するように、Usersコントローラのcreateアクションを変更します。deliver_nowという新しいメソッドが出てきますが、名前から何をやるかわかるので問題ないと思います。

リダイレクト先を変更したので、テストも変更します。

メールの文面はサーバーログで確認できます。

11.3 アカウントを有効化する

やっとアカウントの有効化です。

remember_token、remember_digestで使ったauthenticated?メソッドをアカウントの有効化でも使えるように変更します。メタプログラミングと呼ぶそうです。

メソッド名に変数を使って、式展開で変化させるという感じで、やってること自体はそこまで難しくないと思います。

メソッド名で式展開をするにはsendメソッドを使って、その引数に「文字列」としてメソッド名を渡します。

ここでやること自体は本筋ではなく、rememberとは別にactivationのauthenticated?メソッドを作っても問題ないようです。

11.3.2 editアクションで有効化

ユーザー登録の流れが変わったので、Sessionsコントローラのcreateアクションを有効化されてるかどうかで処理が変わるように変更します。

処理の流れがわかってればそこまで難しくないと思います。

11.3.3 有効化とテストのリファクタリング

テストを書きます。テスト自体は長くて難しそうですが、1つずつ読んでいけば理解できると思います。

リファクタリングも難しくありませんが、1周目では何のためにやるかがいまいち理解できませんでした。解説動画を見て理解できました。

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

演習2で有効化されてないユーザーを表示しないように変更します。この時点で表示されるのかとか調べてたら、存在しないIDのプロフィールページ(例えば、users/1000)にアクセスしたらエラーが出ることに気づきました。

エラーになってるのはUsersコントローラのshowアクションです。

def show
  @user = User.find(params[:id])
end

(app/controllers/users_controller.rb)

GitHubにあるコードサンプルも確認しましたが、同じコードでした。

ということで、他に影響が出るかもしれないと思いながら、自分で修正を試みました。

この問題を解決するには、存在しないIDが検索された場合に、root_urlなどにリダイレクトすればいいというのはすぐにわかりました。でも、それをどうやって実装すればいいかで悩みました。

そもそもfindメソッドだとエラーになってしまうので、if文とかで分岐ができません。そこでfind_byメソッドを使うことを考えました。

第6章で出てきたfindメソッドとfind_byメソッドの違いですね。

Railsコンソールも使ってチェックして、find_byメソッドを使えば存在しないIDで検索したときにnilが返ってくることを確認しました。これが使えそうです。

@user = User.find_by(id: params[:id])

nilであればroot_urlにリダイレクトさせればいいだけなら簡単です。でも、それに加えて演習で求められてる「有効化されてないユーザーのプロフィールページにアクセスしたらroot_urlにリダイレクト」も同時に実装します。

同じページにリダイレクトするなら1つの条件式に入れたいところです。

redirect_to root_url and return unless FILL_IN

これが演習で出てくるコードです。FILL_INを埋めて完成させるわけですが、有効化されてなかった場合にリダイレクトするように書くだけです。

チェックするのは有効化されてるかどうかなので、authenticated?メソッドが使えそうです。それがfalseならリダイレクトというコードになってます。

ということは、「authenticated?がfalse、または@userがnil」という条件になります。

autheticated?はオブジェクトがnilだとダメなので、@userがnilの場合に処理がスキップされるようにする必要があります。先に書いてエラーが出て気づきました。

それなら逆にすればいいと思ったけどそれもダメでした。これを書きながら調べてみたら、

少なくともunlessと||の組み合わせは苦情が出やすいので避けましょう。

unless obj1.blank? || obj2.blank?

言葉で書けば「obj1とobj2のどちらもblankでないなら」となります。

Rubyにおけるunlessとコードの読みやすさについて – TechRacho

ということみたいなので、両方がチェックされるということみたいですね。それはうまくいかないわけです。

どうやって解決したかというと、if文に書き直しました。

まず、@userがnilかどうかを調べます。ここでnilであれば有効化とか以前の話なので、リダイレクトすればいいことになります。

もし@userがnilでなければ有効化されてるかをチェックする必要があります。有効化されてればプロフィールページを表示して、有効化されてなかったらroot_urlにリダイレクトします。

ということを書いていたら、そもそも

redirect_to root_url and return unless FILL_IN

これの動きがよくわからないことに気づきました。今更ですが。

「Railsガイド」の「2.2.14 二重レンダリングエラーを避ける」を何度も読んで何とか理解できた気がして説明しようとすると、理解できてないことに気づかされる感じです。

上のコードを書き直すと、

unless FILL_IN
  redirect_to root_url and return
end

になります。FILL_INがfalseである場合に、「redirect_to root_url and return」を実行するわけです。なので、よくわからないのは「and return」です。

試しに「and return」を削除してみたら問題なく動きました。

and returnを使うときの注意点」によると、

renderの戻り値がたまたまtrueなのを利用して、andで受けて次の文を書いているだけっぽいです。なので万が一falseやnilを返してきたらreturnは実行されないということらしい。

and returnを使うときの注意点

ということみたいです。「らしい」という表現なので確信は持てませんが、これが正しいとしても処理をandで繋ぐのは何だろう?という疑問が。

とりあえず、if文のところとは関係ないので、「これで動く!」ということにして、いつか戻ってきてしっかり理解することにします。

で、if文ですが、@userがnilのときにauthenticated?メソッドが実行されないようにしたい、つまりnilガードを付けたいということになります。

ありがたいことに、@userがnilのときも同じ処理なので、nilだったらその後の条件をチェックしないようにすればいいということになります。ということは、2つの条件式を繋ぐのは「||」(or)になるはずです。

1つ目の条件式がtrueだったら2つ目の処理は実行されず、falseだったら実行されるという流れになるので、nilガード的には@userがnilだったらtrueを返すようにすればいいことになります。

nilじゃなかったら次の条件式を評価することになりますが、こちらは「有効化されてない」がtrueになってほしいわけです。

authenticated?メソッドは有効化されてるときにtrueなので、真偽値を反転させる必要があります。

これらを合わせると、

if @user.nil? || !@user.authenticated?

になるはずです。実際にそれで処理がうまくいきました。おそらくこれで解決のはずです。

Railsチュートリアルのコードと変えてしまったので、どこかで不具合が出る可能性があります。そのため、元のコードはコメントアウトで残してあります。

11.4 本番環境でのメール送信

HerokuでSendGridアドオンを使うにはクレジットカード登録が必要ということで迷いましたが、無料で使えるから登録することにしました。

指示通りやったのにエラー。

Herokuのエラー

Railsチュートリアル11章 SendGrid 凍結問題を解決する」を試してみるも、凍結されてないので意味なし。

Herokuデプロイ後のエラー」を参考にHerokuサーバーを再起動してみても、変化なし。

Railsチュートリアル 第11章 11.4 「本番環境でのメール送信」 で詰まったところ」を参考にマイグレーションのステータスを見てみましたが、見方がよくわからず、Add activation to usersとあったのでOKと判断。

いろいろ調べたり、試したりした結果、マイグレーションのステータスでAdd activation to usersだけがdownになってることに気づき、マイグレーションできてないことがわかりました。

そういえば、マイグレーションしたときのログが長かった気がしたので、よくよく見てみると、

rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedObject: ERROR:  type "dattetime" does not exist
LINE 1: ALTER TABLE "users" ADD "activated_at" dattetime

という記述がありました。activated_atのデータ型が「dattetime」になってるというミス。ということは、驚くことに、開発環境だとそれで動いてたということですね。

マイグレーションファイルを直して、やり直したら、マイグレーションはできました。

up     20...  Create users
up     20...  Add index to users email
up     20...  Add password digest to users
up     20...  Add remember digest to users
up     20...  Add admin to users
up     20...  Add activation to users

Herokuでデータベースのリセットなどをやって、データベースにサンプルユーザーが入ってる状態まで戻しました。もちろん、ユーザーの有効化関係のカラムも追加されてます。

これでうまくいくと思って登録してみたら、また同じエラー。ログによると、今度は凍結のようです。

Railsチュートリアル11章 SendGrid 凍結問題を解決する」と同じように

Net::SMTPAuthenticationError (535 Authentication failed: account disabled

がありました。なので、書いてある通りにHerokuアプリを削除して作り直しました。

時間はかかりましたが、なんとかメール送信とアカウントの有効化できました。

まとめ

最後に大きくつまずきましたが、第9章がある程度理解できてればそこまで難しくないような気がします。もしかしたらサラッと流して、重要なところの理解が抜けてる可能性はありますが。

「もう完璧!」というわけではありませんが、何となくは理解できてる感覚があります。これ以上の理解を現段階で求めるのは効率が悪くなりそうなので、ここではこのくらいにしておこうと思います。

演習の「and return」は最後までわからず、存在しないIDのプロフィールページにアクセスするとエラーになる問題にぶつかるなど、本筋とは違ったところで苦労しました。

エラー問題は勝手に直しましたが、それでよかったのか、そもそもどこかでエラーが出ないようにしてるはずなのにそれを飛ばしたのか。疑問は残りますが、とりあえず進むことにします。

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