SINKCAPITAL
SINKCAPITAL
company Blog
Ruby on Rails チュートリアル第12章をやってみて
techinternlearning

パスワードの再設定

■第12章
本章では、ここで生成したメイラーにリソースとデータモデルを追加して、パスワードの再設定を実現していく。

12.1 PasswordResetsリソース

必要なデータをUserモデルに追加していく。11章ではeditアクションのみだったが、今回はフォームが必要なのでnewアクションとeditアクションが必要。

12.1.1 PasswordResetsコントローラ

パスワード設定用のコントローラを作成する。今回はビューも扱うため、newアクションとeditアクションも一緒に生成する。

$ rails generate controller PasswordResets new edit --no-test-framework

新しいパスワードを再設定するためのフォームと、Userモデル内のパスワードを変更するためのフォームが必要になるので、newcreateeditupdateのルーティングも用意する。

以下のコードを追加。

resources :password_resets,     only: [:new, :create, :edit, :update]

パスワード再設定画面へのリンクを追加して、テストもGREENに。

12.1.2 新しいパスワードの設定

トークン用の仮想的な属性とそれに対応するダイジェストを用意する。 また、トークンを平文でデータベースに保存してしまうとセキュリティ上の問題があるため、必ずダイジェストを利用する。

再設定用のリンクはなるべく短時間で期限切れになるようにする。

reset_digest属性とreset_sent_at属性をUserモデルに追加。

$ rails generate migration add_reset_to_users reset_digest:string \
> reset_sent_at:datetime

その後はマイグレーション。

ログインフォームのコードを改変して、下記を作成。form_forで扱うリソースとURLが異なっている点と、パスワード属性が省略されている点が違う。

<% provide(:title, "Forgot password") %>
<h1>Forgot password</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(:password_reset, url: password_resets_path) do |f| %>
      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.submit "Submit", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

無事、「forgot password」フォームが完成。

スクリーンショット

12.1.3 createアクションでパスワード再設定

フォームから送信を行なった後、メールアドレスをキーとしてユーザーをデータベースから見つけ、パスワード再設定用トークンと送信時のタイムスタンプでデータベースの属性を更新する必要がある。

それに続いてルートURLにリダイレクトし、フラッシュメッセージをユーザーに表示する。

送信が無効の場合、ログインと同様にnewページを出力してflash.nowメッセージを表示する。

class PasswordResetsController < ApplicationController

  def new
  end

  def create
    @user = User.find_by(email: params[:password_reset][:email].downcase)
    if @user
      @user.create_reset_digest
      @user.send_password_reset_email
      flash[:info] = "Email sent with password reset instructions"
      redirect_to root_url
    else
      flash.now[:danger] = "Email address not found"
      render 'new'
    end
  end

  def edit
  end
end

パスワード再設定の属性を設定する。トークンを生成し、それをダイジェストに変換して設定。時間も設定する。

ここまでで、無効なメールアドレスを入力したときの動作が完成。 有効なメールアドレスを入力をした場合の動作を次からやる。

12.2 パスワード再設定のメール送信

パスワード再設定に関するメールを送信する部分を作っていく。

Userメイラーを生成したときに、デフォルトのpassword_resetメソッドもまとめて生成されているはず。

12.2.1 パスワード再設定のメールとテンプレート

パスワード再設定のリンクをメール送信する。

password_resetメソッドを作成。

def password_reset(user)
    @user = user
    mail to: user.email, subject: "Password reset"
  end

テキストとHTMLも改変する。 プレビューも完成させる。

# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview

  # Preview this email at
  # http://localhost:3000/rails/mailers/user_mailer/account_activation
  def account_activation
    user = User.first
    user.activation_token = User.new_token
    UserMailer.account_activation(user)
  end

  # Preview this email at
  # http://localhost:3000/rails/mailers/user_mailer/password_reset
  def password_reset
    user = User.first
    user.reset_token = User.new_token
    UserMailer.password_reset(user)
  end
end

プレビューできました。

スクリーンショット

12.2.2 送信メールのテスト

メイラーメソッドのテストを書いていく。

require 'test_helper'

