Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

招待URL作成時に研修生を支払い方法別に選択可能にした・研修生の場合は会員登録時の支払い方法の指定を必須にした #8013

Open
wants to merge 39 commits into
base: main
Choose a base branch
from

Conversation

ham-cap
Copy link
Contributor

@ham-cap ham-cap commented Aug 15, 2024

Issue

概要

現在、管理者用メニューにはアドバイザー、研修生、メンター用の招待URL作成ページがあり、所属企業、ロール(FBC内での会員種別)、コース(FBCで受講するコース)の三つを選択することでそれぞれに合った内容の会員登録ページへ飛ぶURLを作成することができます。
このPRでは、これまで一つだった研修生のロールを支払い方法別に三つに分割し、それぞれの支払い方法があらかじめ選択された会員登録ページを表示するように変更しました。
具体的には、管理者だけが閲覧・変更できる項目の「請求書払いの設定」欄を廃止し、研修生の会員登録ページにのみ表示される「支払い方法」欄を新たに設置して「請求書払い」と「クレジットカード払い」をチェックボックスで表示するようにしました。
招待URL作成時に選択したロールによってそのチェックボックスの初期状態や操作の可否を適切に指定して表示されるようになっています。

変更確認方法

招待URL作成ページ

  1. feature/require-payment-method-selection-for-trainees-when-creating-invitation-urlをローカルに取り込む
  2. admin権限のあるユーザー(komagatamachida)でログインする
  3. http://localhost:3000/admin/invitation_urlにアクセスし、ロールのプルダウンに「アドバイザー」、「研修生(請求書払い)」、「研修生(クレジットカード払い)」、「研修生(支払い方法を選択)」、「メンター」の5つの選択肢が表示されていることを確認する。

研修生(請求書払い)の会員登録ページ

  1. 招待URL作成ページ(/admin/invitation_url)で「研修生(請求書払い)」のロールを選択して作成したURLへアクセスする。
  2. 「支払い方法」の欄で
    • 「請求書払い」にチェックがあること
    • 「クレジットカード払い」が空欄であること
    • それらのチェック状態を変更できない(請求書払いのチェックを外したりクレジットカード払いにチェックしたりできない)こと
      を確認する。
  3. その他の必須項目を入力し、「参加する」をクリックしてユーザー登録ができることを確認する。

研修生(クレジットカード払い)の会員登録ページ

  1. 招待URL作成ページ(/admin/invitation_url)で「研修生(クレジットカード払い)」のロールを選択して作成したURLへアクセスする。
  2. 「支払い方法」の欄で
    • 「クレジットカード払い」にチェックがあること
    • カード番号の入力欄が表示されていること
    • 「請求書払い」が空欄であること
    • それらのチェック状態を変更できない(クレジットカード払いのチェックを外したり請求書払いにチェックしたりできない)こと
      を確認する。
  3. その他の必須項目を入力し、カード番号の入力欄が空欄の場合は「参加する」をクリックしてもフォームの送信ができないことを確認する。
  4. カード番号の入力欄に開発者用のダミーのカード番号「4242 4242 4242 4242」、有効期限「12 / 50」、セキュリティナンバー「111」を入力のうえ、「参加する」をクリックしてユーザー登録ができることを確認する。

研修生(支払い方法を選択)の会員登録ページ

  1. 招待URL作成ページ(/admin/invitation_url)で「研修生(支払い方法を選択)」のロールを選択して作成したURLへアクセスする。
  2. 「支払い方法」の欄で、
    • 初期状態では「請求書払い」及び「クレジットカード払い」のチェックがいずれも空欄であること
    • 「請求書払い」のチェックを付けたり外したりできること
    • 「クレジットカード払い」のチェックを付け外しでき、チェックがある場合だけカード番号入力欄が表示されること
    • 「請求書払い」と「クレジットカード払い」のいずれか一方にチェックがある場合はもう一方のチェックが自動で外れること
      を確認する。
  3. いずれの支払い方法にもチェックがない場合や、カード番号欄が空欄の場合はフォームの送信ができないことを確認する。
  4. 「請求書払い」、「クレジットカード払い」それぞれにおいて上記のやり方と同様に必要事項等を入力のうえ、いずれも正常にユーザー登録ができることを確認する。

Screenshot

招待URL作成ページ

変更前

image

