###フォロワー
####受動的関係を使ってuser.followersを実装する
app/models/user.rb
class User < ApplicationRecord
has_many :microposts, dependent: :destroy
# 他のモデルとの間に「1対多」のつながり
# has_many関連付けが使われている場合、
# 「反対側」のモデルでは多くの場合belongs_toが使われます。
# dependent: :destroy
# ユーザーが破棄された場合
# 、ユーザーのマイクロポストも同様に破棄される。
has_many :active_relationships, class_name: "Relationship",
# :active_relationsshitpsデータモデル 能動的関係(フォローしているがフォローはされていない関係)
# class_name: テーブル名 "Relationship"
foreign_key: "follower_id",
# foreign_key: 外部キー
# データベースの二つのテーブルを繋げる
# Micropostモデルとrelationshipsモデルを繋げる
dependent: :destroy
# ユーザーが削除されるとマイクロポストも削除される
has_many :passive_relationships, class_name: "Relationship",
# passive_relationsshipsデータモデル 受動的関係(フォローされている)
# class_name: テーブル名 Relationsship
foreign_key: "followed_id",
# 外部キー テーブル同士の紐づけに用いるカラムのこと。
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
# :following followingデータモデル
# has_many :through関連付けは、他方のモデルと「多対多」のつながりを設定する場合によく使われます。
# この関連付けは、2つのモデルの間に「第3のモデル」(joinモデル)が介在する点が特徴です。
# フォローするには:active_relationshipsデータモデルが介在する
# source: following配列の元はfollowed idの集合である
has_many :followers, through: :passive_relationships, source: :follower
# folloersデータモデル
# userとfollowersの間にはpasseive_relationsshipsが介在する
# souce: 関連付けにおける「ソースの」関連付け名、つまり関連付け元の名前を指定します。
# 関連づけ名とは関連付けたいデータモデルの名前
# この場合 :followerが関連付けられる
.
.
.
end
####followersに対するテスト
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
test "should follow and unfollow a user" do
michael = users(:michael)
# テストユーザーを代入
archer = users(:archer)
assert_not michael.following?(archer)
# archerはmichaelをふぉろーしていないことを確認
michael.follow(archer)
# archerをフォロー
assert michael.following?(archer)
# フォローしているか?
assert archer.followers.include?(michael)
# archerのフォロワーの中にmichaelは入っているか?
michael.unfollow(archer)
assert_not michael.following?(archer)
end
end
####テスト
ubuntu:~/environment/sample_app (following-users) $ rails t
Running via Spring preloader in process 8487
Started with run options --seed 30377
63/63: [==============================] 100% Time: 00:00:08, Time: 00:00:08
Finished in 8.39715s
63 tests, 320 assertions, 0 failures, 0 errors, 0 skips
###演習
1.
コンソールを開き、何人かのユーザーが最初のユーザーをフォローしている状況を作ってみてください。最初のユーザーをuserとすると、user.followers.map(&:id)の値はどのようになっているでしょうか?
>> user = User.first
User Load (1.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2021-10-27 07:03:00", updated_at: "2021-10-27 07:03:00", password_digest: [FILTERED], remember_digest: nil, admin: true, activation_digest: "$2a$12$i2cTpO7yNDagp6oMpVyIq.lyvQ1yEuoSJsOj7G9qZaX...", activated: true, activated_at: "2021-10-27 07:03:00", reset_digest: nil, reset_sent_at: nil>
>> user2 = User.second
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ? [["LIMIT", 1], ["OFFSET", 1]]
=> #<User id: 2, name: "Isaias Ebert", email: "example-1@railstutorial.org", created_at: "2021-10-27 07:03:01", updated_at: "2021-10-27 07:03:01", password_digest: [FILTERED], remember_digest: nil, admin: false, activation_digest: "$2a$12$2NxDk9cW24wyZiN3f4LI7OI3D6NSgj7mvZ/huoEFRbE...", activated: true, activated_at: "2021-10-27 07:03:00", reset_digest: nil, reset_sent_at: nil>
>> user3 = User.third
User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ? [["LIMIT", 1], ["OFFSET", 2]]
=> #<User id: 3, name: "Freddy Hills Jr.", email: "example-2@railstutorial.org", created_at: "2021-10-27 07:03:01", updated_at: "2021-10-27 07:03:01", password_digest: [FILTERED], remember_digest: nil, admin: false, activation_digest: "$2a$12$LkEsBxgeobFCiJ3ssmWkZ.AxMinGX35D2x4yas988fL...", activated: true, activated_at: "2021-10-27 07:03:01", reset_digest: nil, reset_sent_at: nil>
>> user.active_relationships.create(followed_id: user2.id)
(0.1ms) begin transaction
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Relationship Create (2.5ms) INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["follower_id", 1], ["followed_id", 2], ["created_at", "2021-10-28 14:07:57.360763"], ["updated_at", "2021-10-28 14:07:57.360763"]]
(7.1ms) commit transaction
=> #<Relationship id: 2, follower_id: 1, followed_id: 2, created_at: "2021-10-28 14:07:57", updated_at: "2021-10-28 14:07:57">
>> user.active_relationships.create(followed_id: user3.id)
(0.1ms) begin transaction
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
Relationship Create (1.6ms) INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["follower_id", 1], ["followed_id", 3], ["created_at", "2021-10-28 14:08:02.784912"], ["updated_at", "2021-10-28 14:08:02.784912"]]
(9.7ms) commit transaction
=> #<Relationship id: 3, follower_id: 1, followed_id: 3, created_at: "2021-10-28 14:08:02", updated_at: "2021-10-28 14:08:02">
>> user.followers.map(&:id)
Traceback (most recent call last):
1: from (irb):17
NoMethodError (undefined method `followers' for #<User:0x000055e039bd2a80>)
Did you mean? follow
following
>> user.followers.map(&:id)
Traceback (most recent call last):
2: from (irb):18
1: from (irb):18:in `rescue in irb_binding'
NoMethodError (undefined method `followers' for #<User:0x000055e039bd2a80>)
>> user.followers.count
Traceback (most recent call last):
2: from (irb):19
1: from (irb):19:in `rescue in irb_binding'
NoMethodError (undefined method `followers' for #<User:0x000055e039bd2a80>)
>> user.followers.map(&:id)
Traceback (most recent call last):
2: from (irb):20
1: from (irb):20:in `rescue in irb_binding'
NoMethodError (undefined method `followers' for #<User:0x000055e039bd2a80>)
>> user.following.map(&:id)
User Load (0.3ms) SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? [["follower_id", 1]]
=> [2, 3]
上の演習が終わったら、user.followers.countの実行結果が、先ほどフォローさせたユーザー数と一致していることを確認してみましょう。
>> user.following.count
(0.2ms) SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? [["follower_id", 1]]
=> 2
user.followers.countを実行した結果、出力されるSQL文はどのような内容になっているでしょうか? また、user.followers.to_a.countの実行結果と違っている箇所はありますか? ヒント: もしuserに100万人のフォロワーがいた場合、どのような違いがあるでしょうか? 考えてみてください。
FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? [["follower_id", 1]]
=> 2
uesrsからrelationshipsに繋がれた。 フォローされているidカラムとフォローしているカラム
>> user.following.to_a.count
=> 2
SQL文が表示されない