Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rails Tutorial 第14章 #87

Closed
kenta0425 opened this issue Feb 3, 2021 · 5 comments
Closed

Rails Tutorial 第14章 #87

kenta0425 opened this issue Feb 3, 2021 · 5 comments
Assignees

Comments

@kenta0425
Copy link
Collaborator

WHY

WHAT

  • 第14章のユーザーをフォローするを理解し、sample_appを完成すること
@kenta0425 kenta0425 self-assigned this Feb 3, 2021
This was referenced Feb 3, 2021
@kenta0425
Copy link
Collaborator Author

kenta0425 commented Feb 4, 2021

ユーザーをフォローする

ユーザーをフォローする

  • 他のユーザーをフォロー・アンフォローできるソーシャルな仕組みの追加
  • フォローしているユーザーの投稿をステータスフィールドに表示する機能を追加

followers

  • あるユーザーをフォローしている(follower)すべての人のはuser.followesになる

following

  • あるユーザーがフォローしている(following)すべての人はuser.followingになる

フォロー

  • AがBをフォローしていても、BがAをフォローしてないという関係性が成り立つので、左右非対称な関係を見分けなければいけない
    -- フォローしている関係性をactive_relationshipとする

user(id: 1) 
↓ through
active_relationships([follower_id: 1, 1, 3, 7, 1, 2 ], [followed_id: 2, 7, 1, 2, 10, 1] )
↓ ↓ ↓ has_many
user_following{ user(id:2) }, { user(id:7) }, { user(id:10) }

user.1 がuser.2, user.7, user.10をフォローしている
user.3, user.2がuser.1をフォローしている
user.7 がuser.2をフォローしている

Relationshipモデル

  • 関連付けにreference型を使わない
    -- モデル名_idが使えないから
    -- follower_idfollowed_idを使っているから、Rails側がFollowerモデルやFollowedモデルがあり、そこと関連付けしていると解釈するため
 t.integer :follower_id
 t.integer :followed_id
  • follower_id followed_idはユニーク(一意性)であることを担保する
    • 30フォロワーがいたら30人の別々のユーザーからフォローされていることにするため
add_index :relationships, [:follower_id, :followed_id], unique: true

User/Relationshipの関連付け

  • 1人のユーザーに対して複数のリレーションシップがあるので、has_manyにする
    • active_relationshipsメソッドを作ってもらうが、 RailsがActiveRelationshipモデルを参照しないためにモデル名(Relationshipモデル)をオプションで指定する
  • リレーションシップは2人のユーザーの間の関係で、フォローしているユーザーとフォロワーの両方に属すので、belongs_toにする
  • foreign key (Micropostモデルのuser_idなど)
    • データベースの2つのテーブルを繋ぐときに、foreign keyの名前を使って、Railsは関係付けの推測を行う
    • Rails はデフォルトではforein keyの名前を<class>_idというパターンとして理解し、<class>に当たる部分からクラス名を推測する
has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"

メソッド一覧

  • active_relationship.follower
    • フォロワーを返す
  • active_relationship.followed
    • フォローしているユーザーを返す
  • user.active_relationships.create(followed_id: other_user.id)
    • userと紐付けて能動的関係を作成/登録する
  • user.active_relationships.create!(followed_id: other_user.id)
    • userを紐付けて能動的関係を作成/登録する(失敗時にエラーを出力)
  • user.active_relationships.build(followed_id: other_user.id)
    • userと紐付けた新しいRelationshipオブジェクトを返す

has_many through

  • has_manyはメソッドを作るメソッドで、定義するばメソッド(following)を呼び出せるようになる
  • followingメソッドはactiverelationshipsメソッドを実行し、そこで得られたRelationshipのデータに対してfollowedメソッドを実行した結果を返してくれるメソッド名
has_many :following, through: :active_relationships, source: :followed

followingの関連メソッド

  • ユーザーをフォローする
    • following(フォローしている集合)にユーザーを引数で渡すことで追加できる
def follow(other_user)
  following << other_user  # << 新たに配列に追加する
end
  • ユーザーをフォロー解除する
    • 引数に渡されたユーザーがactive_relationshipsfollowed_idに含まれてたらそれを削除する
