【Twitterクローン作成】6.お気に入り機能作成

プログラミング




 

 

この記事は【Twitterクローン作成】5.フォロー機能作成の続きとなります。

 




お気に入り機能の実装

お気に入り機能の考え方は、フォロー機能と全く同じく多対多の関係です。

フォロー機能では中間テーブルによってUserモデル同士を繋げましたが、

今回はUserモデルとTweetモデルを繋げます。

 

早速実装していきましょう!

Model

$ rails g model Favorite user:references tweet:references

model名はFavoriteとしましょう。

 

マイグレーションファイルの確認

db/migrate/年月日時_create_favorites.rb

class CreateFavorites < ActiveRecord::Migration[5.0]
  def change
    create_table :relationships do |t|
      t.references :user, foreign_key: true
      t.references :tweet, foreign_key: true

      t.timestamps

      t.index [:user_id, :tweet_id], unique: true
    end
  end
end

前回はfollowsテーブルが存在しなかったために、参照はusersテーブルを外部キーとして指定したが、今回はtweetsテーブルが存在するので指定しません。

しかし t.index [:user_id, :tweet_id], unique: true はお気に入りをするユーザとされるツイートの重複を避けるためのものなのでしっかりと記述しておきましょう

 

上記の通りに記述したら、マイグレーションを実行します

ターミナル

$ rails db:migrate

 

Userモデルに多対多の関係性を明記します

app/models/user.rb

class User < ApplicationRecord
  before_save { self.email.downcase! }
  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i },
                    uniqueness: { case_sensitive: false }
   
  has_secure_password

  has_many :tweets
  has_many :relationships
  has_many :followings, through: :relationships, source: :follow
  has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'
  has_many :followers, through: :reverses_of_relationship, source: :user
  has_many :favorites
  has_many :favs, through: :favorites, source: :tweet


  def follow(other_user)
    unless self == other_user
      self.relationships.find_or_create_by(follow_id: other_user.id)
    end
  end

  def unfollow(other_user)
    relationship = self.relationships.find_by(follow_id: other_user.id)
    relationship.destroy if relationship
  end

  def following?(user_id)
    self.followings.include?(user_id)
  end
  def fav(tweet_id)
      self.favorites.find_or_create_by(tweet_id:tweet_id)
  end

  def unfav(tweet_id)
    favorite = self.favorites.find_by(tweet_id:tweet_id)
    favorite.destroy if favorite
  end

  def faving?(tweet_id)
    self.favs.include?(tweet_id)
  end
end

 

tweetモデルにも多対多の関係であることを明記します

app/models/tweet.rb

class Tweet < ApplicationRecord
  belongs_to :user
  
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 255 }
  
  has_many :favorites
  has_many :favs, through: :favorites, source: :user
end

 

Router

config/routes.rb

Rails.application.routes.draw do
  root to: 'toppages#index'

  get 'login', to: 'sessions#new'
  post 'login', to: 'sessions#create'
  delete 'logout', to: 'sessions#destroy'

  get 'signup', to: 'users#new'
  resources :users, only: [:index, :show, :new, :create] do
    member do
      get :followings
      get :followers
      get :favorites
    end
  end

  resources :tweets, only: [:create, :destroy]
  resources :relationships, only: [:create, :destroy]
  resources :favorites, only: [:create, :destroy]
end

 

create,destroy

Controller

$ rails g controller favorites create destroy

 

app/controllers/favorites_controller.rb

class FavoritesController < ApplicationController
  before_action :require_user_logged_in
  
  def create
    @tweet = Tweet.find(params[:tweet_id])
    @tweet.fav(current_user)
    flash[:success] = 'お気に入りしました'
    redirect_to current_user
  end

  def destroy
    @tweet = Tweet.find(params[:tweet_id])
    @tweet.unfav(current_user)
    flash[:success] = 'お気に入りを解除しました'
    redirect_to current_user
  end
  
end

 

お気に入りボタン作成

app/views/favorites/_fav_button.html.erb

<% if logged_in? %>
  <% if current_user.faving?(tweet) %>
    <%= form_for(current_user.favorites.find_by(tweet_id:tweet.id), html: { method: :delete }) do |f| %>
      <%= hidden_field_tag :tweet_id, tweet.id %>
      <%= f.submit 'お気に入りを解除', class: 'btn btn-danger btn-block' %>
    <% end %>
  <% else %>
    <%= form_for(current_user.favorites.build) do |f| %>
      <%= hidden_field_tag :tweet_id,tweet.id %>
      <%= f.submit 'お気に入り', class: 'btn btn-primary btn-block' %>
    <% end %>
  <% end %>
<% end %>

 

tweet一覧にお気に入りボタンを設置します

app/views/tweets/_tweets.html.erb

<ul class="media-list">
  <% tweets.each do |tweet| %>
    <li class="media">
      <div class="media-left">
        <img class="media-object img-rounded" src="<%= gravatar_url(tweet.user, options = { size: 50 }) %>" alt="">
      </div>
      <div class="media-body">
        <div>
          <%= link_to tweet.user.name, user_path(tweet.user) %> <span class="text-muted">posted at <%= tweet.created_at %></span>
        </div>
        <div>
          <p><%= tweet.content %></p>
        </div>
          <%= render 'favorites/fav_button' , tweet: tweet %>
        <div>
          <% if current_user == tweet.user %>
            <%= link_to "Delete", tweet, method: :delete, data: { confirm: "You sure?" }, class: 'btn btn-danger btn-xs' %>
          <% end %>
        </div>
      </div>
    </li>
  <% end %>
  <%= paginate tweets %>
</ul>

 

favoritesメソッドを追加し、before_actionをfavoritesメソッドに反映させましょう

 

class UsersController < ApplicationController
  before_action :require_user_logged_in, only: [:index, :show, :followings, :followers, :favorites]

  def index
    @users = User.all.page(params[:page])
  end

  def show
    @user = User.find(params[:id])
    @tweets = @user.tweets.order('created_at DESC').page(params[:page])
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)

    if @user.save
      flash[:success] = 'ユーザを登録しました。'
      redirect_to @user
    else
      flash.now[:danger] = 'ユーザの登録に失敗しました。'
      render :new
    end
  end

  def followings
    @user = User.find(params[:id])
    @followings = @user.followings.page(params[:page])
  end
  
  def followers
    @user = User.find(params[:id])
    @followers = @user.followers.page(params[:page])
  end

  def favorites
    @user = User.find(params[:id])
    @favorites = @user.favorites.page(params[:page])
  end

  private

  def user_params
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
  end
end

 

 

View

ユーザがお気に入りしたツイートを表示させます

app/views/users/favorites.html.erb

<div class="row">
  <aside class="col-xs-4">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title"><%= @user.name %></h3>
      </div>
      <div class="panel-body">
        <img class="media-object img-rounded img-responsive" src="<%= gravatar_url(@user, { size: 500 }) %>" alt="">
      </div>
    </div>
    <%= render 'relationships/follow_button', user: @user %>
  </aside>
  <div class="col-xs-8">
    <ul class="nav nav-tabs nav-justified">
      <li class="<%= 'active' if current_page?(user_path(@user)) %>"><%= link_to user_path(@user) do %>Tweets <% end %></li>
      <li class="<%= 'active' if current_page?(followings_user_path(@user)) %>"><%= link_to followings_user_path(@user) do %>Followings <% end %></li>
      <li class="<%= 'active' if current_page?(followers_user_path(@user)) %>"><%= link_to followers_user_path(@user) do %>Followers <% end %></li>
            <li class="<%= 'active' if current_page?(favorites_user_path(@user)) %>"><%= link_to favorites_user_path(@user) do %>Favorites <% end %></li>
    </ul>
    <%= render 'tweets/tweets', tweets: @favorites %>
  </div>
</div>

 

 

users/followings、followers、showのページにもfavoritesを追加しましょう。

app/views/users/followings.html.erb

<div class="row">
  <aside class="col-xs-4">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title"><%= @user.name %></h3>
      </div>
      <div class="panel-body">
        <img class="media-object img-rounded img-responsive" src="<%= gravatar_url(@user, { size: 500 }) %>" alt="">
      </div>
    </div>
    <%= render 'relationships/follow_button', user: @user %>
  </aside>
  <div class="col-xs-8">
    <ul class="nav nav-tabs nav-justified">
      <li class="<%= 'active' if current_page?(user_path(@user)) %>"><%= link_to user_path(@user) do %>Tweets <% end %></li>
      <li class="<%= 'active' if current_page?(followings_user_path(@user)) %>"><%= link_to followings_user_path(@user) do %>Followings <% end %></li>
      <li class="<%= 'active' if current_page?(followers_user_path(@user)) %>"><%= link_to followers_user_path(@user) do %>Followers <% end %></li>
      <li class="<%= 'active' if current_page?(favorites_user_path(@user)) %>"><%= link_to favorites_user_path(@user) do %>Favorites <% end %></li>
    </ul>
    <%= render 'users', users: @followings %>
  </div>
</div>

 

app/views/users/followers.html.erb

<div class="row">
  <aside class="col-xs-4">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title"><%= @user.name %></h3>
      </div>
      <div class="panel-body">
        <img class="media-object img-rounded img-responsive" src="<%= gravatar_url(@user, { size: 500 }) %>" alt="">
      </div>
    </div>
    <%= render 'relationships/follow_button', user: @user %>
  </aside>
  <div class="col-xs-8">
    <ul class="nav nav-tabs nav-justified">
      <li class="<%= 'active' if current_page?(user_path(@user)) %>"><%= link_to user_path(@user) do %>Microposts <span class="badge"><%= @count_microposts %></span><% end %></li>
      <li class="<%= 'active' if current_page?(followings_user_path(@user)) %>"><%= link_to followings_user_path(@user) do %>Followings <span class="badge"><%= @count_followings %></span><% end %></li>
      <li class="<%= 'active' if current_page?(followers_user_path(@user)) %>"><%= link_to followers_user_path(@user) do %>Followers <span class="badge"><%= @count_followers %></span><% end %></li>
      <li class="<%= 'active' if current_page?(favorites_user_path(@user)) %>"><%= link_to favorites_user_path(@user) do %>Favorites <% end %></li>
    </ul>
    <%= render 'users', users: @followers %>
  </div>
</div>

 

app/views/users/show.html.erb

<div class="row">
  <aside class="col-xs-4">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title"><%= @user.name %></h3>
      </div>
      <div class="panel-body">
        <img class="media-object img-rounded img-responsive" src="<%= gravatar_url(@user, { size: 500 }) %>" alt="">
      </div>
    </div>
    <%= render 'relationships/follow_button', user: @user %>
  </aside>
  <div class="col-xs-8">
    <ul class="nav nav-tabs nav-justified">
      <li class="<%= 'active' if current_page?(user_path(@user)) %>"><%= link_to user_path(@user) do %>Tweets <% end %></li>
      <li class="<%= 'active' if current_page?(followings_user_path(@user)) %>"><%= link_to followings_user_path(@user) do %>Followings <% end %></li>
      <li class="<%= 'active' if current_page?(followers_user_path(@user)) %>"><%= link_to followers_user_path(@user) do %>Followers <% end %></li> </ul>
      <li class="<%= 'active' if current_page?(favorites_user_path(@user)) %>"><%= link_to favorites_user_path(@user) do %>Favorites <% end %></li>    <%= render 'tweets/tweets', tweets: @tweets %>
  </div>
</div>

 

以上でお気に入り機能の実装は終了です。

まとめ

今回実装した、お気に入り機能はフォロー機能と全く同じ考え方です。違いとしては、ユーザ同士を繋げるかユーザとツイートを繋げるかの違いです。

基礎となる考え方は1対多とその組み合わせである多対多の関係性です。複雑ではありますが、上記の考え方を抑えてしまえばそこまで難しいものではありません。

理解ができなければ、曖昧にせずにしっかりとわからない部分まで戻って理解を深めてください!

Twitter作成は以上でお疲れ様でした!