フロントエンドエンジニアがスキルアップ目指してRuby on Railsを勉強します!サインアップ〜ログイン機能を作ってみる編

このエントリーをはてなブックマークに追加

フロントエンドエンジニアがスキルアップ目指してRuby on Railsを勉強します!サインアップ〜ログイン機能を作ってみる編

今回はサインアップ・ログイン・ログアウトあたりの機能を実装してみたいと思います。チュートリアルサイトを参考にしながらわからないところは調べながらやってみました。まだまだわからないことだらけですけど、なんとなくRailsの良さがわかってきた?

Ads

はじめに

とりあえず何からやればいいのかわからないので、第1章 ゼロからデプロイまで | Rails チュートリアルRuby on Railsでユーザ登録/ログイン機能を自作する方法 – ztbuz@devを見ながらやって見ました。作って見て動きを見ながらわからないところを補足で調べながらっていう風に勉強を進めました。

なんかrailsアプリが作れなかった件

rails new appしてもできなかった

Rails is not currently installed on this system. To get the latest version, simply type:

    $ sudo gem install rails

You can then rerun your "rails" command.

が出てくる。出てこない人はここは無視して大丈夫です。

$ echo 'export PATH="$HOME/.rbenv/shims:$PATH" ' >> ~/.bash_profile

ってしてたんだけど、あーzshもやらなきゃデスよね。あほでした。

$ echo 'export PATH="$HOME/.rbenv/shims:$PATH" ' >> ~/.zshrc

これでできました。

とりあえず開発環境

サーバOS: Mac OS X
Ruby 2.3.1
Rails 5.0.0.1
SQL MySQL2

インストール

Gemfile

source 'https://rubygems.org'


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use Puma as the app server
gem 'puma', '~> 3.0'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
#
gem 'mysql2'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platform: :mri
end

group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console'
  gem 'listen', '~> 3.0.5'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

gem 'bcrypt-ruby', '3.1.1.rc1', :require => 'bcrypt'
bundle install

Rails用のMySQLユーザを作成

MySQLにログイン

mysql -u root -p

ユーザー作成

CREATE USER 'rails'@'localhost' IDENTIFIED BY 'hoge';

権限付与

GRANT ALL PRIVILEGES ON *.* TO 'rails'@'localhost';

データベース設定ファイル修正

config/database.yml

# MySQL2
#   gem install mysql2
#
#   gem 'mysql2'
#
default: &default
  adapter: mysql2
  encoding: utf8
  database: Rails_development
  pool: 5
  username: rails
  password: hoge
  socket: /tmp/mysql.sock

development:
  adapter: mysql2
  encoding: utf8
  database: Rails_development
  pool: 5
  username: rails
  password: hoge
  socket: /tmp/mysql.sock

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: mysql2
  encoding: utf8
  database: Rails_test
  pool: 5
  username: rails
  password: hoge
  socket: /tmp/mysql.sock

production:
  adapter: mysql2
  encoding: utf8
  database: Rails_production
  pool: 5
  username: rails
  password: hoge
  socket: /tmp/mysql.sock

準備するコマンド

$ bundle exec rake db:create
$ rails g model User name:string email:string password_digest:string remember_token:string
$ rails g controller Sessions
$ rails g controller Users
$ touch app/views/sessions/new.html.erb
$ touch app/views/users/edit.html.erb
$ touch app/views/users/index.html.erb
$ touch app/views/users/show.html.erb
$ touch app/views/users/new.html.erb
$ bundle exec rake db:migrate

サインイン&ログイン機能の実装

では早速作ってみます。機能としては

  • トップページ
  • サインアップ
  • ログイン
  • ログアウト
  • ユーザー一覧
  • ユーザー詳細
  • ユーザー情報の編集

こんな感じです。

config/routes.rb

REST(Representational State Transfer)

RESTとはWebアプリケーションなどにおける設計概念の一つで、すべてのリソースに一意の識別子(URI)をつけ、そのリソースに対して動作を決めてアクセスします。

ちょっと難しいけど

アプリケーションを構成するコンポーネント(Userなど)を、GET/POST/PUT/DELETEなどのHTTPRequestの各メソッド
に対応させて、自由に作成/読み出し/更新/削除できるもの(リソース)として扱うアーキテクチャのことらしい。

usersと同じようにsessionsもRESTfulに管理しているんですね

Rails.application.routes.draw do
  resources :users, only: [:index, :show, :new, :edit, :create, :update]
  resources :sessions, only: [:new, :create, :destroy]

  match 'signup', to: 'users#new', via: 'get'
  match 'signin', to: 'sessions#new', via: 'get'
  match 'signout', to: 'sessions#destroy', via: 'delete'

  root 'users#index'
end

viewsから

まずはviewsの中身から進めていきます。

app/views/layouts/application.html.erb

トップページに相当するところです。

<!DOCTYPE html>
<html>
  <head>
    <title>RailsApp</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
  <ul>
    <li><%= link_to 'ユーザ一覧', users_path %></li>
    <% if signed_in? %>
      <li><%= link_to 'プロフィール', current_user %></li>
      <li><%= link_to 'ログアウト', signout_path, method: :delete %></li>
    <% else %>
      <li><%= link_to 'ログイン', signin_path %></li>
      <li><%= link_to 'アカウント作成', signup_path %></li>
    <% end %>
  </ul>

    <%= yield %>
  </body>
</html>

viewsの中の<%= yield %>は、hoge.html.erbのコンテンツが入るとこ。app/views/layouts/application.html.erbはテンプレートなんですね。

ログイン・ログアウト時の出し分け。まあ、普通によく見るパターン。

<% if signed_in? %>
  // ログイン
<% else %>
  // ログアウト
<% end %>

app/views/sessions/new.html.erb

ここはログイン機能を実装しているとこですね。今回はメールアドレスをパスワードでログイン。

<h1>ログイン</h1>

<%= form_for(:session, url: sessions_path) do | f | %>
  <dl>
    <dt><%= f.label :email, 'メールアドレス' %></dt>
    <dd><%= f.text_field :email %></dd>
  </dl>
  <dl>
    <dt><%= f.label :password, 'パスワード' %></dt>
    <dd><%= f.text_field :password %></dd>
  </dl>
  <%= f.submit 'ログインする' %>
<% end %>

セッションはモデルが存在しないため、リソース名と対応するURLを指定する必要があるらしい。

<%= form_for(:session, url: sessions_path) do | f | %>

ちょっと特殊な書き方!

app/views/users/index.html.erb

ユーザーの一覧ページ

<h1>ユーザー一覧ページ</h1>

<ul>
  <% @users.each do | user | %>
  <li><%= link_to user.name, user %></li>
  <% end %>
</ul>

@usersの情報を繰り返し表示

オブジェクト.each do |変数|
  実行する処理1
  実行する処理2
end

<%= link_to user.name, user %>link_toメソッドを使ってます。

<%= link_to '表示名', リンク先 %>

app/views/users/new.html.erb

ユーザー新規登録ページ

<h1>ユーザー新規登録ページ</h1>

<%= form_for(@user) do |f| %>

  <dl>
    <dt><%= f.label :name, '名前' %></dt>
    <dd><%= f.text_field :name %></dd>
  </dl>
  <dl>
    <dt><%= f.label :email, 'メールアドレス' %></dt>
    <dd><%= f.text_field :email %></dd>
  </dl>
  <dl>
    <dt><%= f.label :password, 'パスワード' %></dt>
    <dd><%= f.password_field :password %></dd>
  </dl>
  <dl>
    <dt><%= f.label :password_confirmation, '確認' %></dt>
    <dd><%= f.password_field :password_confirmation %></dd>
  </dl>

  <%= f.submit 'アカウントを作成する' %>

<% end %>

do〜endまでがformであることを表していて、|f|は変数で、formをさしています。
@userはcontrollerから受け取っています。

app/views/users/edit.html.erb

ユーザー編集ページ

やっていることは「ユーザー新規登録ページ」と同じですね。

<h1>ユーザー編集ページ</h1>

<%= form_for(@user) do |f| %>

  <dl>
    <dt><%= f.label :name, '名前' %></dt>
    <dd><%= f.text_field :name %></dd>
  </dl>
  <dl>
    <dt><%= f.label :email, 'メールアドレス' %></dt>
    <dd><%= f.text_field :email %></dd>
  </dl>
  <dl>
    <dt><%= f.label :password, 'パスワード' %></dt>
    <dd><%= f.password_field :password %></dd>
  </dl>
  <dl>
    <dt><%= f.label :password_confirmation, '確認' %></dt>
    <dd><%= f.password_field :password_confirmation %></dd>
  </dl>

  <%= f.submit 'アカウントを編集する' %>

<% end %>

app/views/users/show.html.erb

ユーザー詳細ページ

<h1>ユーザー詳細ページ</h1>

<dl>
  <dt>名前</dt>
  <dd><%= @user.name %></dd>
</dl>
<dl>
  <dt>メールアドレス</dt>
  <dd><%= @user.email %></dd>
</dl>

<% if current_user?(@user) %>
  <p><%= link_to '編集', edit_user_path %></p>
<% end %>

controllerの@user = User.find(params[:id])でparamsのidでユーザーを出し分けてるんですね。

current_userはログイン中かどうかの判定です。sessions_controllerでやっているっぽい?

controllers

app/controllers/application_controller.rb

SessionsHelperをコントローラにinclude

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

app/controllers/sessions_controller.rb

  • createはサインイン
  • destroyはサインアウト
class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      // ユーザーをサインインさせ、ユーザーページ (show) にリダイレクトする。
      sign_in user
      redirect_to user
    else
      // エラーを表示したり
      render 'new'
    end
  end

  def destroy
    sign_out
    redirect_to root_url
  end
end

該当emailのユーザが存在するかどうか

User.find_by(email: params[:session][:email].downcase)

パスワードが適切かどうか

user.authenticate(params[:session][:password])

app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_action :signed_in_user, only: [:edit, :update]
  before_action :correct_user, only: [:edit, :update]

  def index
    @users = User.all
  end

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

  def new
    @user = User.new
  end

  def edit
  end

  def create
    @user = User.new(user_params)
    if @user.save
      sign_in @user
      redirect_to @user
    else
      render 'new'
    end
  end

  def update
    if @user.update_attributes(user_params)
      redirect_to @user
    else
      render 'edit'
    end
  end

  private

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

    def correct_user
      @user = User.find(params[:id])
      redirect_to root_url unless current_user?(@user)
    end

end

privateとすることでメソッドの呼び出しを制限できます。privateだと同じクラスやサブクラス内のメソッドの中だけで呼び出せます。

helpers

app/helpers/sessions_helper.rb

sign_in関連のメソッドの実装しているところ

  • サインイン状態を永続化
  • ユーザが明示的にサインアウトしたときのみ破棄

流れ

  1. 新たにremember_token生成
  2. cookieにremember_tokenを保管
  3. 暗号化の上dbにも保管
  4. コントローラからもビューからもアクセスできる@current_userを準備
  5. ユーザアクセスの度、cookieの中身とdbの中身を擦り合わせて照合
  6. サインアウトでセッションを破棄する

[Rails] セッション管理をベタで実装してみる – Qiitaがすっごい参考になりました。

module SessionsHelper
  def sign_in(user)
    remember_token = User.new_remember_token
    cookies.permanent[:remember_token] = remember_token
    user.update_attribute(:remember_token, User.encrypt(remember_token))
    self.current_user = user
  end

  def sign_out
    self.current_user = nil
    cookies.delete(:remember_token)
  end

  def current_user=(user)
    @current_user = user
  end

  def current_user
    remember_token = User.encrypt(cookies[:remember_token])
    @current_user ||= User.find_by(remember_token: remember_token)
  end

  def current_user?(user)
    user == current_user
  end

  def signed_in?
    !current_user.nil?
  end

  def signed_in_user
    redirect_to signin_url unless signed_in?
  end
end

models

app/models/user.rb

class User < ApplicationRecord
  before_save { email.downcase! }

  has_secure_password

  def self.new_remember_token
    SecureRandom.urlsafe_base64
  end

  def self.encrypt(token)
    Digest::SHA1.hexdigest(token.to_s)
  end
end

SecureRandom を利用した乱数の生成

def self.new_remember_token
  SecureRandom.urlsafe_base64
end

万一dbが攻撃されて漏洩された場合に備えて暗号化

def self.encrypt(token)
  Digest::SHA1.hexdigest(token.to_s)
end

暗号化されたパスワード認証はこれだけでよしなにしてくれるみたいです。

使用条件もあるみたい

この魔術的なhas_secure_password機能を使えるようにするには、1つだけ条件があります。それは、モデル内にpassword_digestという属性が含まれていることです。

has_secure_password

まとめ

サイトを参考にさせてもらいながらなんとかですけど実装することができました。まだまだ自分のものにできてないですけど・・・

記事も記事あまりまとまってないですねー。もっとわかりやすくしたかったんですが、自分もあまり理解できていないのでご容赦ください。
備忘録と思っていただければ幸いです。

イチマルニデザインブログをフォローしよう

イチマルニデザインブログではTwitterアカウントでWebに関する情報をつぶやいています。フォローすることで最新情報をすぐに受け取ることができます

いいなと思ったらシェアお願いします

このエントリーをはてなブックマークに追加

同じカテゴリーの記事

Ads
ページの先頭へ