ぺーぺーSEのブログ

備忘録・メモ用サイト。

Rails4でTwitterっぽいアプリを作ってみる

Railsですっげー簡易なTwitter作ってみる。
Twitterと大きく出たが、ここでは掲示板とかチャットと変わらん。。。
環境構築については下記を参照。

blog.pepese.com

環境

Windows 7
Ruby 2.0.0
Rails 4.1.1


手順

★ファイルは「UTF-8」で保存すること。
1.プロジェクト作成

> rails new twitter

2.プロジェクトディレクトリに移動

> cd twitter

3.Gemfileの下記の行を有効化

# gem 'bcrypt', '~> 3.1.7'

4.Gemfileの変更を有効化

> bundle install

5.ユーザモデル作成

> rails generate model user name:string password_digest:string

※「password_digest」は固定。

6.作成したユーザモデルのテーブル作成

> rake db:migrate

7.作成したテーブルを確認

> rails dbconsole
>> .tables
>> .schema users
>> .quit

usersテーブルの存在と定義を確認する。

8.user.rbを下記のように修正

class User < ActiveRecord::Base
  has_secure_password # 追加した行
end

9.userデータを登録

> rails console
>> User.create!(:name => "user1", :password => "user1", :password_confirmation => "user1")
>> User.create!(:name => "user2", :password => "user2", :password_confirmation => "user2")
>> exit

★Fixturesでデータを登録したい場合は「test/fixtures/users.yml」を下記のように修正して「rake db:fixtures:load FIXTURES=users」を実行する。

one:
  name: user1
  password_digest: <%= BCrypt::Password.create("user1", cost: 4) %>

two:
  name: user2
  password_digest: <%= BCrypt::Password.create("user2", cost: 4) %>

10.userデータが登録できたか確認

> rails dbconsole
>> select * from users;
>> .quit

11.ログイン用コントローラ作成

> rails generate controller logins

※コントローラは複数形で定義すること。

12.logins_controller.rbを下記のように修正

class LoginsController < ApplicationController
  def show
      render "login"
  end

  def create
    user = User.find_by_name params[:name]
    if user && user.authenticate(params[:pass])
      # セッションのキー:user_idへユーザーのIDを登録
      session[:user_id] = user.id
      redirect_to tweets_path
    else
      # flash変数にメッセージをセット
      flash.now.alert = "もう一度入力してください。"
      render "login"
    end
  end

  def destroy
    session[:user_id] = nil
    @current_user = nil
    redirect_to login_path
  end
end

13.ログイン画面(views/logins/login.html.erb)を下記のように作成

<h1>ログイン画面</h1>

<!-- フラッシュ変数のメッセージを表示(ログインエラー時のみ) -->
<p><font color=red><%= flash[:alert] %></font></p>
<%= form_tag login_path do %>
<table id="login_form">
  <tr>
    <td><%= label_tag :name, 'ユーザー名' %></td>
    <td><%= text_field_tag :name, params[:name] %></td>
  </tr>

  <tr>
    <td><%= label_tag :pass, 'パスワード' %></td>
    <td><%= password_field_tag :pass, params[:pass] %></td>
  </tr>

  <tr>
    <td><%= submit_tag "ログイン" %></td>
  </tr>
</table>
<% end %>

14.application_controller.rbを下記のように修正

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  def current_user
    if session[:user_id]
      # @current_userがnilかfalseならログインユーザーを代入
      @current_user ||= User.find(session[:user_id])
    end
  end

  helper_method :current_user
end

ヘルパーメソッド「current_user」を定義している。

15.ツイート用のScaffoldを作成

> rails generate scaffold tweet user:string tweet:string

16.作成したツイートモデルのテーブル作成

> rake db:migrate

17.routes.rbを下記のように修正

Rails.application.routes.draw do
  root 'logins#show'
  resource :login, only: %i{show create destroy}
  resources :tweets
end

18.ルーティングの確認

> rake routes
    Prefix Verb   URI Pattern                Controller#Action
      root GET    /                          logins#show
     login POST   /login(.:format)           logins#create
           GET    /login(.:format)           logins#show
           DELETE /login(.:format)           logins#destroy
    tweets GET    /tweets(.:format)          tweets#index
           POST   /tweets(.:format)          tweets#create
 new_tweet GET    /tweets/new(.:format)      tweets#new
edit_tweet GET    /tweets/:id/edit(.:format) tweets#edit
     tweet GET    /tweets/:id(.:format)      tweets#show
           PATCH  /tweets/:id(.:format)      tweets#update
           PUT    /tweets/:id(.:format)      tweets#update
           DELETE /tweets/:id(.:format)      tweets#destroy

19.サーバを起動

> rails server

20.下記へアクセス
http://localhost:3000/


改良

上記はログイン機能とScaffoldで作ったTweetをつなげただけ。
下記のような改良をしてみる。

(1)ログアウトできるようにする
(2)ツイートメイン画面から直接ツイートできるようにする
(3)ツイートを古い順ではなく新しい順に表示する
(4)ハッシュタグみたい機能をつける


手順

(1)ログアウトできるようにする
ツイートメイン画面(views/tweets/index.html.erb)に下記を好きなところ(最後尾あたり)に追記。

<%= link_to 'ログアウト', login_path, method: :delete, data: { confirm: 'ログアウトしますか?' } %>

ついでにログインしてないやつはツイートできないようにする。
Tweetsコントローラ(controllers/tweets_controller.rb)を下記のように修正。

class TweetsController < ApplicationController
  before_action :set_tweet, only: [:show, :edit, :update, :destroy]
  before_action :confirm_session # 追記

  # 〜省略

  private
    # 〜省略
    # confirm_sessionメソッドを追記
    def confirm_session
      if !session[:user_id]
        redirect_to login_path, method: 'delete'
      end
    end
end

フィルタでセッションを確認して、セッションがなければログイン画面(Loginsコントローラのdestroyアクション)へ。

(2)ツイートメイン画面から直接ツイートできるようにする
Tweetsコントローラ(controllers/tweets_controller.rb)を下記のように修正。

class TweetsController < ApplicationController

# 〜省略

  # indexメソッドを下記のように修正
  def index
    @tweets = Tweet.all
    @tweet = Tweet.new
  end
  
  # 〜省略
  
  # createメソッドを下記のように修正
  def create
    tweet = Tweet.new(params.require(:tweet).permit(:tweet))
    tweet.user = User.find(session[:user_id]).name
    tweet.save
    redirect_to tweets_path, method: 'get'
  end

# 〜省略

end

ツイートメイン画面(views/tweets/index.html.erb)の下記の行(27行目あたり)

<%= link_to 'New Tweet', new_tweet_path %>

を消して下記を追記。

<%= form_for(@tweet) do |f| %>
  <% if @tweet.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@tweet.errors.count, "error") %> prohibited this tweet from being saved:</h2>

      <ul>
      <% @tweet.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :tweet %><br>
    <%= f.text_area :tweet %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

ついでに「Show」「Edit」はいらないので下記の行(17、18行目あたり)を削除。

        <td><%= link_to 'Show', tweet %></td>
        <td><%= link_to 'Edit', edit_tweet_path(tweet) %></td>

Tweetsコントローラの「new」「edit」「show」「update」は使わないのでルーティング設定(routes.rb)を下記のように修正。

Rails.application.routes.draw do
  root 'logins#show'
  resource :login, only: %i{show create destroy}
  resources :tweets, except: [:new, :edit, :show, :update]
end

確認。

> rake routes
Prefix Verb   URI Pattern           Controller#Action
  root GET    /                     logins#show
 login POST   /login(.:format)      logins#create
       GET    /login(.:format)      logins#show
       DELETE /login(.:format)      logins#destroy
tweets GET    /tweets(.:format)     tweets#index
       POST   /tweets(.:format)     tweets#create
 tweet DELETE /tweets/:id(.:format) tweets#destroy


(3)ツイートを古い順ではなく新しい順に表示する
Tweetsコントローラ(controllers/tweets_controller.rb)のindexアクションを下記のように修正。

class TweetsController < ApplicationController

# 〜省略

  def index
    @tweets = Tweet.all.order("created_at DESC") # この行を修正
    @tweet = Tweet.new
  end
  
# 〜省略

end

ついでに時間を表示する。
ツイートメイン画面(views/tweets/index.html.erb)のテーブル部分(3〜21行目あたり)を下記のように修正

<table>
  <thead>
    <tr>
      <th>User</th>
      <th>Tweet</th>
      <th>Time</th> <!-- 追記 -->
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @tweets.each do |tweet| %>
      <tr>
        <td><%= tweet.user %></td>
        <td><%= tweet.tweet %></td>
        <td><%= tweet.created_at %></td> <!-- 追記 -->
        <td><%= link_to 'Destroy', tweet, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>


(4)ハッシュタグみたい機能をつける
ツイートのテキストエリアの最後の行にシャープで始まる「#タグ」をハッシュタグとして認識するようにする。
ハッシュタグ用モデル作成。

