ユーザーをフォローする
まずはRelationshipモデルを作っていこう
$ rails generate model Relationship follower_id:integer followed_id:integer
作成されたマイグレーションファイルにインデックスを書き足していく。
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回同じユーザーをフォローするとかはできなくなる。
UserとRelationshipの関連付け
まずは以下を見てみよう
class Relationship < ApplicationRecord
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
end
belongs_to :followerとすると、Relationshipのfollower_idとFollowerクラスのidカラムを結びつけますよーという意味になる。
ただ、結びつけたいのはUserクラスのidカラムなので、オプションでclass_name: "User"とすることで、follower_idとUserクラスのidカラムが結びつく。
では、Userモデルのファイルにはどのように書けば良いのだろうか?
class User < ApplicationRecord
has_many :microposts, dependent: :destroy
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
.
.
.
end
まず、has_many :active_relationshipsとすると、active_relationshipsクラスを探そうとするが、そんなクラスはないのでオプションでclass_name: "Relationship"と指定している。逆に言えば、active_relationshipsの部分はなんでも良いのである。
次は、foreign_key: "follower_id"の部分だ。
これを指定しないと、UserクラスのidカラムをRelationshipクラスのどのカラムと関連づけるのかがわからなくなってしまう。(デフォルトではuser_idカラムと関連付けようとする)
なので、外部キーとして、Userクラスのidカラムと関連づけるカラムを指定してあげている。
これにより、@user.active_relationships
というような参照方法が可能になる。
また、@relationship.follower
という参照方法も可能になるってこと。
逆に言えば、belongs_toやhas_manyはメソッドを定義するメソッドであるとも言える。
(このケースだと、active_relationshipメソッド、followerメソッド)
なので、@user.active_relationships.first.followed
とすると、@userが最初にフォローしたユーザーが返ってくるということになる。
ここで全く関係ない余談
&.(ぼっち演算子)はレシーバーであるオブジェクトに対してあるメソッドを実行した時、そのオブジェクトがnilの場合、nilを返すことでエラーを出さなくしています。&.(ぼっち演算子)とはレシーバーであるオブジェクトがnilでなければそのまま結果を返し、nilの場合はnilを返すメソッドなのです。
もっと簡潔に
@user.active_relationships.first.followedだと長いのでどうにかできないか
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
これは、@user.active_relationships.followedを
followingメソッドを定義することにより、
@user.followingというように簡潔にかけるようにする。
(active_relationshipsメソッドを経由してfollowedメソッドを実行するのをfollowingと名付けますよーってこと)
relationshipモデルのバリデーションのテストをする
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
次はバリデーションを設定する
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
さらに、生成されたRelationship用のfixtureでは、マイグレーション (リスト 14.1) で制約させた一意性を満たすことができません。ということで、ユーザーのときと同じで (リスト 6.31でfixtureの内容を削除したように)、今の時点では生成されたRelationship用のfixtureファイルも空にしておきましょう
ちなみにテストが全て落ちるというときは、fixtureを見た方が良い。
そもそも、fixtureのサンプルデータが間違っており、dbにそれを伝えたことで全てのテストが落ちるらしい。
これでテストは通る。
その他のメソッド定義
次にfollowingなどの便利メソッドをテストする
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
この状態では、まだ定義してないメソッドがあるので、それを定義していかないといけない。
class User < ApplicationRecord
.
.
.
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
.
.
.
end
少しわかりにくいのは、
def follow(other_user)
following << other_user
end
これは、そもそもfollow(other_user)メソッドがインスタンスメソッドなので、
self.following << other_user
の省略形だったことがわかる。
また、self.followingはフォローしているユーザーの配列を返すので、<<で問題ない。
ただ、どうやってrelationshipインスタンスが生成されるか、dbに保存されるかわからないので、安ラボの動画で書いてあるコードに変更。
def follow(other_user)
self.active_relationships.create(followed_id: other_user.id)
end
また、
def following?(other_user)
following.include?(other_user)
end
include?メソッドは配列の要素に引数が含まれているかを判断してくれるメソッド。
余談:ダックタイピング
改良前
class EmploymentHandler
def work(employees)
employees.each do |employee|
case employee
when Staff then employee.work
when Manager then employee.work
end
end
end
end
class Staff
def work
do_clean_up
end
def do_clean_up
# ...
end
end
class Manager
def work
do_check
end
def do_check
# ...
end
end
ダックタイピングを使うと、
class EmploymentHandler
def work(employees)
employees.each do |employee|
employee.work
end
end
end
class Staff
def work
do_clean_up
end
def do_clean_up
# ...
end
end
class Manager
def work
do_check
end
def do_check
# ...
end
end
when case文がいらなくなったし、workメソッドを持っているクラスであれば、work(employees)メソッドを使えるようになった。
workメソッドを持っているということが、「ガーと鳴けば」に当たるのでは?
フォロワーを考える
これはフォローの時と逆のことをすれば良い
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
これで自分のフォロワーが@user.followersで参照できるようになったので、
テストを書いていこう。
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)
assert archer.followers.include?(michael)
michael.unfollow(archer)
assert_not michael.following?(archer)
end
end
UIを実装していこう
これでメソッドなどは定義できたので、UIを実装していく。
まずはフォローのサンプルデータを作るために
# リレーションシップ
users = User.all
user = users.first
following = users[2..50]
followers = users[3..40]
following.each { |followed| user.follow(followed) }
followers.each { |follower| follower.follow(user) }
ちなみに、followingとfollowersはローカル変数?
$ rails db:migrate:reset
$ rails db:seed
ルーティング設定
次にフォローしてる一覧ページとフォローされてる一覧ページを作るためのルーティングを設定していく。
urlは
/users/1/following や /users/1/followersのような形にしたい。
それを踏まえると、
Rails.application.routes.draw do
root 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
resources :users do
member do
get :following, :followers
end
end
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resources :microposts, only: [:create, :destroy]
end
memberメソッドを使うと、
/users/:id/...
この...に何を入れますか?というのを書き足すことができる
じゃあ、followingアクションとfollowersアクションはどこに作るの?
Usersコントローラに書けば良い。
statsパーシャルを作る。
次にフォロワーなどの統計情報を表示するstatsパーシャルを作る。
<% @user ||= current_user %>
<div class="stats">
<a href="<%= following_user_path(@user) %>">
<strong id="following" class="stat">
<%= @user.following.count %>
</strong>
following
</a>
<a href="<%= followers_user_path(@user) %>">
<strong id="followers" class="stat">
<%= @user.followers.count %>
</strong>
followers
</a>
</div>
そしたら、できたパーシャルをhomeページ(ログイン時)と、プロフィールページに挿入しよう。
その前に、なぜ<% @user ||= current_user %>のようなコードになるか見ていこう。
これは、static_pageコントローラのhomeアクションとuserコントローラのshowアクションで定義されているインスタンス変数が異なるからである。
showアクションには@userが定義されているが、static_pageコントローラでは定義されていない。なので、@userがいなければcurrent_userメソッドを呼び出すようにしている。
また、current_userメソッドはapplicationコントローラにmoduleをincludeしているので使える。
では、実際に埋め込んでいこう
```app/views/static_pages/home.html.erb
<% if logged_in? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
<section class="micropost_form">
<%= render 'shared/micropost_form' %>
</section>
</aside>
<div class="col-md-8">
<h3>Micropost Feed</h3>
<%= render 'shared/feed' %>
</div>
</div>
<% else %>
.
.
.
<% end %>
次にcssを整えてあげる
.
.
.
/* sidebar */
.
.
.
.gravatar {
float: left;
margin-right: 10px;
}
.gravatar_edit {
margin-top: 15px;
}
.stats {
overflow: auto;
margin-top: 0;
padding: 0;
a {
float: left;
padding: 0 10px;
border-left: 1px solid $gray-lighter;
color: gray;
&:first-child {
padding-left: 0;
border: 0;
}
&:hover {
text-decoration: none;
color: blue;
}
}
strong {
display: block;
}
}
.user_avatars {
overflow: auto;
margin-top: 10px;
.gravatar {
margin: 1px 1px;
}
a {
padding: 0;
}
}
.users.follow {
padding: 0;
}
/* forms */
.
.
.
フォローボタンの設置
これは、自分以外のユーザーのプロフィールページで、フォローしてなかったらフォローボタンが、フォローしたらフォロー解除ボタンが表示されるようにする。
これをdryに書くにはフォローボタンをそのままパーシャルにすると便利。
具体的には、フォローボタン、アンフォローボタン、その二つをif文で表示するfollow_formパーシャルの計3つを作る。
<% unless current_user?(@user) %>
<div id="follow_form">
<% if current_user.following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
<% unless current_user?(@user) %>
はもし@userが自分と同じなら、フォローボタンを表示しないようにする。
次はfollowパーシャルと、unfollowパーシャルを作成する。
<%= form_for(current_user.active_relationships.build) do |f| %>
<div><%= hidden_field_tag :followed_id, @user.id %></div>
<%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn" %>
<% end %>
unfollowの方は、そのままform_forの引数にインスタンスを渡すと、patchリクエストになってしまうので、html: {method: :delete}としている。
三つのパーシャルができたので、follow_formパーシャルをusersのshowアクションのページに設置しよう。
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
</aside>
<div class="col-md-8">
<%= render 'follow_form' if logged_in? %>
<% if @user.microposts.any? %>
<h3>Microposts (<%= @user.microposts.count %>)</h3>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
そしたら、relationshipsコントローラのcreateアクションとdestroyアクションのルーティングを設定しておこう。
Rails.application.routes.draw do
root 'static_pages#home'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
get 'login' => 'sessions#new'
post 'login' => 'sessions#create'
delete 'logout' => 'sessions#destroy'
resources :users do
member do
get :following, :followers
end
end
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resources :microposts, only: [:create, :destroy]
resources :relationships, only: [:create, :destroy]
end
フォロワーやフォローしてる人一覧が出るページを作る。
そしたら、次はrelationshipsコントローラにdestroyアクションを定義していこう。
また、別々のアクションなのに、同じテンプレートを表示するように実装していく。
/users/id/followingと/users/id/followersで同じビューを使うってこと。
では、早速作っていこう
TDDで作っていくので、
require 'test_helper'
class UsersControllerTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
@other_user = users(:archer)
end
.
.
.
test "should redirect following when not logged in" do
get following_user_path(@user)
assert_redirected_to login_url
end
test "should redirect followers when not logged in" do
get followers_user_path(@user)
assert_redirected_to login_url
end
end
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
:following, :followers]
.
.
.
def following
@title = "Following"
@user = User.find(params[:id])
@users = @user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
@title = "Followers"
@user = User.find(params[:id])
@users = @user.followers.paginate(page: params[:page])
render 'show_follow'
end
private
.
.
.
end
まずは、共通の変数をもつ、followingアクションと、followersアクションを定義する。
次に、フォローしているユーザーとフォロワーの両方を表示するshow_followビューを定義する。
注意点としては、パーシャルではなく、usersリソースのビューであるという点だ。
これで、異なるアクションから同じビューを呼び出し、内容をごっそり変えるというテクニックができる。
<% provide(:title, @title) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= gravatar_for @user %>
<h1><%= @user.name %></h1>
<span><%= link_to "view my profile", @user %></span>
<span><b>Microposts:</b> <%= @user.microposts.count %></span>
</section>
<section class="stats">
<%= render 'shared/stats' %>
<% if @users.any? %>
<div class="user_avatars">
<% @users.each do |user| %>
<%= link_to gravatar_for(user, size: 30), user %>
<% end %>
</div>
<% end %>
</section>
</aside>
<div class="col-md-8">
<h3><%= @title %></h3>
<% if @users.any? %>
<ul class="users follow">
<%= render @users %>
</ul>
<%= will_paginate %>
<% end %>
</div>
</div>
注目すべきは
<% if @users.any? %>
<div class="user_avatars">
<% @users.each do |user| %>
<%= link_to gravatar_for(user, size: 30), user %>
<% end %>
</div>
<% end %>
これは、もしフォローしているユーザー、自分をフォローしているユーザが1人以上いれば、顔画像の集合体を表示しますよーってこと。
で、その下の、
<% if @users.any? %>
<ul class="users follow">
<%= render @users %>
</ul>
<%= will_paginate %>
<% end %>
これは、フォローしているユーザー、または自分をフォローしているユーザーがいればその集合体を表示するということ。
render @usersなので、eachメソッドを使って、_userパーシャルが展開される。
また、will_pagenateに関しては、@usersを指定しても良いが、usersのviewでuserリソースを扱うことがデフォルトで設定されているので、別に書かなくても良い。
次に、統合テストを作る。
$ rails generate integration_test following
まずはテスト用のサンプルデータを作っていく
one:
follower: michael
followed: lana
two:
follower: michael
followed: malory
three:
follower: lana
followed: michael
four:
follower: archer
followed: michael
作ったサンプルデータを使って統合テストを書いていく
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
log_in_as(@user)
end
test "following page" do
get following_user_path(@user)
assert_not @user.following.empty?
assert_match @user.following.count.to_s, response.body
@user.following.each do |user|
assert_select "a[href=?]", user_path(user)
end
end
test "followers page" do
get followers_user_path(@user)
assert_not @user.followers.empty?
assert_match @user.followers.count.to_s, response.body
@user.followers.each do |user|
assert_select "a[href=?]", user_path(user)
end
end
end
これでテスト通る。
Relationshipsコントローラの実装。
フォローボタンを設置したはいいが、肝心のコントローラがまだなので、それを実装していく。
まずは、コントローラを作成する。
$ rails generate controller Relationships
次にrelationshipsコントローラの基本的なアクセス制御についてテストを書いていこう。
require 'test_helper'
class RelationshipsControllerTest < ActionDispatch::IntegrationTest
test "create should require logged-in user" do
assert_no_difference 'Relationship.count' do
post relationships_path
end
assert_redirected_to login_url
end
test "destroy should require logged-in user" do
assert_no_difference 'Relationship.count' do
delete relationship_path(relationships(:one))
end
assert_redirected_to login_url
end
end
このテストを通るようにするために、各アクションとバリデーションを設定してく
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
end
def destroy
end
end
次に二つのアクションの中身を実装していく。
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
user = User.find(params[:followed_id])
current_user.follow(user)
redirect_to user
end
def destroy
user = Relationship.find(params[:id]).followed
current_user.unfollow(user)
redirect_to user
end
end
まずはcreateアクションから。
hidden_fieldタグで@user.idを格納したfollowed_idを送ったので、
params[:followed_id]と参照することができる。
ここにはプロフィールページの@userのidが入る。
次にdestroyアクションを見ていこう。
params[:id]のidはrelationshipのidカラムが入る。
なので、params[:id]をもつrelationshipにfollowedメソッドを実行することにより、
フォローされているユーザーが戻り、userという変数に格納される。
correct_userのバリデーションを設定しないのは?
それはcurrent_user.followにある。
もし、仮にいたずらでポストリクエストやデリートリクエストを送ったとする。
だとしても、フォローするのもフォローを解除するのもcurrent_userなので、誰かのアカウントを乗っ取ってフォローしたりフォローを解除しているわけではない。
他人と他人の関係性を勝手に作られるのなら困るけど、別に攻撃者のフォロワーやフォローしてる人が増え用が増えまいが関係ないよって話。
一応これでアクションの実装は完了。
発展編Ajaxを使ったフォローボタンの実装
これは、フォローする前とした後で、htmlにそこまで差がないことから、もう一度1から描画しなくても、変わった部分だけJS使って変えられないか?という話。
<%= form_for(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 %>
まず、Ajaxを有効にするには、form_forの引数?オプションでremote: trueを有効にする。
そして、フォロー解除フォームでも同じことをする
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
html: { method: :delete },
remote: true) do |f| %>
<%= f.submit "Unfollow", class: "btn" %>
<% end %>
フォームの更新が終わったので、今度はこれに対応するRelationshipsコントローラを改造して、Ajaxリクエストに応答できるようにしましょう。こういったリクエストの種類によって応答を場合分けするときは、respond_toメソッドというメソッドを使います。
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
respond_to do |format|
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
これ何をやっているかというと、current_user.followでDBへの処理は終わってる。
その時点で、jsを使ってデータを再読み込みしてるらしい。
また、ユーザーの中にはjsが無効になっている人もいるので、無効だったらhtmlを使うように以下の処理をしておく
require File.expand_path('../boot', __FILE__)
.
.
.
module SampleApp
class Application < Rails::Application
.
.
.
# 認証トークンをremoteフォームに埋め込む
config.action_view.embed_authenticity_token_in_remote_forms = true
end
end
次にjsと埋め込みRubyを使ってフォローの関係性を作成する。
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>");
$("#followers").html('<%= @user.followers.count %>');
$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>");
$("#followers").html('<%= @user.followers.count %>');
ここで何をやってるかというと、id = follow_formの部分を見つけて、以下(html("<%= escape_javascript(render('users/unfollow')) %>");)のhtmlに切り替えますよーっていう意味らしい。
これでAjaxの実装は終了。ただ、そこまで重要ではない。
Ajaxのテストをする
xhrオプションをtrueにすることで対応できるらしい。
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
@other = users(:archer)
log_in_as(@user)
end
.
.
.
test "should follow a user the standard way" do
assert_difference '@user.following.count', 1 do
post relationships_path, params: { followed_id: @other.id }
end
end
test "should follow a user with Ajax" do
assert_difference '@user.following.count', 1 do
post relationships_path, xhr: true, params: { followed_id: @other.id }
end
end
test "should unfollow a user the standard way" do
@user.follow(@other)
relationship = @user.active_relationships.find_by(followed_id: @other.id)
assert_difference '@user.following.count', -1 do
delete relationship_path(relationship)
end
end
test "should unfollow a user with Ajax" do
@user.follow(@other)
relationship = @user.active_relationships.find_by(followed_id: @other.id)
assert_difference '@user.following.count', -1 do
delete relationship_path(relationship), xhr: true
end
end
end
このままでテストは通る。
これでフォローボタン、アンフォローボタンのAjax版が終了
ステータスフィードを完成させよう
具体的には、フォローしてるユーザーの投稿が自分のタイムラインに出るということ。
で、答えがわかったので、まずはテストを書いていこう
require 'test_helper'
class UserTest < ActiveSupport::TestCase
.
.
.
test "feed should have the right posts" do
michael = users(:michael)
archer = users(:archer)
lana = users(:lana)
# フォローしているユーザーの投稿を確認
lana.microposts.each do |post_following|
assert michael.feed.include?(post_following)
end
# 自分自身の投稿を確認
michael.microposts.each do |post_self|
assert michael.feed.include?(post_self)
end
# フォローしていないユーザーの投稿を確認
archer.microposts.each do |post_unfollowed|
assert_not michael.feed.include?(post_unfollowed)
end
end
end
この状態ではfeedメソッドがプロトタイプなので落ちてしまう。
さて、これをどうやって実現するか?
current_user.microposts + current_user.following.map { |n| n.microposts }
これでも、自分と自分のフォローしている人の投稿を取得できるけど、発行されるSQL文が非常に多くなってしまう。
これをどうやったら簡潔に、そして1回のSQL文の発行で収めることができるか?
フィードを実装していく。
フィードはタイムラインのことだよ。
これはsqlに関係してて難しい。
class User < ApplicationRecord
.
.
.
# パスワード再設定の期限が切れている場合はtrueを返す
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
# ユーザーのステータスフィードを返す
def feed
Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
end
# ユーザーをフォローする
def follow(other_user)
following << other_user
end
.
.
.
end
これは、一つ目の?にfollowing_ids
following_idsはhas_manyメソッドを定義すると同時に使えるようになるメソッド。
フォローしているユーザーのidの集合体を引っ張ってくる。
2つ目の?にはidが入る。self.idの略かな?
これでテストが通る。
サブセレクト
SQLの発行回数を減らして高速化しようねーって話。
class User < ApplicationRecord
.
.
.
# ユーザーのステータスフィードを返す
def feed
Micropost.where("user_id IN (:following_ids) OR user_id = :user_id",
following_ids: following_ids, user_id: id)
end
.
.
.
end
最終的な実装
class User < ApplicationRecord
.
.
.
# ユーザーのステータスフィードを返す
def feed
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
Micropost.where("user_id IN (#{following_ids})
OR user_id = :user_id", user_id: id)
end
.
.
.
end
renderのurl指定について
renderのurlは基本的にはapp/views以下の相対パスを入れる。
例えば、render 'shared/stats'
html.erbは省略してもok
便利コマンド
$ rails test
$ git add -A
$ git commit -m "Add user following"
$ git checkout master
$ git merge following-users
$ git push
$ git push heroku
$ heroku pg:reset DATABASE
$ heroku run rails db:migrate
$ heroku run rails db:seed