学習日記

Ruby on Rails勉強してます

今日やったこと9

第10章 ユーザーの更新・表示・削除

10.1 ユーザーを更新する

ユーザーを編集するためのeditアクションを作成する。

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


editアクションに対応するeditビューを実装
  app/views/users/edit.html.erb


WebブラウザはネイティブではPATCHリクエストを送信できないので、RailsはPOSTリクエストとinputフィールドを利用してPATCHリクエストを「偽造」している。

<input name="_method" type="hidden" value="patch" />


Railsは、form_for(@user)を使ってフォームを構成すると、@user.new_record?がtrueのときにはPOSTを、falseのときにはPATCHを使う。

$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false


ヘッダーに”Setting”リンクを追加する。
editビューへのパスとcurrent_userのヘルパーメソッドを使う。

<%= link_to "Settings", edit_user_path(current_user) %>


演習
・10.1.1.1
target="_blank"で新しいページを開くときには、セキュリティのためrel属性にnoopenerを設定する。

<a href="http://gravatar.com/emails" target="_blank" rel="noopener">change</a>

演習
・10.1.3.1
assert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というエラーメッセージが表示されているかテストする行を追加する。

assert_select "div.alert", "The form contains 4 errors."


Updateアクションを書くまえに編集の成功に対するテストを書く。
テストを先に書くことでユーザーが実際に使うイメージや追加するコードがわかりやすくなる。

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

・
・
・
  
  # 編集の成功に対するテスト
  test "successful edit" do
    get edit_user_path(@user)
    assert_template 'user/edit'
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirect_to @user
    @user.reload
    assert_equal name, @user.name
    assert_equal email, @user.email
  end
end


ユーザー名やメールアドレスだけを編集したいときはパスワード欄を入力する必要はないが空のままだとバリテーションに引っかかってしまうので空のままでも更新できるようにvalidatesにallow_nil: trueというオプションを追加する。

validates :password, presence: true, length: { minimum: 6 }, allow_nil: true


updateアクションを作成しテストする。

def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
end

10.2 認可

今のままでは誰でも (ログインしていないユーザーでも) ユーザー情報を編集できてしまうのでユーザーにログインを要求し、かつ自分以外のユーザー情報を変更できないようにする。
ログインしていないユーザーが保護されたページにアクセスしようとしたときはログインページに転送して、メッセージも表示するようにする。
許可されていないページに対してアクセスするログイン済みのユーザーがいたら 、ルートURLにリダイレクトさせるようにする。

転送させる仕組みを実装したいときは、Usersコントローラの中でbeforeフィルターを使う。beforeフィルターは、before_actionメソッドを使って何らかの処理が実行される直前に特定のメソッドを実行する仕組みである。

ユーザーにログインを要求するために、logged_in_userメソッドを定義してbefore_action :logged_in_userという形式で使用する。
デフォルトでは、beforeフィルターはコントローラ内のすべてのアクションに適用されるので、ここでは適切な:onlyオプション (ハッシュ) を渡すことで、:editと:updateアクションだけにこのフィルタが適用されるように制限をかけている。

app/controllers/users_controller.rb


class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  .
  .
  .
  private

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

    # beforeアクション

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
end


editアクションやupdateアクションをテストする前にログインしておく必要があるので、log_in_asヘルパーを使ってテストを修正する。

test/integration/users_edit_test.rb


require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    log_in_as(@user)
    get edit_user_path(@user)
    .
    .
    .
  end

  test "successful edit" do
    log_in_as(@user)
    get edit_user_path(@user)
    .
    .
    .
  end
end


beforeフィルターが機能しているかどうかのテストを追加する。

test/controllers/users_controller_test.rb


require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest
  
  def setup
    @user = users(:michael)
  end
  ・
  ・
  ・
  # editアクションの保護に対するテスト
  test "should redirect edit when not logged in" do
    get edit_user_path(@user)
    assert_not flash.empty?
    assert_redirected_to login_url
  end
  
  # updateアクションの保護に対するテスト
  test "should redirect update when not logged in" do
    patch user_path(@user), params: { user: { name: @user.name,
                                              email: @user.email } }
    assert_not flash.empty?
    assert_redirected_to login_url
  end

end


ユーザーが自分の情報だけを編集できるようにする必要があるためのテストを追加する。
ユーザーの情報が互いに編集できないことを確認するために、ユーザー用のfixtureファイルに2人目のユーザーを追加する。

test/fixtures/users.yml


michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>

# 2人目のユーザーを追加する
archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>


次に、 log_in_asメソッドを使って、editアクションとupdateアクションをテストする。

test/controllers/users_controller_test.rb


require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer) #2人目のユーザー
  end
  .
  .
  .
  test "should redirect edit when logged in as wrong user" do
    log_in_as(@other_user)
    get edit_user_path(@user)
    assert flash.empty?   #ユーザーが違う際にメッセージが出ているか
    assert_redirected_to root_url #ルートURLにリダイレクトしているか
  end

  test "should redirect update when logged in as wrong user" do
    log_in_as(@other_user)
    patch user_path(@user), params: { user: { name: @user.name,
                                              email: @user.email } }
    assert flash.empty?
    assert_redirected_to root_url
  end
end


別のユーザーのプロフィールを編集しようとしたらリダイレクトするようにさせるためにcorrect_userというメソッドを作成し、beforeフィルターからこのメソッドを呼び出すようにする。

app/controllers/users_controller.rb


class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  #editとupdateの各アクションでcorrect_userを呼び出す
  before_action :correct_user,   only: [:edit, :update] 
  .
  .
  .
  def edit
  end

  def update
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end
  .
  .
  .
  private

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

    # beforeアクション

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless @user == current_user
    end
end

今日やったこと8

Ruby on Rails チュートリアル(第4版) 第9章 発展的なログイン構造

(9.1 Remember me機能 〜 9章最後まで)

 

9.1 remenber機能

ユーザーのログイン状態をブラウザを閉じた後でも有効にする機能、ユーザーがログアウトしない限り、ログイン状態を維持できる。

トークン認証に記憶トークンを使うため、remenber_gigest属性をUserモデルに追加する。

 

9.1.2 ログイン状態の保持

ユーザーの暗号化済みIDと記憶トークンをブラウザの永続cookiesに保存して、永続セッションを作成するためcookiesメソッドをを使う。

記憶トークンと記憶ダイジェストをユーザごとに関連付けて、永続セッションが実現できる。

 

9.1.3 ユーザーを忘れる

ログアウトできるようにするため、user.forgetメソッドを定義し、update_attributeメソッドでremember_digestを取り消す。

 

9.2 Remember meチェックボックス

ログインフォームにチェックボックスを追加する。

他のフォームと同様にヘルパーメソッドで作成する。

セッションコントローラのcreateアクションにRemember meチェックボックスの痩身結果の処理を追加する。

Remember meチェックボックスのテスト。

今日やったこと7

Ruby on Rails チュートリアル(第4版) 第8章 基本的なログイン構造

(8.1 セッション 〜 8章最後まで)

 

セッション

SessionというRailsのメソッドを使って一時セッションを作成する。

UserリソースはバックエンドでUserモデルを介してデータベース上のデータにアクセスするが、Sessionリソースはcookiesを保存場所として使う。

Sessionコントローラを生成。

 

演習8.1.1.2で使ったパイプとgrepコマンド

パイプとは

コマンドとコマンドの間に「|」を使うことで複数のコマンドを組み合わせて使うことができる。

grepコマンド

指定したパターンにマッチする行を表示するコマンド。

 

ログインフォーム

SessionActive Recordオブジェクトではない(Sessionモデルがない)のでユーザー登録フォームでform_forヘルパーを使う場合は引数にインスタンス変数ではなく、リソースの名前とそれに対応するURLを具体的に指定する必要がある。

 

ユーザーの検索と認証

ログインが送信されるたびに、パスワードとメールアドレスの組み合わせが有効かどうかを判断する。

認証にはUser.find_byメソッドとauthenticateメソッドを使う。

 

フラッシュメッセージを表示する

ユーザー登録の場合はActive Recordオブジェクトによってエラーメッセージを表示できたが、セッションではActive Recordのモデルを使っていないので自分でフラッシュメッセージを表示させる。

flashを使うとrenderメソッドでレンダリングしてもメッセージが残ったままになってしまうため、flash.nowを使う。

flash.nowのメッセージはその後リクエストが発生した時に消滅する。

 

ログイン

session[:user_id]を使ってユーザーIDを代入する。

sessionメソッドで作成された一時cookiesは、ブラウザを閉じた瞬間に有効期限が終了する。

ユーザーIDを一時セッションに保存できたら、そのユーザーIDを別のページで取り出すためにcurrent_userメソッドを定義する。

レイアウトリンクを修正・テストする。

ユーザー登録と同時にログインできるように修正。

 

ログアウト

ログアウトの処理ではセッションからユーザーIDを削除するためにdeleteメソッドを使う。

 

今日やったこと6

Ruby on Rails チュートリアル(第4版) 第7章 ユーザー登録

(7.3 ユーザー登録失敗 〜 7章最後まで)

 

・ユーザー登録を成功

 

新規ユーザーを実際にデータベースに保存できるようにし、ユーザー登録フォームを完成させる。 

保存に成功すると、ユーザー情報は自動的にデータベースに登録され、次にブラウザの表示をリダイレクトして、登録されたユーザーのプロフィールを表示する。

Railsはデフォルトのアクションに対応するビューを表示しようとするが、Createアクションには対応するビューのテンプレートがない。テンプレートを作成することもできるが一般的には別のページ(今回は新しく作成されたユーザーのプロフィールページ)にリダイレクトするようにする。

 

flash

 

登録完了後に表示させるページにメッセージを表示させるためにflash変数を使う。

flash[:success]といった:successキーを文字列として(本来はシンボルだが)利用することで、キーの内容によって異なったCSSクラスを適用させることができ、メッセージの種類によって動的に変更させることができる。

Bootstrap CSSflashのクラス用に4つのスタイルがある(successinfowarningdanger)

 

・本番環境でのSSL(Secure Sockets Layer)

 

フォームで送信したデータはネットワーク上で補足できてしまうため、個人情報などの扱いには注意が必要で、このデータを保護するためにSLLを使用する。

SLLはローカルのサーバーからネットワークに流れる前に、大事な情報を暗号化する技術。

本番用のWebサイトでSSLを使えるようにするためには、ドメイン毎にSSL証明書を購入しセットアップする必要がある

Heroku上でサンプルアプリケーションを動かす場合は、Heroku上のSSL証明書に便乗する方法がある。(サブドメインでのみ有効)

HerokuのデフォルトではWEBrickというサーバを使っているが、本番環境として適切なサーバではないため、PumaというWebサーバに置き換える。

今日やったこと5

Ruby on Rails チュートリアル(第4版) 第7章 ユーザー登録

(7.1 ユーザーを表示する 〜 7.3 ユーザー登録失敗)

 

・ユーザーを表示する

サイトのレイアウトにデバック情報を追加

If Rails.env.development? で3つある環境のうち開発環境だけで表示される

デバック情報は開発環境以外では表示させないほうが良い。

 

Rails3つの環境

デフォルトではテスト環境(test)、開発環境(development)、本番環境(production)の3

の環境がある。

Rails console の環境はdevelopment

 

debuggerメソッド

Byebug gemによるdebuggerメソッドである。

Debuggerメソッドをアプリケーションに差し込みRailsサーバーを立ち上げたターミナルで確認する。

Railsアプリケーションの中でよく分からない挙動があったら、トラブルが起こっていそうなコードの近くにdebuggerを差し込んで調べてみる。

 

・ユーザー登録フォームの作成

入力フォームを作るためRailsform_forヘルパーメソッドを使う。

今日やったこと4

Ruby on Rails チュートリアル(第4版) 第6章 ユーザーモデルを作成する

(6.2.4 ユーザーを検証する_フォーマットを検証する 〜 6章の最後まで)

 

・データベースのインデックス

カラムにインデックスを追加することで、送信されたデータと一致するデータをデータベースから検索する際、効率よく検索できるようになる。

・セキュアなパスワードを追加する

パスワードとパスワードの確認を入力したものをハッシュ化し保存する。

ハッシュはRubyのデータ構造だけどこの「ハッシュ化」とは別で、ハッシュ関数を使って、入力されたデータを元に戻せないデータにすること。

データベース内ではなくハッシュ化されたパスワード同士を比較しているので、仮にデータベースの内容が盗まれたり覗かれたりされても、パスワードの安全性は保たれる。

has_secure_passwordメソッドを使うことで、モデルに対してセキュアなパスワードを追加することができる

・ユーザーの作成と認証

コンソールでユーザーを作成しデータベースをDB Browser for SQLliteで開き、usersテーブルの中身を確認

今日やったこと3

今日はRailsチュートリアルの6章に出てくる正規表現について学習しました。

正直、正規表現のコード見てもさっぱり分からずRailsチュートリアルの説明だけではわかりずらかったので下記の記事を参考にさせて頂きました。

 

初心者歓迎!手と目で覚える正規表現入門・その1「さまざまな形式の電話番号を検索しよう」(https://qiita.com/jnchito/items/893c887fbf19e17d3ff9#)

初心者歓迎!手と目で覚える正規表現入門・その2「微妙な違いを許容しつつ置換しよう」(https://qiita.com/jnchito/items/64c3fdc53766ac6f2008)

初心者歓迎!手と目で覚える正規表現入門・その3「空白文字を自由自在に操ろう」(https://qiita.com/jnchito/items/6f0c885c1c4929092578)

初心者歓迎!手と目で覚える正規表現入門・その4(最終回)「中級者テクニックをマスターしよう」(https://qiita.com/jnchito/items/b0839f4f4651c29da408)

Rails正規表現でよく使われる \A \z って何??(https://qiita.com/jnchito/items/ea7832df6f64a9034872#_reference-12cb3cafc745f4e14e3a)

 

その3までは結構理解できたと思うんですがその4の最後のほうは難しかったです。

少なくともRailsチュートリアルで出てきた正規表現のコードは理解できるようになったので明日から再開していきます。

 

それと上記の記事を書いておられる伊藤淳一さんの説明がすごく丁寧で分かりやすかったので伊藤淳一さんの書いたRubyの本を購入しました。

 Railsチュートリアルと合わせて学習していこうと思います。