> rails generate model hashtag name:string

hashtag.rbを下記のように修正。

class Hashtag < ActiveRecord::Base
  has_and_belongs_to_many :tweets # 追加行
end

tweet.rbを下記のように修正。

class Tweet < ActiveRecord::Base
  has_and_belongs_to_many :hashtags # 追加行
end

リレーションテーブルの作成。これを作らないとtweetとhashtagのモデルを関連付けできない。

> rails generate migration hashtags_tweets

命名は2つのモデルの複数形をアンスコ(_)で繋ぐと決まってる。
下記のようにコマンドで作成したマイグレーション定義(db/migrate/xxx_hashtags_tweets.rb)の修正。

class HashtagsTweets < ActiveRecord::Migration
  def change
    create_table :hashtags_tweets do |t|
      t.integer :hashtag_id
      t.integer :tweet_id
    end
  end
end

テーブル作成。

> rake db:migrate

Tweetsコントローラ(controllers/tweets_controller.rb)を下記のように修正。

class TweetsController < ApplicationController
  before_action :set_tweet, only: [:edit, :update, :destroy] # 「:show」を削除
  
  # 〜省略

  # indexメソッドを下記のように修正
  def index
    @tweets = Tweet.all
    @tweet = Tweet.new
    @hashtags = Hashtag.all       # 追加行
  end
  
  # 〜省略
  
  # showメソッドを下記のように修正
  def show
    hashTag = Hashtag.find_by(id: params[:id])                # [追加箇所]
    if hashTag == nil || hashTag.tweets.empty?                # 「:id」でハッシュタグを検索
      session[:hashtag] = nil                                 # 存在しなければindexへリダイレクト
      redirect_to tweets_path, method: 'get'                  #
      return                                                  #
    else                                                      #
      session[:hashtag] = params[:id]                         # ハッシュタグが存在すれば
      @tweets = hashTag.tweets(true).order("created_at DESC") # そのハッシュタグのツイートを全て取得
      @tweet = Tweet.new                                      #
      @hashtags = Hashtag.all                                 #
      render :index                                           #
    end                                                       #
  end
  
  # 〜省略
  
  # createメソッドを下記のように修正
  def create
    tweet = Tweet.new(params.require(:tweet).permit(:tweet))
    tweet.user = User.find(session[:user_id]).name
    
    str = tweet.tweet                              # [追加箇所]
    hashTagName = nil                              #
    str.each_line {|line|                          # ツイートテキストエリアの
      hashTagName = line                           # 最終行を取得
    }                                              #
    if /^#/ =~ hashTagName                         # 最終行がシャープで始まっていればハッシュタブということにする
      exitTag = Hashtag.find_by(name: hashTagName) # 既に存在しているハッシュタグか確認
      if exitTag == nil                            #
        hashTag = Hashtag.new                      # 存在しなければハッシュタグを新規作成
        hashTag.name = hashTagName                 #
        tweet.hashtags << hashTag                  # ハッシュタグとツイートの関連付け
        hashTag.save                               #
      else                                         #
        tweet.hashtags << exitTag                  # ハッシュタグとツイートの関連付け
      end                                          #
    end                                            #
    
    tweet.save
    if !session[:hashtag]                                      # [修正箇所]
      redirect_to tweets_path, method: 'get'                   # セッション内にハッシュタグidがあれば
    else                                                       # そのハッシュタグのツイートのみ表示
      redirect_to tweet_path(session[:hashtag]), method: 'get' # 無ければ前件表示
    end                                                        #
  end

# 〜省略

end

tweetsコントローラのshowアクションを使うので、ルーティング設定(routes.rb)を下記のように修正。

Rails.application.routes.draw do
  root 'logins#show'
  resource :login, only: %i{show create destroy}
  resources :tweets, except: [:new, :edit, :update] # exceptのshowを削除
end

ツイートメイン画面(views/tweets/index.html.erb)を修正してハッシュタグの一覧を表示。
最終行の

<%= link_to 'ログアウト', login_path, method: :delete, data: { confirm: 'ログアウトしますか?' } %>

の直上あたりに下記を追記。

<table>
  <thead>
    <tr>
      <th>ハッシュタグ</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td><%= link_to 'すべてのタグ', tweet_path(0), method: :get %></td>
    </tr>
    <% @hashtags.each do |hashtag| %>
      <tr>
        <td><%= link_to hashtag.name, tweet_path(hashtag), method: :get %></td>
      </tr>
    <% end %>
  </tbody>
</table>

「すべてのタグ」は存在しないid「0」で代用。