Railsチュートリアル14章で大事なとこメモっときます
テストコードはRspecで書いています。
フォローとフォロワーの関係ややこしいのでまとめる。
userモデル
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :followers, through: :passive_relationships, source: :follower
has_many :following, through: :active_relationships, source: :followed
active_relationshipsのforeignキーはfollower_idで逆じゃないのか?ってなったがこれで正しい。
これは片方(逆の関係であるもの)を固定することによって片方の情報を持ってきている。
図を見た方がわかりやすい(14.7)
active
followed | folower
1 2
2 1
3 1
4 1
foreignキーでfollewer_idを指定する current_user(今回であれば1)によってフォローしているfollowedの情報を取ってくる。
has_many :followers, through: :passive_relationships, source: :follower
上と考え方一緒。逆の関係
図(14.9)
has_many active_relationshipsとhas_many following両方いる理由
has_many active_relationshipsではfollower_idとfollowed_idの2つの関係を取ってきている。
has_many followingでfollowed_idの情報だけ取ってきている。
そこからnameやemailなどuserの情報を持ってこれる。
これは多対多 中間テーブルの考え方 と一緒
---------------------------------------------------------------------------------------
Ajax 非同期通信
form_withをlocal trueからremote trueに変更する。
_follow.html.erb
<%= form_with(model: current_user.active_relationships.build, remote: true) do |f| %>
ここ!!
<div><%= hidden_field_tag :followed_id, @user.id %></div>
<%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>
_unfollow.html.erb
<%= form_with(model: current_user.active_relationships.find_by(followed_id: @user.id),
html: { method: :delete }, remote: true) do |f| %>
ここ!!
<%= f.submit "Unfollow", class: "btn" %>
<% end %>
relationshipsコントローラー
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
@user = User.find(params[:followed_id])
# ここ@userに変わっているので注意! ここ注意!!
current_user.follow(@user)
respond_toメソッドを使って
ajaxに対応させる
respond_to do |format|
下の文はif文のような作り。どちらか1つ
format.html { redirect_to @user }
format.js
end
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
end
javascriptが無効になっていた時の設定
config/application.rb
require_relative 'boot'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module TutorialApp
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 6.0
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
config.action_view.embed_authenticity_token_in_remote_forms = true ここ追加
end
end
app/views/relationships/create.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>");
$("#followers").html('<%= @user.followers.count %>');
escape_javascriptメソッド
JavaScriptファイル内にHTMLを挿入するときに実行結果をエスケープするために必要
app/views/relationships/destroy.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>");
$("#followers").html('<%= @user.followers.count %>');
---------------------------------------------------------------------------------------
テストコード(Rspec)
relationshipモデル
RSpec.describe Relationship, type: :model do
before do
@user = FactoryBot.create(:user)
@other_user= FactoryBot.create(:user)
@relationship = Relationship.new(follower_id:@user.id,
followed_id:@other_user.id)
relationshipはfactory_
bot内に記述書いてないです。
end
it "should be valid" do
expect(@relationship).to be_valid
end
it "should require a follower_id" do
@relationship.follower_id = nil
expect(@relationship).not_to be_valid
end
it "should require a followed_id" do
@relationship.followed_id = nil
expect(@relationship).not_to be_valid
end
end
system
follows_spec.rb
RSpec.describe "Relationships", type: :system do
before do
@user = FactoryBot.create(:user)
@other_user = FactoryBot.create(:user)
@other_users=FactoryBot.create_list(:user, 10)
# 下でフォロー、フォロワーの関係を作っている
# 一番でいうとfollowing_idは@user これはactive_relationship時に
# foreignキーとして設定してる(モデル見ればわかる)
ここでフォロー・フォロワーの関係を作る
@other_users[0..9].each do |other_user|
@user.active_relationships.create!(followed_id: other_user.id) userがother_userをフォロー
ここ大事 @user.とすることで follower_idは@userになる
@user.passive_relationships.create!(follower_id: other_user.id)other_userがuserをフォロー
end
end
it 'following page' do
log_in(@user)
visit following_user_path(@user)
expect(@user.following).not_to be_empty
expect(page).to have_content(@user.following.count)
@user.following.each do |user|
expect(page).to have_content(user.name)
expect(page).to have_link href: user_path(user)
end
end
it 'follower page' do
log_in(@user)
visit followers_user_path(@user)
expect(@user.following).not_to be_empty
expect(page).to have_content(@user.followers.count)
@user.followers.each do |user|
expect(page).to have_content(user.name)
expect(page).to have_link href: user_path(user)
end
end
it 'follow unfollow' do 下にrequestで4つ書いてるけどsystemでも書いてみた
log_in(@user)
visit user_path(@other_user)
# expect(page).to have_content("Follow")
expect do
click_on 'Follow'
sleep 1 ajaxなのでsleep使って少し待ってあげる
end.to change{Relationship.count}.by(1)
# expect(page).not_to have_content("Follow")
# expect(page).to have_content("Unfollow")
expect do
click_on 'Unfollow'
sleep 1
end.to change{Relationship.count}.by(-1)
end
end
RSpec.describe "Microposts", type: :request do
before do
@user = FactoryBot.create(:user)
@other_user = FactoryBot.create(:user)
end
it "create should require logged-in user" do
expect do
post relationships_path
end.to change{Relationship.count}.by(0)
expect(response).to redirect_to login_path
end
it "destroy should require logged-in user" do
@follow=@user.active_relationships.create!(followed_id: @other_user.id)
expect do
delete relationship_path(@follow)
end.to change{Relationship.count}.by(0)
expect(response).to redirect_to login_path
end
it "should follow a user the standard way" do ここ以降4つは上のsystemで書いた
follow unfollowと内容一緒
log_in_as(@user)
expect do
post relationships_path, params: { followed_id: @other_user.id }
end.to change{Relationship.count}.by(1)
end
it "should follow a user with Ajax" do
log_in_as(@user)
expect do
post relationships_path, xhr: true, params: { followed_id: @other_user.id }
end.to change{Relationship.count}.by(1)
end
it "should unfollow a user the standard way" do
log_in_as(@user)
# @user.follow(@other_user)
@follow=@user.active_relationships.create!(followed_id: @other_user.id)
expect do
delete relationship_path(@follow)
end.to change{Relationship.count}.by(-1)
end
it "should unfollow a user with Ajax" do
log_in_as(@user)
# @user.follow(@other_user)
@follow=@user.active_relationships.create!(followed_id: @other_user.id)
expect do
delete relationship_path(@follow), xhr: true
end.to change{Relationship.count}.by(-1)
end
end
RSpec.describe "Microposts", type: :system do
before do
@user = FactoryBot.create(:user)
@other_user = FactoryBot.create(:user)
@other_user2 = FactoryBot.create(:user)
それぞれmicropostを10こ生成する
10.times do
content = Faker::Lorem.sentence(word_count: 5)
@user.microposts.create!(content: content)
end
10.times do
content = Faker::Lorem.sentence(word_count: 5)
@other_user.microposts.create!(content: content)
end
10.times do
content = Faker::Lorem.sentence(word_count: 5)
@other_user2.microposts.create!(content: content)
end
end
it "feed should have the right posts" do
log_in(@user)
visit user_path(@other_user)
# expect(page).to have_content("Follow")
expect do
click_on 'Follow'
sleep 1
end.to change{Relationship.count}.by(1)
visit root_path
@user.microposts.each do |micropost| eachなしで@user.microposts.contentとしてたけどダメ
それだとどれを指しているのかわからないからeach使って
1つ1つcontentを表示する感じで
expect(page).to have_content(micropost.content)
end
@other_user.microposts.each do |micropost|
expect(page).to have_content(micropost.content)
end
@other_user2.microposts.each do |micropost|
expect(page).not_to have_content(micropost.content)
end
end
end