def unfollow(other_user)
  active_relationships.find_by(followed_id: other_user.id).destroy
end
  • 現在のユーザーがフォローしてたらtrueを返す
    • 引数に渡されたユーザーがfollowingに入ってるかチェックできる
def following?(other_user)
  following.include?(other_user)
end

フォロワー

  • フォローされることをpassive_relationshipsとする
  • フォローで実装した物の逆のことを実装する

ルーティング

  • フォローの集合体とフォロワーの集合体のページを作るためのルーティング
  • resources :user の中にget: followingとすることでGETで使えるfollowingアクション作られ、/users/1/following
    というURLが使えるようになる
resources :users do
  member do
    get :following, :followers
  end
end

フォローボタン

  • 自分自身のプロフィールページではフォローボタンが表示されないようにする
<% unless current_user?(@user) %>
  • ログインしているユーザー(current_user)がactive_relationshipsをcreateする
  • @user.idは今見てるプロフィールページのユーザーのidが入る
<%= form_with(model: current_user.active_relationships.build, local: true) do |f| %>
  <div><%= hidden_field_tag :followed_id, @user.id %></div>

アンフォロー

  • ログインしているユーザー(current_user)のactive_relationshipsの中から今見てるプロフィールページのユーザーのデータを引っ張ってきて、そのデータに対して、deleteリクエストを送る
<%= form_with(model: current_user.active_relationships.find_by(followed_id: @user.id),
            html: { method: :delete }, local: true) do |f| %>

followingfollowersアクション

  • 1つのテンプレートを2つのアクションで作り出す
  • 呼び出すアクションによって表示される情報をかえる
def following
  @title = "Following"
end
def followers
  @title = "Followers"
end

Relationshipのcreateアクション

  • フォローボタンフォームのhidden_field_tagで送られてきたfollowed_iduserに代入し、それuserfollowの引数に渡す
user = User.find(params[:followed_id])
current_user.follow(user)

Relationshipのdestroyアクション

  • Relationshipのデータのidを取得し、belongs_toで生成したfollowedからuserオブジェクトを引っ張ってきて、unfollowの引数に渡す
user = Relationship.find(params[:id]).followed
current_user.unfollow(user)

Ajax (非同期通信)

  • webページのフォローボタンのみを更新する
  • webページからサーバーに非同期で、ページを移動することなくリクエストを送信することができる
  • form_withの一部を変える
form_with(model: ..., local: true)

form_with(model: ..., remote: true)
  • コントローラのアクションを変える
  • respond_toif文のような感じでhtml(HTTP)のリクエストがきたらformat.htmlを実行し、JavaScript(XHR)のリクエストがきたらformat.jsを実行する
  • format.jsのあと何も渡されてない時は、デフォルトのapp/views/relationships/create.js.erbが反応する
  • View側でuserを呼び出したいので、インスタンス変数@userに変える
def create  
  @user = User.find(params[:followed_id])
  current_user.follow(@user)
  respond_to do |format|
    format.html { redirect_to @user }
    format.js
  end
end

ステータスフィールド

  • 自分のタイムラインにフォローしてるユーザーの投稿も載せるようにする
  • SQLなどデータベースへの問い合わせは1つにまとめる方がよい
  • マイクロポストの集合から以下の条件に合うものすべてを取得して欲しい
    • マイクロポストのユーザーidが自分がフォローしているユーザーid、もしくはそのユーザーidが自分自身の場合
SELECT * FROM microposts
WHERE user_id IN (<list of ids>) OR user_id = <user id>
  • 集合体を呼び出す(フォローしてるユーザーの集合体など)
@user.following.map(&: id)

↓ こちらも同じく呼び出せる

@user.following_ids

サブセレクトで1つの問い合わせに

  • 文字列を式展開する
  • relationshipsテーブルの中から、follower_idに自分のidを当て込み、followed_id(フォローしているユーザーのid)を引っ張ってくる
