さらなる高みへ -higher ground -

プログラミング学習の定着を狙いとしたアウトプット(独り言)をしてみるブログ

ログイン機能を追加する1

これから、PicTweetにログイン機能を追加していく。
実装にはDeviseというGemを使用する。
このGemは広く使われているもので、オリジナルでアプリケーションを作る際に活用できまるため理解しながら進めていきたい。

作業の準備する

必要なファイルを準備する

機能の追加を学習を始める前に、必要なファイルの準備を行う。
リンク先のファイルをダウンロードして「arrow_top.png」を下記のディレクトリに配置する。arrow_top.png 

https://tech-master.s3.amazonaws.com/uploads/curriculums//57fe6d4d4f622b0c62a723cffdcef0de.gif

app/asset/simages/arrow_top.png

ルートパスを追加する

ツイート一覧のページを、アプリケーションのトップページに設定する。

ルートパス

ウェブサイトへアクセスする際などに使用するURLには、ホスト名とパスの2つの部分が存在する。「
www.yahoo.co.jp」や「pictweet.me」のように、パスを付けないホスト名だけのURLのことをルートパスという。

現在のPicTweetは「/tweets」にアクセスするとツイートの一覧画面が表示される。
今回はルーティングを追記してルートパスにアクセスした際にツイート一覧画面が表示されるようにしたい。

そのためには、routes.rbにルートパス専用のルーティングを設定する必要がある。
以下の例のように、root コントローラ名#アクション名と書く。

【例】
config/routes.rb

  Rails.application.routes.draw do
    root  'コントローラ名#アクション名'
  end

ルートパスを設定する

routes.rbを編集してルートパスの設定を行う。

config/routes.rb

Rails.application.routes.draw do
  root  'tweets#index'                       #ルートパスの指定
  get   'tweets'      =>  'tweets#index'     #ツイート一覧画面  
  get   'tweets/new'  =>  'tweets#new'       #ツイート投稿画面
  post  'tweets'      =>  'tweets#create'    #ツイート投稿機能
end

ルートパスにアクセスする

トップページにアクセスして、ルートパスががきちんと反映されているか確認する。

こちらにアクセスすると、「トップページアドレス/tweets」にアクセスした画面と同じ画面になっているのが確認できる。

これは、ルートパスによってtweets_controllerのindexアクションが動いているということである。

ログイン機能とは

Twitterなどのウェブサイトにはログイン機能がある。
この機能はサインアップしたユーザーに対してアカウントを発行し、そのアカウントで情報を管理することができる機能である。

Railsの場合、ログイン機能は「devise」というGemを使用することで簡単に実装することができる。

以下の動画のように、ヘッダーにログイン/新規登録のボタンを置き、実際にログインするとその部分が登録したユーザー名と投稿するボタンになる。

https://tech-master.s3.amazonaws.com/uploads/curriculums//9edb5446444521a3db10df75ad3168c5.gif

今回はログイン機能を実装することで、ログインしたユーザーのみがツイートの投稿を行えるようにする。

ツイートの閲覧に関してはログインにかかわらずできるようにする。

ツイート閲覧時

ログイン機能説明1

ツイート投稿時

ログイン機能説明2作業内容

1.Gemをインストールしてサーバーを立ち上げ直す
2.コマンドを利用してdeviseの設定ファイルを作成する
3.コマンドを利用してUserモデルを作成する
4.未ログイン時にはログインと新規登録ボタンを表示する
5.コントローラにリダイレクトを設定する

1.Gemをインストールしてサーバーを立ち上げ直す

ログイン機能を実装する際には「devise」というGemをインストールして使用する。
またGemをインストールした後はrails sをし直しサーバーを立ち上げ直す必要がある。
これはインストールしたGemが反映されるタイミングがサーバーを立てるときだからである。

devise

deviseは、ログイン機能を簡単に作成することができるGemである。
ログイン機能をGem無しで実装するのは非常に大変である。
しかし、このGemを使うことで比較的簡単に実装することができる。

以下に従い、deviseをインストール

  • ①Gemfileの最後の行に下記の記述を追記
    Gemfile
    gem 'devise'
  • ②bundle installを実行
    ターミナル
    $ pwd
    #現在のディレクトリが/home/ec2-user/environment/pictweetであることを確認
    
    $ bundle install
    #bundle install の実行

  • ③ローカルサーバーを再起動
    ターミナル
    $ rails s

これでインストールしたGemが反映された。
この後ターミナルで作業をするため1回controlキー+cでサーバーをシャットダウンする。

2.コマンドを利用してdeviseの設定ファイルを作成する

deviseを使用するためには、Gemのインストールに加えてdevise専用のコマンドを利用して設定ファイルを作成する必要がある。

ターミナルから下記のコマンドを実行する。
$ rails g devise:install
# deviseの設定ファイルを作成

https://tech-master.s3.amazonaws.com/uploads/curriculums//c8bb6653e46f0efd19c2374930c0012d.jpeg

ターミナルに上のように表示されていれば成功。

新規作成されるファイル

  • config/initializers/devise.rb
  • config/locales/devise.en.yml

3.コマンドを利用してUserモデルを作成する

deviseを利用する際にはアカウントを作成するためのUserモデルを新しく作成する。
作成には通常のモデルの作成方法ではなく、deviseのモデルの作成用コマンドを使用する。

rails g devise コマンド

deviseで、ログイン機能をつける概念のモデルを作成する際に利用するコマンド。
モデルに加えて、ログイン機能のために必要なカラムが追加されるマイグレーションファイルなどが生成される。

下記の指示に従い、ログイン機能を持つuserクラスを作成する

1.rails g deviseコマンドでuserモデルを作成する

ターミナル

$ rails g devise user
# deviseコマンドでモデルを作成

新規作成されるファイル

  • app/models/user.rb
  • db/migrate/2014XXXXXXXXXX_devise_create_users.rb
  • test/fixtures/users.yml
  • test/models/user_test.rb

2.マイグレートを実行する

ターミナル

$ rails db:migrate
# 作成されたマイグレーションファイルを実行
phpMyAdminにusersテーブルが作成されているか確認する

phpMyAdminをリロードし、以下のようにusersテーブルが作成されていれば成功。

https://tech-master.s3.amazonaws.com/uploads/curriculums//7231c6d518c5882f0d8e882e4fbfca44.jpeg
マイグレーションファイルを実行してもusersテーブルが表示されない場合は、ブラウザをリロードしてみる。

phpMyAdminの起動が出来ない方向け

usersテーブルが作成されているかどうか、コンソールから確認してみる。

以下のコマンドを実行する

$ rails c
# コンソールの起動

pry(main)>ActiveRecord::Base.connection.tables
=> ["schema_migrations", "tweets", "users"]
# 上記コマンドの返り値に、"users"が含まれていれば、usersテーブルの作成に成功しています

pry(main)>exit
# コンソールの終了

user.rbを開き、5行目の「:trackable」を削除する

:trackableのみを削除。

バージョンによる差異

使用している環境によって、つくられるファイルの内容が異なる。
もともと「;trackable」の記述がない場合は作業不要。

app/models/user.rb(修正前)

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end
app/models/user.rb(修正後)
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

 

4.未ログイン時にはログインと新規登録ボタンを表示する

誰かが未ログイン時にツイート一覧画面を表示した際に、ヘッダーに投稿ボタンの代わりにログインと新規登録ボタンを表示するようにする。

Rubyタグ

<%=%>で囲まれた部分をRubyタグと言う。
Rubyタグは拡張子が「erb」のビューファイルで使用することができる。
Rubyタグを使用して記述されたコードはビューファイルが読み込まれる際にHTMLコードとなって読み込まれる。

また-%>のように閉じるタグに-をつける形もある。
このようにすることで、余計な改行を取り除くことができる。

<%= %>と<% %>の違いは、Rubyタグに囲まれた処理の返り値を出力するか、しないかという違いがある。

例1<%= tweet.text %>

これは計算結果を出力したいので<%= %>を使う。

例2<% if user.name == "たなかたろう" %>

これは条件分岐の処理に使いたいだけで、user_signed_in? の返り値は特に出力する必要はない。

状況に応じて、使い分ける。

link_toメソッド

link_toRubyタグの中で使用することができるメソッドである。
このメソッドは引数を指定することで様々なリンクを生成する。

通常HTMLコード内でリンクを生成する際にはaタグを使用する。
link_toメソッドを使って記述を行うと、HTMLコードが読み込まれる際にaタグに変換されるため、サイトを表示した際にはaタグと同様に、リンクとして表示される。

今回の実装で追加するログインや新規登録ボタンもこのメソッドを利用して生成する。

【例】
sample.html.erb

  <%= link_to 'ツイート一覧へ', '/tweets' %>
  # link_toメソッドを使ってリンクを生成

また、htmlの要素に指定できるclassなどの属性は、以下の例のように続けてclass: 'sample'などと書くことで付与することができる。

sample.html.erb

  <%= link_to 'ツイート一覧へ', '/tweets', class: 'sample' %>
  # 作成したaタグに`class="sample"`属性を付与

link_toメソッドがビューファイルとして読み込まれる際には、以下の様なHTMLコードになる。

sample.html.erb

<a class="sample" href="/tweets">ツイート一覧へ</a>

user_signed_in?

deviseでログイン機能を実装すると、user_signed_in?というメソッドを使用することができる。
これは、ユーザーがサインインしているかどうか検証するメソッドである。
サインインしている場合にはtrueを返し、サインインしていない場合にはfalseを返す。【例】
sample.html.erb

  <% if user_signed_in? %>
    # ユーザーがサインインしている場合に実行する処理
  <% end %>

user_signed_in?が返す値は最終的にtrueかfalseになるので、上記の例のようにif文または、unless文とともに使用します。

prefix

Prefix(プレフィックス)とは、ルーティングのパスが入る変数のことである。
コントローラやビューなどで呼び出すことで、prefixに入っているパスやURL情報を取得できるようになる。

Prefixは、routes.rbの各リクエストにオプションとして設定するが、記述によっては自動的に作成される場合もある。

これを確認するには、ターミナルからrake routesコマンドを実行する。
すると、以下のような表示になる。

【例】ターミナル

#rake routesコマンドの実行
$rake routes
#実行結果

                  Prefix Verb   URI Pattern                    Controller#Action
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
           user_password POST   /users/password(.:format)      devise/passwords#create
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
                         PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
       user_registration POST   /users(.:format)               devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
                         PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy
                    root GET    /                              tweets#index
              tweets_new GET    /tweets/new(.:format)          tweets#new
                  tweets POST   /tweets(.:format)              tweets#create
                         GET    /users/:id(.:format)           users#show
                         DELETE /tweets/:id(.:format)          tweets#destroy
                         GET    /tweets/:id/edit(.:format)     tweets#edit
                         PATCH  /tweets/:id(.:format)          tweets#update

上記の表示でわかる通り、Prefixが設定されている場合は表の一番左に示される。

deviseを導入した場合、ユーザーの新規登録やログインに関するprefixは予め決まっていて、以下のようになる。

【例】deviseによって設定されるprefixの一部
リクエス prefix パス
devise/sessions#new new_user_session /users/sign_in
devise/sessions#create user_session /users/sign_in
devise/sessions#destroy destroy_user_session /users/sign_out

実際にprefixを利用する場合は、new_user_session_pathのように、最後に_pathとつける必要があります。

ここまでの説明を踏まえて、以下の問題に挑戦してみる。
ヘッダー部分のhtmlは、全てのビューの共通のテンプレートであるapplication.html.erbに書きます。

 問題1:ログインしている時とそうでない時で表示するビューを変更しましょう

作業ファイル:app/views/layouts/application.html.erb
ヒント:下記の例をもとに、user_signed_in?メソッドを利用してください。
【例】
application.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    <header class="header">
      <div class="header__bar row">
        <h1 class="grid-6"><a href="/">PicTweet</a></h1>
        <% if ユーザーがサインインしている場合 %>
          <div class="user_nav grid-6">
            <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
            <a class="post" href="/tweets/new">投稿する</a>
          </div>
        <% サインインしていない場合 %>
          <div class="grid-6">
            <%= link_to "ログイン", new_user_session_path, class: 'post' %>
            <%= link_to "新規登録", new_user_registration_path, class: 'post' %>
          </div>
        <% 閉じタグ %>
      </div>
    </header>

トップページを開きログインしている時としていない時でヘッダー部分の表示が変わることを確認する

ログインしていない時

「ログイン」「新規登録」ボタンの2つが表示されていることが確認できる。

008

グインしている時

「新規登録」ボタンより、ユーザーの新規登録(作成)を行う。

無事、登録ができれば、自動的にログインされ、以下のように「ログアウト」「投稿する」ボタンの2つが表示されていることが確認できる。

009

5. コントローラにリダイレクトを設定する

上記の作業で未ログインユーザーがツイートの一覧画面を表示しても投稿ボタンが表示されなくなりった。
しかし、未ログインユーザーであっても直接/tweets/newというパスにアクセスすることで投稿ができてしまうという問題が残る。

トップページを開き下記のアドレスを追記しする

トップページアドレス/tweets/new

ログインしていない状態でも「トップページアドレス/tweets/new」にアクセスできるのが確認できる。

https://tech-master.s3.amazonaws.com/uploads/curriculums//213efc253fe4f4466a3c25a2a47f527d.jpeg

そこで、未ログインユーザーが投稿画面など直接アクセスしてきた際にはルートパスに遷移するように設定を行います。

unless文

ここまで条件分岐にはif文を使用してきたが、似た制御構造を持つものとしてunless文がある。
unless文は条件式が偽(false)の場合の処理を記述するのに使われる。

今回はユーザーがログインしていない場合の条件分岐にunless文を使用する。
以下は最も基本的なunless文の書き方である。

【例】

  unless 条件式
    # 条件式が偽(false)のときに実行する処理
  end

また条件分岐の中が一行で記述できる場合は、if/unless文は以下のように一行で記述することができる。
記述が簡単で見やすくなるため、一行で記述できる場合は一行で記述する。

【例】

  puts 'ログインをしてください' unless user_signed_in?
  # 以下と同義

  unless user_signed_in?
    puts 'ログインをしてください'
  end
redirect_toメソッド

Railsでは通常、アクション内の処理が終了すると自動的にアクション名と同名のビューが表示される。
ただしredirect_toメソッドをアクション内で利用すると、そこからさらに別のアクションを実行したり、ビューに遷移させたりできる。

引数にはaction: :indexという形で、キーがaction:バリューが:indexであるハッシュを指定する。
このようにバリューにはアクションの名前のシンボル型を利用する。
丁寧に書くのであれば{ action: :index }となるが、Railsの内部では特別にハッシュの括弧{}を省略することができる。

今回はindexアクションを実行させるために、redirect_to action: :indexとする。

【例】

app/controllers/tweets_controller.rb

  class TweetsController < ApplicationController

    def index
      @tweets = Tweet.all
    end

    private
    def move_to_index
      redirect_to action: :index
      # indexアクションを強制的に実行する
    end
  end

 

before_action

Railsではコントローラでbefore_action :メソッド名と記述することで、コントローラのアクションが実行される前にそのメソッドを実行することができる。
また、オプションonlyexceptを使うことにより、before_actionを実行することをアクションごとに制限をかけることができる。

【例】indexアクション以外でbefore_actionを実行したい場合

app/controllers/tweets_controller.rb

  class TweetsController < ApplicationController

    before_action :hoge, except: :index
    # indexアクション以外が実行される前にhogeが実行される。

    def index
      @tweets = Tweet.all
    end

    private
    def hoge
    end
  end

 

 問題2:ログインしていないときに新規投稿しようとすると、indexアクションにリダイレクトされるようにしましょう

作業ファイル:app/controllers/tweets_controller.rb
ヒント:before_actionは全てのアクションの実行前に強制的に実行されるメソッドを設定します。
tweets_controller.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class TweetsController < ApplicationController

  before_action :move_to_index, indexアクションの場合は実行しない条件をつける

  def index
    @tweets = Tweet.all
  end

  def new
  end

  def create
    Tweet.create(tweet_params)
  end

  private
  def tweet_params
    params.permit(:name, :image, :text)
  end

  def move_to_index
    # ログインしていなかった場合、「index」アクションを実行する
  end
end
  

 【 問題2の解答を確認する 】 

 

要点チェック