変更後

image

会員登録ページ(請求書払い)

image

会員登録ページ(クレジットカード払い)

image

会員登録ページ(支払い方法を選択)

Image from Gyazo

@ham-cap ham-cap self-assigned this Aug 15, 2024
@ham-cap ham-cap marked this pull request as ready for review August 15, 2024 06:02
@ham-cap ham-cap requested a review from mousu-a August 15, 2024 06:10
@ham-cap
Copy link
Contributor Author

ham-cap commented Aug 15, 2024

@mousu-a
お疲れ様です!
こちらのPRについてレビューをお願いできますでしょうか🙏
急ぎではありませんのでお手隙の際で結構です🙇‍♂️
よろしくお願いいたします!

@mousu-a
Copy link
Contributor

mousu-a commented Aug 15, 2024

@ham-cap
レビュー依頼ありがとうございます〜!
正直レビューにどれくらい掛かるかわかりませんがぜひ引き受けさせていただきます〜😄

わからないところがあれば質問させていただきますのでその時はよろしくお願いいたします🙇‍♂️

Copy link
Contributor

@mousu-a mousu-a left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ham-cap

お疲れ様です!
レビューお待たせいたしました。
コミット時の説明が多く助かりました😊
見やすい様にコミット履歴が整理されているともっと良いかもです!(不要なコミット履歴をrebaseして省くなど)
(レビュアー視点でコミット履歴を作るのが良いのかなと思います)

いくつか気になる点をコメントさせていただいていますのでご確認ください🙇‍♂️
的外れなことを言っていたら申し訳ないですが適宜コメント頂けたらと思います〜🙏

@@ -27,7 +27,9 @@ class User < ApplicationRecord

INVITATION_ROLES = [
[I18n.t('invitation_role.adviser'), :adviser],
[I18n.t('invitation_role.trainee'), :trainee],
[I18n.t('invitation_role.trainee(invoice_payment)'), :trainee_invoice_payment],
Copy link
Contributor

@mousu-a mousu-a Aug 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

提案です!
I18n.t('invitation_role.trainee(invoice_payment)')このコードに関してです〜。

# ja.yml
invitation_role:
 trainee: 研修生(${payment_method})

# app/models/user.rb
I18n.t('invitation_role.trainee', payment_method: 請求書払い)

みたいな形でも良いかもしれません。判断はお任せします🙇‍♂️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらご提案に従って修正してみました!🙏

checkBoxes.forEach((checkBox) => {
if (checkBox) {
checkBox.addEventListener('click', () => {
if (!checkBox.checked) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

細かいですが1行で書けそうです!
if (!checkBox.checked) checkBox.checked = true

'.selectable-credit-card-box'
)
if (selectableCreditCardCheckBox.checked) {
stripe.createToken(card).then(function (result) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

前の実装にならって、ということなのだと思いますが、特別な理由がなければファイル内でfunctionかアローどちらかに統一したいかもです!(今回変更を加えたコードだけでも)
(今は特別functionを使う理由もなさそうです)

@@ -68,6 +68,7 @@ def create
@user.free = true if @user.trainee?
@user.build_discord_profile
@user.payment_method_of_trainee = params[:user][:payment_method_of_trainee]
@user.credit_card_payment = params[:credit_card_payment] == '1'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません、このコードが何をやっているのかちょっと説明ほしいです🙏(72行目、73行目)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

72行目の@user.payment_method_of_trainee = params[:user][:payment_method_of_trainee]については、試行錯誤の痕跡が残ってしまっているだけですので、Userモデルのattr_accessorと併せて削除いたします🙏

73行目についてはcheck_box_tagにチェックが入っていた場合に送られてくる値1を真偽値に変換したうえで代入しているのですが、改めて確認したところ特に真偽値に変換せずとも動作には問題がないようなので、== '1'の部分を削除させていただきます。
@user.credit_card_payment自体はUserモデルの中でバリデーションをかける際の条件に使用しています。


test 'GET /users/new as an adviser' do
visit '/users/new?role=adviser'
assert_equal 'FBCアドバイザー参加登録 | FJORD BOOT CAMP(フィヨルドブートキャンプ)', title
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

システムテストは重たいテストなので必要最小限に絞りたいかもです!
GET /users/newのテストだけで良さそうに思います)

(今回追加した3つのシステムテスト(FBC参加登録以外のテスト)が今までなかったのは必要ないからかもしれません)

Copy link
Contributor Author

@ham-cap ham-cap Aug 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

今回のissueで求められている要件に沿ってタイトル表記のバリエーションを4種類に増やしているので、アドバイザー・研修生・メンター・一般受講生それぞれの登録フォーム上で正しく表示されているかどうかの確認は必要だと思うのですがいかがでしょうか👀
むしろ今までもアドバイザーとそれ以外の場合で2種類存在していたタイトル表記を片方しかチェックしていなかったのが不備だったような気もしています🤔

Copy link
Contributor

@mousu-a mousu-a Aug 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

むしろ今までもアドバイザーとそれ以外の場合で2種類存在していたタイトル表記を片方しかチェックしていなかったのが不備だったような気もしています🤔

そう言われればそうな気がしてきましたね🤔
一旦判断はkomagataさんにお任せしたいと思います!

@@ -0,0 +1,54 @@
function keepCheckBoxChecked(checkBoxes) {
checkBoxes.forEach((checkBox) => {
if (checkBox) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkBoxが存在しない場合はそもそもcheckBoxがクリックされることはないのでこのif文は必要ないかもです!
その場合checkBox?.addEventListenerとする必要があるかもしれません🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

引数checkBoxesの要素二つのうち少なくとも一つは必ずnullになるためif文で存在確認をしているのですが、たしかにオプショナルチェーンを使用すればnullが含まれていることを許容できるようになるのでそのように修正いたしました🙌

'.selectable-credit-card-box'
)
const checkBoxes = [selectableInvoiceCheckBox, selectableCreditCardCheckBox]
if (checkBoxes.includes(null)) {
Copy link
Contributor

@mousu-a mousu-a Aug 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このif文も1行で書けそうです!

@@ -35,8 +45,18 @@ document.addEventListener('DOMContentLoaded', () => {
// Create an instance of the card Element.
const card = elements.create('card', { style: style, hidePostalCode: true })

// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element')
if (!userRole || checkedCreditCardCheckBox) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません、このコードもちょっと説明ほしいです🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらはStripeのカード番号入力欄のマウントを、/users/newにパラメーターなしでアクセスした場合(!userRoleがtrue)とページがレンダリングされた段階でクレジットカード払いにチェックが入っている場合(checkedCreditCardCheckBoxがtrue)に限定するためのものです。
マウントするタイミングを限定せず、単純に#card-elementにマウントするということでも機能としては動作するのですが、それだと招待URL作成ページの段階で請求書払いを選択しているとき等、#card-elementの要素が見当たらない場合にブラウザのコンソールにエラーが出てしまいます。
これを回避するために場合分けをしてマウントすべきタイミングを明示しています。
なお、チェックボックスがselectableな場合はチェックの状態によって動的にマウントする必要があるため、この直下でselectableCreditCardCheckBoxに対してイベントリスナーを登録することで対応しています。
また、この部分のselectableCreditCardCheckBoxの存在確認のためのif文については別途ご指摘いただいているとおりオプショナルチェーンで実装するのがベターだと思いますのでそのように修正させていただきます🙏

card.mount('#card-element')
}

if (selectableCreditCardCheckBox) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectableCreditCardCheckBox?.addEventListenerとすればこのif文は必要ないかもです!

if (checkBoxes.includes(null)) {
return
}
const cardForm = document.getElementById('card')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらの変数名CreditCardFormではどうでしょうか。
cardFormではわかりにくいかもです🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

たしかにちょっと分かりにくいですね👀
修正しました!

・これまで管理者用の項目だった「請求書払いの設定」のUIを削除し、支払い方法(請求書orクレジットカード)を選択するためのチェックボックスを一般ユーザー向けの項目として新たに作成した。併せて、登録フォームに遷移する前の招待URL作成ページのロール欄でどれを選択したかによって、登録フォームが表示された初期状態でどちらの支払い方法にチェックが入っているか(または入っていないか)を振り分けるように修正した。
…装・チェックボックスのパーシャルの名称を変更

・コントローラーで研修生の支払い方法に関する情報を@pay_by_invoiceと@pay_by_credit_cardに格納していたが、params[:role]の参照で事足りるため、これら二つのインスタンス変数は廃止した。

・「研修生(支払い方法を選択)」を選んだ場合は参加登録フォーム上でチェックボックスのチェックを変更できることから、カード番号入力フォームの表示/非表示を切り替えができる必要があったため実装した。

・チェックボックスのパーシャルは既存のUIを流用したため名称が_invoice_paymentのままになっていたが、実態に即していないため変更した。
…wアクション内の条件分岐の書き方を変更

    ・招待URL作成ページで「研修生(請求書払い)」を選択した場合は登録フォームの「請求書払い」のチェックボックスはtrueでなくてはならないため、そ
のバリデーションを追加した。

    ・ビューでparamsを直接呼び出すとURLに含まれるパラメーターに変更があった場合の対応が煩雑になる恐れがあるため、ビューで必要な情報はコントロー
ラーでインスタンス変数に代入して使用することとした。

    ・UsersControllerのnewアクションではparams[:role]の値によって分岐する処理をif文で記述していたが、case文で記述した方が可読性が高いと判断した
ため変更した。
・クレジットカードのチェックボックスのDOM要素を取得する際getElementsByNameを使用していたが、対象の要素が見つからない場合でも空のNodeListが返ってしまい、条件分岐が正しく動作しないため、要素が見つからない場合にnullを返すquerySelectorに置き換えた。
・招待URL作成ページで選択された支払い方法のチェックボックスは登録フォームが表示された時点でチェック済みになっているが、これまではそのチェックを外すことができてしまっていた。そのため、誤操作でチェックを外してしまい無駄にバリデーションエラーになる可能性も考えられたため、ユーザーがチェックを外せないようにした。
・バリデーションで引っかかった場合はrenderで再度newテンプレートが呼び出されるが、招待URL作成ページからの遷移でない場合は必要なパラメータがURLに含まれておらず、チェックボックスが操作不能になる不具合が出ていたため修正した。
・招待URL作成ページにて「研修生(支払い方法を選択)」が選択された場合、登録ページのチェックボックスの挙動が、
  (1)「請求書払い」と「クレジットカード払い」のいずれか一方しかチェックできない、
  (2)「クレジットカード払い」が選択されている時のみカード情報の入力フォームを表示する、
となるように実装した。
それに伴い、カード情報の入力フォームの表示/非表示の制御と2種類のチェックボックスの状態制御を同時に行う必要があったため、card-form-display-switcher.jsで行っていた処理をpayment-methods-check-boxes.jsに統合した。
・DOMツリーにクレジットカード情報の入力フォームが存在すると、カード情報が空欄の場合や有効な番号でない場合にフォーム全体の送信を止める機能が実装されているが、招待URL作成ページで「研修生(支払い方法を選択)」を選んだ場合、クレジットカードフォームが空欄の状態であってもフォームが送信されなければならない(請求書払いを選択している)状況があり得ることになるため、「クレジットカード払い」のチェックボックスにチェックがない場合には当該バリデーションを無効化するように変更した。
・「研修生(クレジットカード払い)」を選択した場合の登録フォームでStripeのカード番号の入力欄が正しくマウントされていなかったため修正。
・users#newアクションの中でuser_paramsを呼び出してしまっていたことで、/users/newなどのパラメーターを持たないか不十分なURLではParameterMissingエラーが発生し、アクセスできない状況となっていたため修正した。
併せて、変数@ROLEは廃止し、@userのパラメーターとして扱うことでパーシャルに渡す変数の削減も行った。
・支払い方法を選択できる場合の登録フォームにあるselectableCreditCardCheckBoxにチェックが入っているかどうかがフォームをそのまま送信するかクレジットカードのバリデーションを通すかの条件となっており、請求書払いが確定している登録フォーム上では存在しない要素のプロパティを呼び出すことになってしまいエラーとなっていたため修正した。
・クレジットカードのバリデーションを作動させる条件にcheckedCreditCardCheckBoxが含まれておらず正常に動作していなかったため修正した。
・メンター用の登録フォームの場合は「FBCメンター参加登録」と表示されていなければならないところ、一般受講生と同じ「FBC参加登録」となってしまっていたため修正した。
・クレジットカード情報の入力を伴う他のテストに倣ってVCRでログをとるように修正した。
・引数checkBoxesの要素である二つのチェックボックスのうち少なくとも一つは必ずnullが渡ってくるためif文で存在確認をしていたが、オプショナルチェーン演算子(?.)を使用するほうがより簡潔に書けるため変更した。
@ham-cap ham-cap force-pushed the feature/require-payment-method-selection-for-trainees-when-creating-invitation-url branch from 283312e to 7c76d88 Compare August 21, 2024 08:43
@ham-cap
Copy link
Contributor Author

ham-cap commented Aug 21, 2024

@mousu-a
ご指摘いただいた点について修正等いたしましたのでご確認くださいー🙏

Copy link
Contributor

@mousu-a mousu-a left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ham-cap
お疲れ様です!
変更ありがとうございます🙇‍♂️コードとても良くなったと思います〜!

最後に気になるところを一点だけ発見いたしましたのでご確認をお願いいたします🙏

@@ -181,6 +185,10 @@ class User < ApplicationRecord
validates :hide_mentor_profile, inclusion: { in: [true, false] }
validates :github_id, uniqueness: true, allow_nil: true
validates :other_editor, presence: true, if: -> { editor == 'other_editor' }
validates :invoice_payment, inclusion: { in: [true], message: 'にチェックを入れてください。' }, if: -> { role == 'trainee_invoice_payment' }
Copy link
Contributor

@mousu-a mousu-a Aug 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません、このコードなのですが、

if role == 'trainee_invoice_payment' {
validation invoice_paymentはtrueでなければならない
  失敗したらmessageを表示
}

ということをやっているのだと思いますが、「trueかどうか」だけを検証するためにinclusionを使うのは意図に合っていない気がします🤔

自分としては下記の様な形を考えています。

  validate :custom_validation, if: -> { role == 'trainee_invoice_payment' }

  private

  def custom_validation
    return invoice_payment == true

    errors.add(:base, "エラーメッセージ")
  end

ただその場合invoice_paymentの値をどうやって取得するのか、errorsにaddする形でメッセージを表示できるのか、などの問題は出てきそうです🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mousu-a

こちらの件、個人的にはここでinclusionを使うことにそこまで違和感を覚えていないので、意図に合っていないと思われる理由についてもう少し詳しく教えていただけると嬉しいです🙏

自分なりの考えを先に書いておくと、今回のケースはカスタムバリデーションを使わなければならないほど複雑な処理を必要とするものではないと考えていて、むしろバリデーションの実行箇所とメソッドの定義場所が離れてしまうことによる可読性の低下のほうがデメリットとして大きく感じます。

また、inclusionの使い方について改めて確認してみました。
Railsガイドの説明によると

このヘルパーは、指定の集合に属性の値が含まれているかどうかを検証します。

とあるので、今回の「trueであることを確認する」というのもユースケースとしてそれほど逸脱したものではないと個人的には思います。
加えて、真偽値の存在チェックのためにはinclusion: [true, false]を使用すべきであることがpresenceオプションの項目で紹介されていることなども勘案すると、「真偽値がtrueであることを確認する」という目的のためにinオプションに[true]だけを渡して厳密に判定するという方法は、inclusionの標準的な使い方の範疇にあるように感じます。

あくまでドキュメントを参照した上での自分なりの解釈なので、見落としている点などがあれば教えてもらえると嬉しいです👀

Copy link
Contributor

@mousu-a mousu-a Sep 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ham-cap
返信ありがとうございます!
すみません、値が集合の中に含まれていることを確認するのにその集合がtrue1つだけ、というところに違和感を持ってしまった次第です。(2つ以上の要素を持つ配列を集合に指定するイメージを持っていました)

カスタムバリデーションを使わなければならないほど複雑な処理を必要とするものではない

理解出来ましたのでこれでApproveとさせていただきます〜!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mousu-a

2つ以上の要素を持つ配列を集合に指定するイメージを持っていました

なるほどです👀
たしかに許容できる値の配列を渡すのが1番スタンダードなやり方ではありますね。

Copy link
Contributor

@mousu-a mousu-a left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

変更ありがとうございました😄
こちらからはApproveとさせていただきます〜!

@ham-cap
Copy link
Contributor Author

ham-cap commented Sep 1, 2024

@mousu-a
丁寧に見ていただきありがとうございました🙏
大変勉強になりました😃

@komagata
メンバーからApproveしていただきましたのでレビューをお願いいたします🙇‍♂️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants