0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】ユーザーのフォロー機能その1 UserモデルとRelationshipモデルの関連付け【Rails Tutorial 14章まとめ】

Posted at

##フォロー機能
特定のユーザーをフォローできるようにし、フォローしたユーザーのマイクロポストをフィードに表示できるようにする。

##Relationshipモデル
###能動関係と受動関係
ユーザーAがユーザーBをフォローしている場合、AにはBをフォローしている(following, follower)という能動関係がある。
逆に、BにはAにフォローされている(followed)という受動関係がある。

この関係を構築するために、Relationshipモデルを作成する。
Relationshipモデルには、フォローしているユーザーのIDを保存するfollower_idと、フォローされているユーザーのIDを保存するfollowed_idカラムがある。

$ rails generate model Relationship follower_id:integer followed_id:integer

Relationshipモデルからは頻繁にユーザーを検索することになるので、インデックスを追加する。

db/migrate/[timestamp]_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.0]
  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

最後の行は複合キーインデックスと呼ばれ、follower_idとfollowed_idの組み合わせがユニークであり、同じユーザーを2回フォローしたりすることがないようにしている。
$rails db:migrateしておく。

###UserとRelationshipの関連付け/外部キーと能動関係
UserモデルとMicropostモデルの関連付けはhas_manyとbelongs_toを使って実現していた。
Userモデルではhas_many :micropostsとすることで、Railsは対応するMicropostモデルを見つけることができる。
今回は能動関係を表す関連付けを:active_relationshipとしたいのだが、これだとActiveRelationshipモデルを探してしまうので、これがRelationshipモデルであることを明示する必要がある。

また、Micropostモデルではbelongs_to :userとすることで、Railsは対応するUserモデルを見つけることができる。
この時、Micropostモデルにはuser_id属性があるので、これをもとに正確なUserオブジェクトを見つけている。
user_id属性のような、2つのモデルを関連づける属性を外部キー(foreign key)と呼ぶ。
Railsにおける外部キーのデフォルトは<class>_idという形になっており、<class>の部分にはクラス(モデル)名を小文字にしたものが入る。
今回はフォローユーザーを見つけるためにfollower_idを外部キーとして使うのだが、Followerモデルは存在しないので、これが外部キーであることを明示する必要がある。

以上により、UserモデルのRelationshipモデルに対する関連付けは以下のようになる(能動関係)。

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  .
  .
  .
end

Userモデルは多くの能動関係(active_relationships、ユーザーをフォローしている)を持ち、それはfollower_idによってRelationshipモデルに関連づけられる。
また、ユーザーが削除されたらその関係も削除される(dependent: :destroy)。

次に、RelationshipモデルをUserモデルに関連づける。

app/models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
end

この関連付けにより、以下のようなメソッドが使えるようになる。
スクリーンショット 2019-12-04 20.56.42.jpg

###Relationshipモデルのバリデーション
Relationshipモデルのfollower_id属性とfollowed_id属性に存在性のバリデーションを追加する。

app/models/relationship.rb
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

テストも書いておく。

test/models/relationship_test.rb
require 'test_helper'

class RelationshipTest < ActiveSupport::TestCase

  def setup
    @relationship = Relationship.new(follower_id: users(:michael).id,
                                     followed_id: users(:archer).id)
  end

  test "should be valid" do
    assert @relationship.valid?
  end

  test "should require a follower_id" do
    @relationship.follower_id = nil
    assert_not @relationship.valid?
  end

  test "should require a followed_id" do
    @relationship.followed_id = nil
    assert_not @relationship.valid?
  end
end

ここでREDになるのは、自動生成されたRelationshipモデル用のfixtureファイルが原因である。
fixtureファイルを空にすればGREENになる。

##フォロー/被フォローの関連付け(能動関係)
###被フォローユーザー
Userモデルにhas_many throughを使って被フォローユーザー(followed)を関連づけ、被フォローユーザーを取得できるようにする。
フォローユーザー(follower)も被フォローユーザーも共にUserモデルのオブジェクトなので、Userモデル(follower)からRelationshipモデルを経由してUserオブジェクト(followed)を取得するという流れになる。
具体的には以下のコードになる。

has_many :followeds, through: :active_relationships

ユーザーは多くの被フォローユーザー(followeds)を持ち、RailsはRelationshipテーブルのfollowed_id(followedsからsを取った単数形)から各被フォローユーザーを取得する。
ここで、followedsというのは文法的に正しくないので、代わりにfollowingを使うことにする。
(過去分詞の代わりに現在分詞を使うことは、誤解を招く原因になると思うが...)

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  has_many :following, through: :active_relationships, source: :followed
  .
  .
  .
end

source:パラメータを使って、following=followedであることを明示する。
これにより、user.followingとすることで、被フォローユーザーを配列で取得できるようになる。

##フォロー関連のメソッド
ユーザーを簡単にフォローしたりフォロー解除できるように、followやunfollowメソッドを作成する。
また、following?論理値メソッドを作成し、フォロしているかどうかを確認できるようにする。

###メソッドのテスト
テストから書いていく。

test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  .
  .
  .
  test "should follow and unfollow a user" do
    michael = users(:michael)
    archer  = users(:archer)
    assert_not michael.following?(archer)
    michael.follow(archer)
    assert michael.following?(archer)
    michael.unfollow(archer)
    assert_not michael.following?(archer)
  end
end

①michaelがarcherをフォローしていないことをfollowing?メソッドで確認する。
②michaelがarcherをフォローする。
③フォローできていることを確認する。
④フォロー解除する。
⑤フォロー解除できていることを確認する。

###メソッドの実装
各メソッドを実装する。

app/models/user.rb
  def feed
    .
    .
    .
  end

  # ユーザーをフォローする
  def follow(other_user)
    following << other_user
  end

  # ユーザーをフォロー解除する
  def unfollow(other_user)
    active_relationships.find_by(followed_id: other_user.id).destroy
  end

  # 現在のユーザーがフォローしていたらtrueを返す
  def following?(other_user)
    following.include?(other_user)
  end

  private
  .
  .
  .

##フォロー/被フォローの関連付け(受動関係)
###フォローユーザー
Userモデルとactive_relationshipを関連づけ、被フォローユーザーを取得できるようになったので、逆にフォローユーザーを取得できるようにする。
これは能動関係の関連付けとちょうど逆のことをするだけでよい。

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships,  class_name:  "Relationship",
                                   foreign_key: "follower_id",
                                   dependent:   :destroy
  has_many :passive_relationships, class_name:  "Relationship",
                                   foreign_key: "followed_id",
                                   dependent:   :destroy
  has_many :following, through: :active_relationships,  source: :followed
  has_many :followers, through: :passive_relationships, source: :follower
  .
  .
  .
end

ここでsourceパラメータは不要だが、followedとの対称性を強調するために付けている。

###メソッドテストの追記
この関連付けによってfollowsメソッドを使えるようになり、フォローユーザーを被フォロワーユーザーから取得できるようになったので、テストに追記する。

test/models/user_test.rb
  test "should follow and unfollow a user" do
    michael  = users(:michael)
    archer   = users(:archer)
    assert_not michael.following?(archer)
    michael.follow(archer)
    assert michael.following?(archer)
    assert archer.followers.include?(michael)
    michael.unfollow(archer)
    assert_not michael.following?(archer)
  end

archerをフォローしているユーザーの中に、michaelがいるかどうかをinclude?メソッドで確認している。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?