【Twitterクローン作成】4.ツイート機能作成

プログラミング




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

 




ツイート機能の実装

ツイート機能の考え方

 1                           対         

ユーザ1人に対してのツイートが複数あります。これを1対多の関係と言います。

Twitterのツイート機能はユーザとそれぞれのツイートを繋げる構造を作成します。

 

Model

上記を踏まえた上で、Modelを作成していきます。

 

テーブルの作成

必要なカラムは

  • ツイート内容
  • ツイートとユーザの繋がり: ここは user:references を使い作成します。型をreferencesにすることでTweet model が User modelを参照できるようにします。

 

Controllerの作成

ターミナル

$ rails g model Tweet content:string user:references

 

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

db/migrate/年月_create_tweets.rb

class CreateTweets < ActiveRecord::Migration[5.0]
  def change
    create_table :tweets do |t|
      t.string :content
      t.references :user, foreign_key: true

      t.timestamps
    end
  end
end

 

上記の内容が反映されていたら、マイグレーションを実行しましょう

$ rails db:migrate

 

UserがTweetを複数持っており、1対多の関係であることを記述します

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
end

 

TweetModelもUserModelに属していることを確認し,バリデーションも記述します

app/models/tweet.rb

class Tweet < ApplicationRecord
  belongs_to :user

  validates :content, presence: true, length: { maximum: 255 }
end

これでUserModelがTweetModelを複数持ち、TweetModelが特定のUserModelに属していることを表すことができました。これが1対多の関係です。

 

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]

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

今回はtoppageでツイートフォームの設置とツイート一覧の表示を行うので、create と destroyの機能だけで大丈夫です。

もし、toppage以外でツイートフォームの設置とツイート一覧の表示を行いたい場合は index new show も追加しなければならないでしょう。

 

new

【Twitterクローン作成】1.トップページ作成で作成したトップページにツイートを反映させます

まずはtoppagesのcontrollerにで使うためのインスタンスを記述しましょう。

Controller

app/controllers/toppages_controller.rb

class ToppagesController < ApplicationController
  def index
    if logged_in?
      @tweet = current_user.tweets.build  
      @tweets = current_user.tweets.order('created_at DESC').page(params[:page])
    end
  end
end

@tweetはツイートフォーム用。
@tweetsはツイート一覧を表示するためにあります。

 

create

トップページにツイートフォームとツイート一覧を表示させるため、まだtweetsコントローラを作成していないのでここでtweetsコントローラを作成します。

Controller

$ rails g controller tweets create destroy

 

app/controllers/tweets_controller.rb

class TweetsController < ApplicationController
  before_action :require_user_logged_in

  def create
    @tweet = current_user.tweets.build(tweet_params)
    if @tweet.save
      flash[:success] = 'メッセージを投稿しました。'
      redirect_to root_url
    else
      @tweets = current_user.tweets.order('created_at DESC').page(params[:page])
      flash.now[:danger] = 'メッセージの投稿に失敗しました。'
      render 'toppages/index'
    end
  end

  def destroy
  end

  private

  def tweet_params
    params.require(:tweet).permit(:content)
  end
end

 

View

ツイート一覧はパーシャルとして、トップページでrenderで反映させましょう。

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, { 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>
      </div>
    </li>
  <% end %>
  <%= paginate tweets %>
</ul>

 

ツイートフォームとツイート一覧を表示します

app/views/toppages/index.html.erb

<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <%= form_for(@tweet) do |f| %>
        <div class="form-group">
          <%= f.text_area :content, class: 'form-control', rows: 5 %>
        </div>
        <%= f.submit 'Post', class: 'btn btn-primary btn-block' %>
      <% end %>
    </aside>
    <div class="col-xs-8">
      <%= render 'tweets/tweets', tweets: @tweets %>
    </div>
  </div>
<% else %>
  <div class="center jumbotron">
    <div class="text-center">
      <h1>Twitter</h1>
      <%= link_to 'Sign up now!', signup_path, class: 'btn btn-lg btn-primary' %>
    </div>
  </div>
<% end %>

 

destroy

コントローラdestroy内の記述に加えてprivate内にcorrect_userを追加し、before_actionとして設定します。

app/controllers/tweets_controller.rb

class TweetsController < ApplicationController
  before_action :require_user_logged_in
  before_action :correct_user, only: [:destroy]

  def create
    @tweet = current_user.tweets.build(tweet_params)
    if @tweet.save
      flash[:success] = 'メッセージを投稿しました。'
      redirect_to root_url
    else
      @tweets = current_user.tweets.order('created_at DESC').page(params[:page])
      flash.now[:danger] = 'メッセージの投稿に失敗しました。'
      render 'toppages/index'
    end
  end

  def destroy
    @tweet.destroy
    flash[:success] = 'メッセージを削除しました。'
    redirect_back(fallback_location: root_path)
  end

  private

  def tweet_params
    params.require(:tweet).permit(:content)
  end

  def correct_user
    @tweet = current_user.tweets.find_by(id: params[:id])
    unless @tweet
      redirect_to root_url
    end
  end
end

correct_user は処理を行おうとしているのが本当に正規のユーザのなのかを確かめるためのメソッドです。

この場合であれば、before_action :correct_user, only: [:destroy]とすることで投稿を削除しようとしているユーザが本当に投稿主なのかを確かめています。

 

View

最後に削除ボタンを設置します

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>
        <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>

まとめ

今回はツイート投稿機能を実装しました

具体的に実装したこととしては、

  • UserModel と TweetModel の1対多の関係を構築
  • ツイートフォーム・ツイート一覧のトップページへの表示
  • correct_userメソッドで処理主が正しいかどうかを判断
  • 削除ボタンの設置

少し複雑にはなりますが1対多の関係性はフォロー機能、お気に入り機能で使用する多対多の関係性にも繋がります。フォロー機能、お気に入り機能の実装に進む前に必ず1対多の関係を理解するようにしましょう。

次はフォロー機能の実装になります。