Rails チュートリアル following followerの関係について

f:id:happy_teeth_ago:20180822124303p:plain

この画面の左側のfollowing followerの数はどのようにして表示されているか?を説明します。

とりあえずアドレスがusers/4なのでUsersControllerのshowが呼ばれているはず

UsersController

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

userのidを取得

Viewの左画面

   <aside class="col-md-4">
            <section>
                <%= render 'shared/user_info' %>
            </section>
            <section>
                <%= render 'shared/stats' %>
            </section>
            <section>
                <%= render 'shared/micropost_form' %>
            </section>
        </aside>
        <div class="col-md-8">
            <h3>Micropost Feed</h3>
            <%= render 'shared/feed' %>
        </div>

真中部分のここが数字を表示している。

                <%= render 'shared/stats' %>
   

'shared/stats'が呼ばれている
Viewの中のパーシャル(部分テンプレート)を見て見ます。

_shared/stats

<% @user ||= current_user %>
<div class="stats">
    <a href="<%= following_user_path(@user) %>">
    
        <strong id="following" class="stat">
            <%= @user.followed_users.count %>
        </strong>
        following
        </a>
        <a href="<%= followers_user_path(@user) %>">
            <strong id="followers" class="stat">
                <%= @user.followers.count %>
            </strong> 
            followers
        </a>
</div>

<%= @user.followed_users.count %>で数字を表示

followed_usersはどこから来たのか?このブログのポイント

Userモデルに関連を宣言

class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :relationships, foreign_key: "follower_id", dependent: :destroy
  has_many :followed_users, through: :relationships, source: :followed

最後の行has_many :followed_users, through: :relationships, source: :followed

この行がポイント Userモデルはfollowed_usersという、プロパティを持つようになる。 それはrelationshipsテーブルを用いて関連付けられる。

そしてfollowedモデルを所有できるようになります。

followedモデルって何?
どこにも書いてないよ?

userテーブルのどこにもその様なカラムはない。

ないのになぜ、アクセスできるのか?

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string "current_sign_in_ip"
    t.string "last_sign_in_ip"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "name"
    t.boolean "admin", default: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true

Relationshipのモデル

followedというテーブルと関連づいているように見える。しかし実態はUser

class Relationship < ApplicationRecord
    belongs_to :follower , class_name: "User"
    belongs_to :followed , class_name: "User"
    validates :follower_id, presence: true
    validates :followed_id, presence: true
end

has_many :followed_users, through: :relationships, source: :followed つまりこれは

relationshipsという中間テーブルを通して、followedを所有することになります。

少し難しいかも?
ポイントは、follower followedも同じuserテーブルを参照していることにあります。
自分自身のテーブルを参照しているのですね。
2つのユーザーが同名の外部キーを持つことはできません。
それで、何らかの方法でkeyの名前を変更しないといけない。

followerとしてフォローするのと
followedとしてフォローされているものは違う2つの値をそれぞれ持つのです。

routesファイルを見てみます。

Rails.application.routes.draw do
   devise_for :users, :controllers => {
    :registrations => "registrations"
  }
    resources :users, only: [:show, :index, :destroy] do
    member do
      get :following, :followers
      end
  end

これによりルーティングにより下記のrouteが定義されます。

f:id:happy_teeth_ago:20180822184635p:plain 画像参照元 Ruby on Rails チュートリアル:実例を使って Rails を学ぼう

なぜこれで行けるかというと、

Userコントローラーfollowingメソッドにより、followed_idカラムを取得しています。

def following?(other_user)
    relationships.find_by(followed_id: other_user.id)
  end
  
  def follow!(other_user)
    relationships.create!(followed_id: other_user.id)
  end

これにより、取得できるのですね。

Relationshipテーブルにはfollowed_idというカラムがあります。

また、余談ですがindexも設定してあります。

  create_table "relationships", force: :cascade do |t|
    t.integer "follower_id"
    t.integer "followed_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["followed_id"], name: "index_relationships_on_followed_id"
    t.index ["follower_id", "followed_id"], name: "index_relationships_on_follower_id_and_followed_id", unique: true
    t.index ["follower_id"], name: "index_relationships_on_follower_id"
  end

インデックスを簡単に言うと対象のカラムのデータを取り出し、高速に検索できるように手を加えて保存しておいたものです

何度も頻繁に使うカラムだから、高速で検索できるようにしておいたのですね。

マイグレーションファイル生成時に設定しています。

class CreateRelationships < ActiveRecord::Migration[5.1]
  def change
    create_table :relationships do |t|
      t.integer :follower_id
      t.integer :followed_id

      t.timestamps
    end
    add_index :relationships, :follower_id
    add_index :relationships, :followed_id
    add_index :relationships, [:follower_id, :followed_id], unique: true
  end
end

ここで add_index :relationships, [:follower_id, :followed_id], unique: true
とあるので、あるユーザーが同じユーザーを複数回フォローすることを防ぐ事ができます。

流れをまとめると

UsersControllerのshowが呼ばれる

Viewには<%= @user.followed_users.count %>とある

followed_usersはどこから来たのかというと

Userモデルに関連付けがある。

has_many :followed_users, through: :relationships, source: :followed

Userモデルはfollowed_usersにアクセスできる。 それはrelationshipsテーブルを用いて関連付けられる。

そしてfollowedモデルを所有できるようになります。

relationshipsモデル。 実態はUserモデルとなっている。

belongs_to :followed , class_name: "User"

Userコントローラーの関数にて取得できる。

def following?(other_user)
    relationships.find_by(followed_id: other_user.id)
  end

ソースをきちんと読むのは大切ということがわかった。 間違えている点がありましたら、ご指導よろしくおねがいします。