following_ids = "SELECT followed_id FROM relationships
                     WHERE follower_id = :user_id"
    Micropost.where("user_id IN (#{following_ids})
                     OR user_id = :user_id", user_id: id)

@kenta0425
Copy link
Collaborator Author

演習

  • Q. id=1のユーザーに対してuser.following.map(&:id)を実行すると、結果はどのようになるか
    A. user.1がフォローしているuser.id(user.2, user.7, user.10, user.8)を返す

  • Q. id=2のユーザーに対してuser.followingを実行すると、結果はどのようになるか
    A. user.2がフォローしているuser.1が返ってくる

  • Q. コンソールを開き、createメソッドを使ってActiveRelationshipを作ろう
    A.

=> #<Relationship id: 1, follower_id: 1, followed_id: 2, created_at: "2021-02-04 01:00:05", updated_at: "2021-02-04 01:00:05">
  • Q. コンソールを開き、最初のユーザーをuserし、何人かのユーザーが最初のユーザーをフォローしている状況を作ったら、user.followers.map(&:id)の値はどうなっているか
    A.
=> [2, 3]
  • Q. user.followers.countを実行した結果とuser.followers.to_a.countの実行結果と違っている箇所はありますか?
    A. SQL文が出力されないので、時間が短い。

  • Q. プロフィールページに表示されている統計情報に対して、テストを書いてみよう
    A.

assert_match @user.followers.count.to_s, response.body
assert_match @user.following.count.to_s, response.body
  • Q. xhr: trueがある行のうち、片方のみを削除するとどういった結果になり、この問題の原因は何か
    A.
"@user.following.count" didn't change by 1.
        Expected: 3
          Actual: 2

postリクエストを送信していないため、フォロー数が変わっておらずテストが失敗する

  • Q. マイクロポストのidが正しく並んでいると仮定して、user.feed.map(&:id)を実行すると、どのような結果が表示されるのか
    A. default_scope ->  { order(created_at: :desc) } と設定しているので、マイクロポストの投稿時間を基準に降順で表示される

  • Q. フォローしているユーザーの投稿を含めないようにするにはどうすれば良いか
    A.

def feed
  Micropost.where("user_id = ?", id)
end
  • Q. フォローしていないユーザーの投稿を含めるためにはどうすれば良いか
    A.
def feed
  Micropost.all
end

@haruhiko95
Copy link
Collaborator

確認事項

  • 結果としてuser.id(user.2, user.7, user.10, user.8)が返ってきたということでしょうか?
    Q. id=1のユーザーに対してuser.following.map(&:id)を実行すると、結果はどのようになるか
    A. user.1がフォローしているuser.id(user.2, user.7, user.10, user.8)を返す
    
  • 結果として何が表示されましたか?
    Q. マイクロポストのidが正しく並んでいると仮定して、user.feed.map(&:id)を実行すると、どのような結果が表示されるのか
    A. default_scope ->  { order(created_at: :desc) } と設定しているので、マイクロポストの投稿時間を基準に降順で表示される
    

@kenta0425
Copy link
Collaborator Author

確認事項

  • 結果としてuser.id(user.2, user.7, user.10, user.8)が返ってきたということでしょうか?
    Q. id=1のユーザーに対してuser.following.map(&:id)を実行すると、結果はどのようになるか
    A. user.1がフォローしているuser.id(user.2, user.7, user.10, user.8)を返す
    

下記のuserがフォローしているユーザーidの値が返ってきます

user.following.map(&:id)
=> [2, 7, 8, 10]
  • 結果として何が表示されましたか?
    Q. マイクロポストのidが正しく並んでいると仮定して、user.feed.map(&:id)を実行すると、どのような結果が表示されるのか
    A. default_scope ->  { order(created_at: :desc) } と設定しているので、マイクロポストの投稿時間を基準に降順で表示される
    

下記のようにマイクロポストのidが降順で表示されます。

user.feed.map(&:id)
=> [300, 299, 298, .... 5, 4, 3, 1]

@haruhiko95
Copy link
Collaborator

haruhiko95 commented Feb 7, 2021

👍
問題文は実行結果を求めているので、[2, 7, 8, 10]といったint配列として答えるのが適切だと思います🤔

[2, 7, 8, 10]に対して、人は「user.1がフォローしているuser.idの配列」という解釈ができますが、Rubyという言語処理系の中では、user.idという情報は欠落していて単なるintの配列にしかすぎないので、そのような違いは認識しておくのが良いと思います🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants