LoginSignup
2
3

More than 3 years have passed since last update.

Rails6.1ハンズオン(3)~ユーザー機能編

Last updated at Posted at 2021-03-04

はじめに

Rails6.1ハンズオン(2)の続きです。
環境は前回を参考にしてください。

いつRails6.1固有の機能にたどり着くのか...

今回はメールアドレスとパスワードを使った、
ユーザーログイン・ログアウトの機能を追加します。
コードはGithubにあげています。章ごとにコミットしてますので、参考にしていただければ幸いです。

やること

Railsでユーザー関連の機能をつけるときは、Railsチュートリアルのように自前で全部用意することも可能ですが、今回は楽をするためにGemを使います。

定番は「devise」でしょうか?仕事でもよく使いますが、せっかくなので、「sorcery」を使ってみようと思います。

最近(21/2/17くらい)v0.16.0になり、Rails6に対応したようです。ぴったりですね!

実装

参考にさせていただくもの:
Sorcery/sorcery
docker-compose下でrails newして Rails6.1 + Sorcery を試す( Sorcery の仕組み少し解説)
シンプル認証gem sorceryを完全入門するで!! - Qiita

4-1. sorcery gem導入、Userクラスをつくる

Gemfileにて以下を追加:

gem 'sorcery'
gem 'bcrypt'

↓これに準拠して進めます
Sorcery/sorcery

コマンドを実行:

bundle install
bundle exec rails g sorcery:install

File unchanged! The supplied flag value not found! app/models/user.rb

と赤字で出た。なんだろう... コマンドオプションを設定するとでないのかな?
とりあえずヨシ!

gコマンドで生成されたmigrationファイルでテーブル名がUserになっているのをusersに直す。(Userのまま進めたらエラーになりました)

db/migrate/(数字)_sorcery_core.rb

class SorceryCore < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      t.string :email,            null: false
      t.string :crypted_password
      t.string :salt

      t.timestamps                null: false
    end

    add_index :users, :email, unique: true
  end
end

Userモデルのバリデーション設定を追加する。

app/models/user.rb

class User < ApplicationRecord
  authenticates_with_sorcery!

  validates :email, uniqueness: true, presence: true

  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }
end

チュートリアルではemailのバリデーションにpresence: trueが入っていなかったので追加。

4-2. ユーザー作成・編集機能

rails g controller Users new create edit update

app/views/layouts/_header.html.haml(セッション機能は後で作る)

%nav.navbar.navbar-expand-lg.navbar-dark.bg-dark
  .container-fluid
    = link_to root_path, class: 'navbar-brand' do
      Rails6.1掲示板
    %button.navbar-toggler{"aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-bs-target" => "#navbarSupportedContent", "data-bs-toggle" => "collapse", :type => "button"}
      %span.navbar-toggler-icon
    #navbarSupportedContent.collapse.navbar-collapse
      %ul.navbar-nav.me-auto.mb-2.mb-lg-0
        %li.nav-item
          = link_to root_path, class: 'nav-link' do
            トップ
        %li.nav-item
          = link_to new_community_path, class: 'nav-link' do
            コミュニティを作る
      .d-flex
        -if current_user
          .nav-item.dropdown
            %a.nav-link.text-white.dropdown-toggle#navbarDropdownMenuLink{href: "#", role: "button", "data-bs-toggle": "dropdown", "aria-expanded": "false"}
              = current_user.email
            %ul.dropdown-menu{"aria-labelledby": "navbarDropdownMenuLink"}
              %li
                = link_to t('header.to_user_edit'), edit_user_path(current_user.id), class: 'dropdown-item'
              %li
                = link_to t('header.to_logout'), :logout, method: :post, class: 'dropdown-item'
        - else
          = link_to t('header.to_register'), new_user_path, class: 'btn btn-primary me-2'
          = link_to t('header.to_login'), :login, class: 'btn btn-secondary'

app/controllers/users_controller.rb(require_loginメソッドは後で作ります)

class UsersController < ApplicationController
  skip_before_action :require_login, only: %i[new create]
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to root_path, notice: 'ユーザーを新規作成しました。'
    else
      render :new
    end
  end

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

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      redirect_to root_path, notice: 'ユーザー情報を変更しました。'
    else
      render :edit
    end
  end

  private

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

app/views/users/new.html.haml

%h1= t '.title'

= form_with model: @user do |f|
  = render 'shared/error_messages', object: f.object

  = f.label :email, class: 'form-label'
  = f.text_field :email, class: 'form-control mb-2'

  = render 'form', form: f
  = f.submit t('.submit'), class: 'btn btn-primary'

app/views/users/edit.html.haml

%h1= t '.title'

= form_with model: @user do |f|
  = render 'shared/error_messages', object: f.object
  = render 'form', form: f
  = f.submit t('.submit'), class: 'btn btn-primary'

app/views/users/_form.html.haml

= form.label :password, class: 'form-label'
= form.password_field :password, class: 'form-control mb-2'

= form.label :password_confirmation, class: 'form-label'
= form.password_field :password_confirmation, class: 'form-control mb-2'

app/views/shared/_error_messages.html.haml

- if object.errors.present?
  .card.mb-2.border-danger
    .card-header.bg-danger.text-white
      エラーがあります
    .card-body
      %ul
        - object.errors.each do |e|
          %li= e.full_message

config/locales/model.ja.yml
config/locales/views/users/ja.yml
config/routes.rb
をいじる(コードは省略、リポジトリをみてください)

4-3. セッション機能

rails g controller UserSessions new create destroy

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  before_action :require_login

  private

  def not_authenticated
    redirect_to login_path, alert: 'ログインしてください。'
  end
end

require_loginはsorceryにもともと入っているメソッドらしい。
ログインしていなかったらnot_authenticatedメソッドを呼ぶという挙動。
not_authenticatedも忘れずに定義しておく。

app/controllers/user_sessions_controller.rb

class UserSessionsController < ApplicationController
  skip_before_action :require_login, only: %i[new create]
  def new; end

  def create
    @user = login(params[:email], params[:password])

    if @user
      redirect_back_or_to(root_path, notice: 'ログインしました。')
    else
      flash.now[:alert] = 'ログインできませんでした。'
      render :new
    end
  end

  def destroy
    logout
    redirect_to(login_path, notice: 'ログアウトしました。')
  end
end

login(メアド, パスワード)でログインできる。
redirect_back_or_toで#not_authenticatedでリダイレクトする前のページに戻せる。
logout()でログアウト。

app/views/user_sessions/new.html.haml

%h1= t '.title'
= form_with url: login_path, method: :post do |f|
  = f.label t('.email'), class: 'form-label'
  = f.text_field :email, class: 'form-control mb-2'

  = f.label t('.password'), class: 'form-label'
  = f.password_field :password, class: 'form-control mb-4'

  = f.submit t('.submit'), class: 'btn btn-primary'

config/routes.rb

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

  get 'login' => 'user_sessions#new', as: :login
  post 'login' => 'user_sessions#create'
  post 'logout' => 'user_sessions#destroy', as: :logout
  resources :users

  resources :communities, only: %i[index new create show] do
    resources :comments, only: %i[new create]
  end
end

4-4. おまけ

flashメッセージを表示する。

app/views/layouts/application.html.haml

!!!
%html
  %head
    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
    %title Rails61HandsOn
    %meta{:content => "width=device-width,initial-scale=1", :name => "viewport"}/
    = csrf_meta_tags
    = csp_meta_tag
    = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
    = stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
  %body
    = render '/layouts/header'
    - if flash[:notice]
      .alert.alert-info.m-2= flash[:notice]
    - if flash[:alert]
      .alert.alert-warning.m-2= flash[:alert]
    .container-fluid.pt-4.px-4
      = yield
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3