class UserMailerTest < ActionMailer::TestCase

  test "account_activation" do
    user = users(:michael)
    user.activation_token = User.new_token
    mail = UserMailer.account_activation(user)
    assert_equal "Account activation", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.name,               mail.body.encoded
    assert_match user.activation_token,   mail.body.encoded
    assert_match CGI.escape(user.email),  mail.body.encoded
  end

  test "password_reset" do
    user = users(:michael)
    user.reset_token = User.new_token
    mail = UserMailer.password_reset(user)
    assert_equal "Password reset", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.reset_token,        mail.body.encoded
    assert_match CGI.escape(user.email),  mail.body.encoded
  end
end

テストもGREENに。

12.3 パスワードを再設定する

editアクションの実装を進める。統合テストも書いていく。

12.3.1 editアクションで再設定

メールアドレスを保持するため、隠しフィールドとしてページ内に保存する手法をとる。

<% provide(:title, 'Reset password') %>
<h1>Reset password</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= hidden_field_tag :email, @user.email %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Update password", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

editupdateでユーザー情報を取得して、正しいユーザーでなければルートページに飛ばす。

class PasswordResetsController < ApplicationController
  before_action :get_user,   only: [:edit, :update]
  before_action :valid_user, only: [:edit, :update]
  .
  .
  .
  def edit
  end

  private

    def get_user
      @user = User.find_by(email: params[:email])
    end

    # 正しいユーザーかどうか確認する
    def valid_user
      unless (@user && @user.activated? &&
              @user.authenticated?(:reset, params[:id]))
        redirect_to root_url
      end
    end
end

12.3.2 パスワードを更新する

今回はフォームから新しいパスワードを送信するようになっています。したがって、フォームからの送信に対応するupdateアクションが必要になる。

updateアクションでは以下の4つを考慮する

  1. パスワード再設定の有効期限が切れていないか
  2. 無効なパスワードであれば失敗させる
  3. 新しいパスワードが空文字列になっていないか
  4. 新しいパスワードが正しければ更新する

①、②、④はbefore_actionで対応。

③がちょっと小難しい。今回は@userオブジェクトにエラーメッセージを追加する方法をとってみる。

@user.errors.add(:password, :blank)

こう書くと、パスワードが空だった時に空の文字列に対するデフォルトのメッセージを表示してくれる。

パスワード再設定のupdateアクション。

class PasswordResetsController < ApplicationController
  before_action :get_user,         only: [:edit, :update]
  before_action :valid_user,       only: [:edit, :update]
  before_action :check_expiration, only: [:edit, :update]    # (1) への対応

  def new
  end

  def create
    @user = User.find_by(email: params[:password_reset][:email].downcase)
    if @user
      @user.create_reset_digest
      @user.send_password_reset_email
      flash[:info] = "Email sent with password reset instructions"
      redirect_to root_url
    else
      flash.now[:danger] = "Email address not found"
      render 'new'
    end
  end

  def edit
  end

  def update
    if params[:user][:password].empty?                  # (3) への対応
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update_attributes(user_params)          # (4) への対応
      log_in @user
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'                                     # (2) への対応
    end
  end

  private

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

    # beforeフィルタ

    def get_user
      @user = User.find_by(email: params[:email])
    end

    # 有効なユーザーかどうか確認する
    def valid_user
      unless (@user && @user.activated? &&
              @user.authenticated?(:reset, params[:id]))
        redirect_to root_url
      end
    end

    # トークンが期限切れかどうか確認する
    def check_expiration
      if @user.password_reset_expired?
        flash[:danger] = "Password reset has expired."
        redirect_to new_password_reset_url
      end
    end
end
reset_sent_at < 2.hours.ago

これで「パスワード再設定メールの送信時刻が、現在時刻より2時間以上前 (早い)」 という意味になる。

12.3.3 パスワードの再設定をテストする

送信に成功した場合と失敗した場合の統合テストを作成する。

テストファイル作成。

$ rails generate integration_test password_resets
require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear
    @user = users(:michael)
  end

  test "password resets" do
    get new_password_reset_path
    assert_template 'password_resets/new'
    # メールアドレスが無効
    post password_resets_path, params: { password_reset: { email: "" } }
    assert_not flash.empty?
    assert_template 'password_resets/new'
    # メールアドレスが有効
    post password_resets_path,
         params: { password_reset: { email: @user.email } }
    assert_not_equal @user.reset_digest, @user.reload.reset_digest
    assert_equal 1, ActionMailer::Base.deliveries.size
    assert_not flash.empty?
    assert_redirected_to root_url
    # パスワード再設定フォームのテスト
    user = assigns(:user)
    # メールアドレスが無効
    get edit_password_reset_path(user.reset_token, email: "")
    assert_redirected_to root_url
    # 無効なユーザー
    user.toggle!(:activated)
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_redirected_to root_url
    user.toggle!(:activated)
    # メールアドレスが有効で、トークンが無効
    get edit_password_reset_path('wrong token', email: user.email)
    assert_redirected_to root_url
    # メールアドレスもトークンも有効
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_template 'password_resets/edit'
    assert_select "input[name=email][type=hidden][value=?]", user.email
    # 無効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "barquux" } }
    assert_select 'div#error_explanation'
    # パスワードが空
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "",
                            password_confirmation: "" } }
    assert_select 'div#error_explanation'
    # 有効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "foobaz" } }
    assert is_logged_in?
    assert_not flash.empty?
    assert_redirected_to user
  end
end

これでテストもGREENに。

感想

12章はすんなり終えることができました。ただ、パスワードの再設定の実装だけでも、ゼロからやるとなると苦労しそうだなーという印象でした。

BQにおけるSQL検算を効率化する無料chrome拡張機能をリリースいたしました
櫻井 裕司
2022/09/01 櫻井 裕司
tech
BigQueryのjoin句を含むstandardSQLを入力することで、join前後でのレコード数の変化を返すSQLを自動でクリップボードにコピーする無料chrome拡張機能をリリースいたしました。
社内ドキュメントにNotionを導入して感じた事
櫻井 裕司
2022/04/02 櫻井 裕司
tech
社内ドキュメントをNotionに寄せることで見えてきたメリット・デメリットをまとめていきたいと思います。また使う中で感じたいくつかの要望もまとめていこうと思います。
「BIツール」活用 超入門 Google Data Portalではじめるデータ集計・分析・可視化 第3章 BIツールに関する知識をつける
白井 透
2022/03/31 白井 透
techinternlearning
【「BIツール」活用 超入門 Google Data Portalではじめるデータ集計・分析・可視化 第3章】現在長期インターンをさせてもらっているSinkCapitalさんの方で、データ系の業務に携わることになりそうなのですが、それの準備期間として紹介していただいた本をまとめていきたいと思います。
「BIツール」活用 超入門 Google Data Portalではじめるデータ集計・分析・可視化 第2章 さまざまな分析をしてみよう
白井 透
2022/03/30 白井 透
techinternlearning
【「BIツール」活用 超入門 Google Data Portalではじめるデータ集計・分析・可視化 第2章】現在長期インターンをさせてもらっているSinkCapitalさんの方で、データ系の業務に携わることになりそうなのですが、それの準備期間として紹介していただいた本をまとめていきたいと思います。
「BIツール」活用 超入門 Google Data Portalではじめるデータ集計・分析・可視化 第1章 分析ダッシュボードを作ってみよう
白井 透
2022/03/29 白井 透
techinternlearning
【「BIツール」活用 超入門 Google Data Portalではじめるデータ集計・分析・可視化 第1章】現在長期インターンをさせてもらっているSinkCapitalさんの方で、データ系の業務に携わることになりそうなのですが、それの準備期間として紹介していただいた本をまとめていきたいと思います。
Ruby on Rails チュートリアル第14章をやってみて & まとめ
白井 透
2022/02/20 白井 透
techinternlearning
【Ruby on rails 第14章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第13章をやってみて
白井 透
2022/02/20 白井 透
techinternlearning
【Ruby on rails 第13章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第11章をやってみて
白井 透
2022/02/19 白井 透
techinternlearning
【Ruby on rails 第11章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第10章をやってみて
白井 透
2022/02/18 白井 透
techinternlearning
【Ruby on rails 第10章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第9章をやってみて
白井 透
2022/02/16 白井 透
techinternlearning
【Ruby on rails 第9章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第8章をやってみて
白井 透
2022/02/14 白井 透
techinternlearning
【Ruby on rails 第8章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第7章をやってみて
白井 透
2022/02/14 白井 透
techinternlearning
【Ruby on rails 第7章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第6章をやってみて
白井 透
2022/02/13 白井 透
techinternlearning
【Ruby on rails 第6章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第5章をやってみて
白井 透
2022/02/12 白井 透
techinternlearning
【Ruby on rails 第5章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第4章をやってみて
白井 透
2022/02/11 白井 透
techinternlearning
【Ruby on rails 第4章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第3章をやってみて
白井 透
2022/02/08 白井 透
techinternlearning
【Ruby on rails 第3章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第2章をやってみて
白井 透
2022/02/07 白井 透
techinternlearning
【Ruby on rails 第2章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Ruby on Rails チュートリアル第1章をやってみて & 自己紹介
白井 透
2022/02/07 白井 透
techinternlearningpersonal
【Ruby on rails 第1章】インターン先の方から、「これやっとけば、だいぶいい感じだよ!」と言われたので、Ruby on railsのチュートリアルをやってみたいと思います。
Nuxt上でのd3を利用した散布図の作成方法
櫻井 裕司
2021/10/29 櫻井 裕司
techdataAnalytics
クリック可能な散布図をNuxtjs上で作成する場合にd3.jsが汎用性が高く便利でした。利用するにあたって難しかった点などを備考録としてまとめています。
アクセスログを可視化しGAのデータを直感的に理解できる型態にする試み(ネットワーク型)
櫻井 裕司
2021/09/05 櫻井 裕司
techdataAnalytics
ビジネスに活きる分析を進める上で弊社では「理解できる」ことを重要と考えており、特に直感的理解は可視化を進める上で特に重要だと考える内容の一つです。弊社では様々なお客様のデータ分析を進める上で常により示唆の大きい可視化を追求しており、今回はその中で最近試みているネットワーク側の可視化についてまとめたいと思います。
代表櫻井による特別講演会が白陵高等学校で開かれました
櫻井 裕司
2021/07/31 櫻井 裕司
eventpersonal
2021年の夏に兵庫県の私立白陵高等学校において、代表櫻井による特別講演会を開催いたしました。今振り返って高校の頃の自分に伝えたいことについてお話しました。
Nuxtで動的ページを随時追加する場合にNot Foundとなる
櫻井 裕司
2021/05/31 櫻井 裕司
tech
Nuxtで動的ページを登録する方法はありますが、登録後に随時コンテンツが追加される際はNot Foundとなってしまうので、そう言った際の対処方法について
GKEをやめてCloud Runを始めてみました
櫻井 裕司
2021/04/19 櫻井 裕司
tech
firebaseで構築したシステムの裏で動かすバッチの負荷が大きく、cloud functionsで終わらなかったためCloud Runを利用してみました。動作確認までの知見等を雑多にまとめてみました。
AWSをやめてfirebaseを使い始めて感じたメリットやデメリットとそれの対応策(LT登壇内容)
櫻井 裕司
2021/03/26 櫻井 裕司
techeventpersonal
みそかつウェブ・GDG Nagoya主催の「around firebase」とCloud Native Nagoya主演の「Cloud Native Nagoya」にてfirebaseのLTをさせていただきました。そこで会話させていただいたfirebaseを使い始めて感じたメリット・デメリットについてまとめています。
PWA+SPAのwebアプリ作成にnuxtjs+firebaseがめちゃ便利だった
櫻井 裕司
2021/01/16 櫻井 裕司
tech
PWA+SPAのwebアプリを作る際にnuxt.js+firebaseを合わせて利用すると便利だったので知見を書き留めています
s3のhostingでPWAを導入する方法
櫻井 裕司
2020/12/19 櫻井 裕司
tech
アプリ作成時にpwaが比較されることが多かったですが、実際にpwaを実装した経験がなかったため今回自社サイトをPWA化してみました。
dockerでseleniumを動かしてみる(chrome_headless)
櫻井 裕司
2020/12/06 櫻井 裕司
tech
seleniumの相談をいただくことが増えたため、seleniumの勉強もかねてdockerでの実行テストを行いました
THE DECKのイベントにお邪魔させていただきました
本林 秀和
2020/12/05 本林 秀和
eventpersonal
大学コンソーシアム大阪のイベント@The DECK にお邪魔してきました
flutter(dart)を触ってみた感想
櫻井 裕司
2020/11/18 櫻井 裕司
tech
android向けアプリへの対応も考慮してflutter(dart)を触ってみたので、感想をまとめておこうと思います。理解が深まっていく中で定期的にまとめていければと思います。
代表本林による特別講演会が滝高校で開かれました
本林 秀和
2020/11/07 本林 秀和
eventpersonal
2020年11月7日(土)愛知県の私立滝高校において、代表本林による特別講演会を開催いたしました。IT業界やデータサイエンスについてお話しました。
AWS・GCPを選ぶ際の観点
櫻井 裕司
2020/10/28 櫻井 裕司
tech
AWSかGCPを選ぶ際の観点について書き留めておこうと思います
CloudFormationとterraformの比較
櫻井 裕司
2020/10/04 櫻井 裕司
tech
AWS CloudFormationとterraformの両方を使ってみて感じた違いをまとめてみました。
iosのcallkit周りでできること
櫻井 裕司
2020/08/24 櫻井 裕司
tech
新規事業を検討する上でios(swift)の電話周りでできることを調査したため、調査結果をブログとして残しています。
【個人ブログ】CTOの株運用ブログ_順調な滑り出し
櫻井 裕司
2020/07/19 櫻井 裕司
personalstock
長年放置してた株に少し手を出してみました。自分なりに少し情報整理と分析と予想をしたので記事にしてみます。
総務省特定サービス産業実態調査のデータ分析
櫻井 裕司
2020/07/18 櫻井 裕司
techdataAnalytics
総務省がAPIで市場データを公開しており、分析技術向上と市場感を養うことを目的に定期的に分析を行なっていこうと思います。今回は「特定サービス産業実態調査」について見ていこうと思います。
「お絵かきつみ木バトル」をリリースしました
櫻井 裕司
2020/07/12 櫻井 裕司
techapp
タスク管理を二次元的に行うアプリ「お絵かきつみ木バトル」をリリースしました。SinkCapitalはデータコンサルですが、知見蓄積のため様々な媒体での実験的開発を行っています
総務省工業統計調査のデータ分析
櫻井 裕司
2020/07/11 櫻井 裕司
techdataAnalytics
総務省がAPIで市場データを公開しており、分析技術向上と市場感を養うことを目的に定期的に分析を行なっていこうと思います。今回は「工業統計調査」について見ていこうと思います。
総務省サービス産業動向調査のデータ分析
櫻井 裕司
2020/07/08 櫻井 裕司
techdataAnalytics
総務省がAPIで市場データを公開しており、分析技術向上と市場感を養うことを目的に定期的に分析を行なっていこうと思います。初回は「サービス産業動向調査」について見ていこうと思います。
【個人ブログ】CTOが個人的に株をはじめました
櫻井 裕司
2020/07/08 櫻井 裕司
personalstock
長年放置してた株に少し手を出してみました。自分なりに少し情報整理と分析と予想をしたので記事にしてみます。
タスク管理アプリ「タスククロス」をリリースしました
櫻井 裕司
2020/04/08 櫻井 裕司
techapp
タスク管理を二次元的に行うアプリ「タスククロス」をリリースしました。SinkCapitalはデータコンサルですが、知見蓄積のため様々な媒体での実験的開発を行っています
【terraform】gcpでcicd環境を構築する方法
櫻井 裕司
2020/01/04 櫻井 裕司
tech
企業サイトはAWSを利用しているのですが、要件によってはGCPの方が適している場合もあるため、GCPでのcicd構築も行いました。AWSと比較しつつ説明しているため是非ご参考にしてみてください。
【合格体験記】GCP_Cloud_Archtectに受かりました
櫻井 裕司
2019/12/23 櫻井 裕司
personalqualification
Google Professional Cloud Architectに合格したので、勉強法別のコスパをまとめてみました。
AWSでサブドメインなし(wwwなし)からサブドメインあり(wwwあり)へのリダイレクト設定
櫻井 裕司
2019/12/23 櫻井 裕司
tech
もともと企業サイトがサブドメインありで公開していたが、サブドメインなしでもエラーなく接続できるように設計。terraformで作成しているので是非ご参考ください。
マークダウンで記事を書けるようにしてみた
櫻井 裕司
2019/12/16 櫻井 裕司
tech
ホームページのブログをマークダウンを使用してかけるようにしました。gatsbyなどもありますが、今回はお手製cicd+pythonを使用してライトに作成しました。
Copyright © SinkCapital 2022
一緒に働きたい方はこちら