Railsチュートリアル14章

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 'rails/all'

# 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モデル

require 'rails_helper'

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

require 'rails_helper'

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 }
                ajaxで書いてる
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