railsチュートリアル14章が終わったので、いつものように自分が難しかったところをまとめていきたいと思います。
この章では主にフォロー/アンフォロー機能について学んでいきました。
「Relotionshipモデル」で、フォロワーのIDとフォローされたIDの一覧表を作ることで、フォロー/フォロワー関係を一つのモデルだけで済ますことができるようになります。
14.1.2 の下に(リスト14.2の上)に、このような説明があります。
(ActiveRelationshipモデルを探してしまい) Relationshipモデルを見つけることができません。このため、今回のケースでは、Railsに探して欲しいモデルのクラス名を明示的に伝える必要があります。
これがどういうことかというと、
例えばmicropostsの場合、userがたくさんのmicropostを持っているので、
has_many :microposts
というコードを書くことが出来ます。
これは、micropostsシンボルから、Railsはこれに対応するMicropostモデルを探し出すことができるからです。つまり、実在するMicropostモデルがあるからこそhas_many :micropostsが書ける、というわけです。
一方、ActiveRelationshipモデルは実在しません。ActiveRelationshipモデルとは、概念的なものであり、実在しているわけではないのです。
(Relationshipモデルの中に概念的/仮想的に作られた、follower_id, followed_id, user.followingの関係性を分かりやすく算出できる便利な方法が、ActiveRelationshipモデルです)
ですからActiveRelationshipモデルという概念上の便利なモデルをhas_manyに紐付けるためには、
has_many :active_relationships
ではなく、リスト14.2の
has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
にする必要があります。
これらのコードを書くことで、概念上のactive_relathionshipsをあたかも実在しているかのように扱うことができるようになります。
ここで気をつけることが一つあります。とても重要です。
このリスト14.2のhas_many関係で紐付けているのは、Userとactive_relationshipsとの間です。
つまり、図14.7の左側の青い矢印(throughの部分)で、has_many関係が成立しています。
右側の赤色のhas_manyはまだ実装していません!
リスト14.2とリスト14.3では、userとactive_relationshipsとの間に関連付けをしています。userからactive_relationshipsへの複数の矢印がhas_manyで、ここのactive_relationshipからuserへの関連付けがbelongs_toになっています。
ちなみにリスト14.3のbelongs_toにおける
belongs_to :follower, class_name: "User"
はフォロワーへの関連付けで、
belongs_to :followed, class_name: "User"
はフォローした相手への関連付けになっています。
このようにコードを書くことで、表14.1のようなメソッドを使うことが出来ます。
例えばactive_relationship.followerならそのフォロワーを表示できますし、active_relationship.followedならフォローした相手を表示することができます。この働きはbelongs_toによるものです。
→※重要:ちなみに、このリスト14.3のおかげで「Relationship.find(params[:id]).followed」のようなコードでユーザーを表示することもできます。
「.followed」「.following」メソッドを、Relationshipモデル(概念上のactive_relationshipとpasssive_relationshipも含む)に対して使えるようにすることが、このリスト14.3の役割だからです。
しかし、これだけでは図14.7の右側の赤い矢印(has_manyの部分)は実装されていません。
つまり、配列のようにして扱うことが出来ないということです。配列のように扱えないということは、include?メソッドのようにフォローしているユーザーをまとめて調べたり、findメソッドでユーザーを調べることができません。
(→配列として扱えない場合、表14.1の下の演習のように、個々のオブジェクトを一つ一つ確認することしかできません。あるいは、もっと複雑なコードを実装するとか……SQL分を書くとか……?)
これを可能にするために、図14.7の青いthroughと赤いhas_manyを実装する必要があります。
それが14.8というわけです。このリストがないと、不便だということです。
passive_relationshipsは簡単で、active_relationshipsと反対にした概念上のモデルを作っちゃおうというだけです。
リスト14.16の
<% @user ||= current_user %>
です。これは、homeページのときは自分のページになり、誰かのユーザーページのときにはそのユーザー(@user)が代入されるということです。
リスト14.19は、フォローしているときにはフォロー解除のボタン、フォローしていないときにはフォロー開始のボタンを表示する必要があるので、これであっています。
リスト14.21とリスト14.22なのですが、これはリスト14.33と合わせて、この章で一番難しいポイントでした。この部分に関しては、後で解説したいと思います。
リスト14.23の
<%= render 'follow_form' if logged_in? %>
は、このrenderによって、リスト14.19に飛びます。そしてそこから、
<% if current_user.following?(@user) %> <%= render 'unfollow' %> <% else %> <%= render 'follow' %>
によって、リスト14.21と14.22に飛んでいます。
リスト14.26の
<div class="user_avatars"> <% @users.each do |user| %> <%= link_to gravatar_for(user, size: 30), user %> <% end %> </div>
の部分は、図14.16の左下のたくさんのアイコンが並んでいるところです。
このdiv class="user_avatars"に関しては、リスト14.18のSCSS
.user_avatars {
のところが対応しています。
リスト14.31に
delete relationship_path(relationships(:one))
というコードがあって、この「:one」とはなんぞや?と少し考え込んでしまったのですが、これは、リスト14.28のfixturesで定義されています。
さて、問題のリスト14.33です。ここが14章の中で個人的には一番難しかったところでした。
このリストにおける
user = User.find(params[:followed_id])
と
user = Relationship.find(params[:id]).followed
の部分がが一体何をやっているのか、直感レベルでは分かるものの、どういった仕組みになっているのかがよく分かっていなくて、とても混乱しました。
(何をしたいのかも結果も分かるけど、中身の構造というか仕組みがよく分かっていませんでした。つまり、どこがどう連関しているのかという関係性です)
まず、上のコードを見てみましょう。これは「def create」のブロックに有り、上の説明書きにもある通り、リスト14.21から渡されています。
それではリスト14.21を見てましょう。
このリスト14.21の
<%= form_for(current_user.active_relationships.build) do |f| %>
これは一体なんぞやと。
このコードはどうして、「.create」ではなく、「.build」になっているのか???????????
という謎です。
まず、同じ用にbuildが出てきたリスト13.12を見てみます。ここでは上の方の説明で、慣習的に正しくマイクロポストを作成するためには、buildメソッドを使う必要があると書かれています。
また、次のようにも書かれています。
(
newメソッドと同様に、buildメソッドはオブジェクトを返しますがデータベースには反映されません。) 一度正しい関連付けを定義してしまえば、@micropost変数のuser_idには、関連するユーザーのidが自動的に設定されます。
buildメソッドはインスタンスを生成するメソッドであり、ここではまだデータベースには登録されていません。ただ、インスタンスとして一時的に保存しているわけです。
ではデータベースにはいつ保存するのかというと、リスト14.33のこのコードです。
current_user.follow(user)
これによって、データベースへと保存されます。
つまり、モデルの関連付けにおいては、モデル上のオブジェクトと、データベース上のデータの両方を操作する必要があります。
そのため、モデルの関連付けにおいては、習慣的にbuildメソッドが使われています。
【Rails】モデルの関連付けで用いられるbuildメソッドまとめ|TechTechMedia
https://techtechmedia.com/build-method-rails/
更に言い換えるなら、
・おそらくform_forの中には、新しくオブジェクトを作成するor既にあるオブジェクトを持ってくる必要がある。だから、リスト14.21と14.22はあのような形になっている。
・buildメソッドで送信すると、自動的にRelationshipsコントローラーでcreateブロックに移動する。deleteを付けると、destroyブロックに移動する。
・リスト14.33のRelationshipコントローラーの役割は、そのオブジェクトに対して操作すること&ページを移動させること。form_forで指定されたオブジェクトがないと、followやunfollowなどの操作ができない。
ということだと思います。
コントローラーが操作できるのは、送信されたオブジェクトに対してである、とも言えるかもしれません。
どうしてこんな二度手間みたいなことをやっているかというと……それはそういうものだと覚えるしかないのかも……。
さて、このリスト14.33にはもう一つの山場があります。それは、
① user = User.find(params[:followed_id])
のfollowed_idとは何か?
そして、
② user = Relationship.find(params[:id]).followed
はどうして、このようになっているのか?
まず、①のほうから見ていきましょう。これはまだ簡単です。
リスト14.21に次のようなコードがあります。
<div><%= hidden_field_tag :followed_id, @user.id %></div>
これが何を意味しているかというと、現在表示しているユーザー(@user.id)を、「:followed_id」として、隠れたタグとして送信するよ。という意味です。
そのためリスト14.33において、:followed_idに表示しているユーザーが代入され、それをcurrent_userがフォローをし、リダイレクトするという流れです。
では、②のほうはどうでしょうか。
まずここで思い出していただきたいのが、この記事の上の方に書いた「※重要」のところの文章です。
リスト14.3において、follewerとfollowedをbelongs_toで関連付けしました。
このことによって、Relationshipモデルの最後に、「.follower」や「.followed」を付けることによって、それだけでユーザーを呼び寄せることができるのです。
また、この②のコードのparams[:id]とは、おそらくログインしているユーザーのIDでしょう。
ですから、「RelationshipモデルからログインユーザーのIDを探し出して、それと一対一で対応しているフォロー相手のユーザーを抽出し、代入している」というのが②の挙動なわけです。
ここでひとつ疑問が生まれます。
「結局のところ、フォロー相手のユーザーを代入すれば良いのでは?」と。
つまり、リスト14.22は別の方法でも書けるはずです。
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id), html: { method: :delete }) do |f| %>
<div><%= hidden_field_tag :followed_id, @user.id %></div>
<%= f.submit "Unfollow", class: "btn btn-default" %>
<% end %>
そして、リスト14.33をちょっと変えて
def destroy user = User.find(params[:followed_id])
current_user.unfollow(user)
redirect_to user
end
にすれば、特に問題なく動くと思います。
つまり、同じ挙動をするための道筋は複数あると書きたかったのでした。
リスト14.38とリスト14.39へと飛びます。
これらコードでどうして、create.js.erbのほうにrender('users/unfollow')があって、destroy.js.erbのほうにrender('users/follow')があるのか、最初よく分からなかったのです。
これらのコードは、フォロー/アンフォローボタンを押した後に表示されるものを表しています。変更の結果を表示させているのです。
つまり、フォローボタンを押す(create)→フォローが実行される→アンフォローボタンが表示される(render)、という流れであるため、この3つ目の変更後の表示を引数として渡しているわけですね。
リスト14.46以降でSQLコードが書かれています。
らくださんのブログによると、リスト14.47のコードはリファクタリングできると書いてあります。
らくだ🐫にもできるRailsチュートリアル|14.3 | らくだ🐫のさいと
https://rakuda3desu.net/rakudas-rails-tutorial14-3/
こういう書き方もできるという参考になりました。勉強になります。感謝致します。
どうしてrailsチュートリアルにおいては、このような書き方をしていないのか。それについてちょっと考えてみました。
自分の推測なのですが、おそらく「SQL文を使ってSQLで検索するほうが速度が速いからではないか?」というのが自分の推測です。
今までの書き方でモデルからオブジェクトをまとめて検索するより、SQLでデータベースにアクセスして調べたほうが大量のデータを処理する場合には速度が速くなるから、railsチュートリアルはこの書き方を推奨している、というのが自分の推測です。
なかなか難しかったですが、なんとか終わらせることができました。
詰まったところ、難しかったところは他にもあった気がするので、書き忘れていることがあれば、後で追記しようと思います。
次回の記事では、railsチュートリアル全体を終わらせた感想を書いてみたいと思います。
いつものように、参考にさせていただいたサイト様を載せていきます。
順番は特に決まってないです。
今回はリンクの数がだいぶ多くなると思います。
Railsチュートリアル2周完了。1章から14章までの演習や解答をQiitaにまとめた | YUUKIのWebエンジニア道
https://webnewage.com/rails-tutorial
Ruby on Rails チュートリアル 完全攻略 概要と演習解答総まとめ - 新米パパの育児留学
https://mochikichi.hatenablog.com/entry/rails_tutorial_guide
らくだ🐫にもできるRailsチュートリアル|14.3 | らくだ🐫のさいと
https://rakuda3desu.net/rakudas-rails-tutorial14-3/
Railsチュートリアル第14章 - エンジニア志望のブログ
https://yuakun.hateblo.jp/entry/2021/07/23/170000
【Railsチュートリアル】第14章 ユーザーをフォローする② - Qiita
https://qiita.com/moutoonm342/items/94dc4f6271977836c3d7
Ruby on Rails チュートリアル 第14章 データモデルの関連付け(フォロー フォロー解除)フィードの実装など 演習 解答 - Qiita
https://qiita.com/bitcoinjpnnet/items/07ba89a6d329bd2893e5
静的型付け言語原理主義者によるRailsチュートリアル(第14章)
https://sore8sore104te.com/rails-tutorial-chapter14/
RubyのCGI.escapeとURI.encodeについて | 酒と涙とRubyとRailsと
https://morizyun.github.io/ruby/basic-uri-encode-cgi-escape.html
form_forでフォームを書く - 今日のごはんは素麺です
https://takkkun.hatenablog.com/entry/20090112/1231718637
railsチュートリアル挑戦記 第14章 ユーザーをフォローする|俺様|note
https://note.com/el93019205/n/n3554a102f1de
【Ruby】array.map(&:method)を理解する - Qiita
https://qiita.com/k-penguin-sato/items/420d7487b28b5d58cac4
[Ruby]Procオブジェクトについて - Qiita
https://qiita.com/nagata03/items/919b0457409cc755ff0f
Railsチュートリアル 第14章 ユーザーをフォローする - Relationshipモデル - Qiita
https://qiita.com/rapidliner00/items/545a0b3bacbc0286325f
Railsチュートリアル 第14章 ユーザーをフォローする - 演習「フォローをテストする」 - FollowingTestの問題点と、その改良 - Qiita
https://qiita.com/rapidliner00/items/1d6d96996e2b241248e3
Active Record クエリインターフェイス - Railsガイド
https://railsguides.jp/active_record_querying.html
Rails – form関係ヘルパーの基本 – TauStation
http://taustation.com/rails-form-helper-basics/
【Rails】paramsがなんなのかやっとわかった! - Web Marina
https://song-of-life.hatenablog.com/entry/2017/10/30/010819
【Rails】モデルの関連付けで用いられるbuildメソッドまとめ|TechTechMedia
https://techtechmedia.com/build-method-rails/
Rails new、build、create、saveの方法の違い - JPDEBUG.COM
https://jpdebug.com/p/573142
第14章Railsチュートリアル演習問題と解答まとめ - エンジニアになりたい肉体労働者
https://yukitoku-sw.hatenablog.com/entry/2019/11/09/011433
Rubyのto_sメソッドの使い方を現役エンジニアが解説【初心者向け】 | TechAcademyマガジン
https://techacademy.jp/magazine/22202
【第14章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学
https://mochikichi.hatenablog.com/entry/2017/03/25/210223
ruby on rails - The differences between .build, .create, and .create! and when should they be used? - Stack Overflow
https://stackoverflow.com/questions/403671/the-differences-between-build-create-and-create-and-when-should-they-be-us
14章 · Issue #26 · yutokyokutyo/rebuild_sample_app · GitHub
https://github.com/yutokyokutyo/rebuild_sample_app/issues/26
Railsチュートリアル 第14章 ユーザーをフォローする - Relationshipモデル - User/Relationshipの関連付け - Qiita
https://qiita.com/rapidliner00/items/2f68f3cda401c1c5227d
Rails アプリケーションを設定する - Railsガイド
https://railsguides.jp/configuring.html#action-view%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B
Ruby on Rails初心者向け テストについて - Qiita
https://qiita.com/yusuke_s221/items/603b6f20d96764e54494