Rails4でTwitterっぽいアプリを作ってみる
Railsですっげー簡易なTwitter作ってみる。
Twitterと大きく出たが、ここでは掲示板とかチャットと変わらん。。。
環境構築については下記を参照。
環境
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」で代用。