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

【園木】料金シミュレーションAPIの開発 #37

Open
wants to merge 45 commits into
base: master
Choose a base branch
from

Conversation

ysk91
Copy link
Collaborator

@ysk91 ysk91 commented Apr 10, 2023

やったこと

  • API開発

    • rates.ymlにデータ作成
    • costs_controller.rbにindexアクションとcalculate_rateアクションを作成
      • indexアクションではrates.ymlの内容をjsonで返す
      • calculate_rateアクションでは入力値から料金計算の結果をjsonで返す
  • テスト設計

    • costs_controller.rbのリクエストspecを作成
  • UI開発

    • simulations_controller.rbにinputアクションとoutputアクションを作成
      • inputアクションでは契約アンペア数と使用量を入力
      • outputアクションでは入力値からapiを叩いた結果を表に出力
  • 本番環境構築

課題

  • UIのデザイン

@ysk91 ysk91 changed the title 【課題提出】料金シミュレーションAPIの開発 【園木】料金シミュレーションAPIの開発 Apr 11, 2023
@mochikichi
Copy link

@ysk91
プルリク提出ありがとうございます!
ひとまず対応お疲れさまでした。

内容はこれから確認させていただきますが、ざっと見たところ、料金計算ロジックに対するテストが記載されておりませんでしたので追加お願いします。
弊社ではテストも重要視しているためよろしくおねがいします。

@ysk91
Copy link
Collaborator Author

ysk91 commented Apr 11, 2023

@mochikichi

ご指摘いただきありがとうございます。
本件以下コミットにて対応いたしました。
c80a8db
ご確認をお願いします。

price: total_cost
}
end
render json: costs.to_json, status: 200

Choose a reason for hiding this comment

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

間違っていたらすみません。to_jsonって必要ですか。

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

costs自体が既にハッシュなので不要でした。
修正しました。
ysk91@7913bdf#diff-bebb16eb4b12378fcb91ff5afb5e43b378eb34c711f2a332446da3b0f13f8619R49

private

def yaml_path
Rails.root.join('config', 'rates.yml')

Choose a reason for hiding this comment

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

リクエスト来るたびに読み込むのは遅くなる原因になりますね

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yaml_pathをコントローラ内でメモ化しました。
ysk91@7913bdf#diff-bebb16eb4b12378fcb91ff5afb5e43b378eb34c711f2a332446da3b0f13f8619R56

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yaml_data自体をメモ化しました。
9286a10#diff-bebb16eb4b12378fcb91ff5afb5e43b378eb34c711f2a332446da3b0f13f8619R54

今はcosts_controllerの中で定義していますが、進める中で最適な場所を考えてみます。

usage = params[:usage]

if usage == "0"
render "input"

Choose a reason for hiding this comment

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

好みですが

Suggested change
render "input"
render :input

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

end
data = JSON.parse(response.body)
@results = data
render "output"

Choose a reason for hiding this comment

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

render "output" っていりますか?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

<%= form_with url: output_simulations_path, method: :get, local: true do |f| %>
<div>
<%= f.label :contract_ampere, '契約アンペア数' %>
<%= f.select :contract_ampere, [10, 15, 20, 30, 40, 50, 60], required: true %>A

Choose a reason for hiding this comment

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

[10, 15, 20, 30, 40, 50, 60]をviewにベタ書きするのは微妙ですね。
いろんなところにベタ書きが増えていき、メンテナンスが大変になりそうなので何処かにまとめておきたいです。

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

コントローラ内に変数として定義しました。
29d409f#diff-feb71b51f115d4dc122999f84a58fffda21b371b56e43d2cc617f3c84d49cd9aR31

SWVDjQKBgQCpqFddnSmatf6oQ/4u1DtGOsE53aRnhxyGr66LwdThD30af4r+6d92
7kZEDXcm3flAjzkpWNd2ifxOkxL8IPP88Twdhy9L/z2wK8Kg5iK4rB2MXwgwuYJV
2VpWWL4BOd2syhXG98zoU2tTASYMGeOSJVgnw/O8VjSqIcuHqYecBw==
-----END RSA PRIVATE KEY-----

Choose a reason for hiding this comment

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

これってなんの値でしょうか。
security的に、PRにあげていいものでしょうか。

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

AWSにアップしようとした時に作成したkeypairです。
gitignoreする前にcommitしたものが残っていました。
今はRenderにアップしているのでこのファイルは不要です。
削除しました。
8897705

usage = params[:usage].to_i
rates = YAML.load_file(yaml_path)

if contract_ampere.zero? || usage.zero?

Choose a reason for hiding this comment

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

リクエスト
契約アンペア数 : 10 / 15 / 20 / 30 / 40 / 50 / 60 のいずれかとする。(単位A)

上記の仕様があるので、フロント側制御のみではなく、サーバーサイド(API側)でバリデーションを入れたほうが良いですね

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

突貫対応ですが、表示するプランが無い場合にinputに戻すようにしました。
ただ、

  • 本質的な解決ではない
  • if節が増えてあまり美しくない

ので、より改善が必要だと感じています。。
29d409f#diff-feb71b51f115d4dc122999f84a58fffda21b371b56e43d2cc617f3c84d49cd9aR23

50: 1430.00
60: 1716.00
usage_rates:
- range: [1, 120]

Choose a reason for hiding this comment

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

データベースに保存するとしたらどのような構造にするのが良いでしょうか?

Copy link
Collaborator Author

@ysk91 ysk91 Apr 20, 2023

Choose a reason for hiding this comment

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

Company has_many :plans という親子関係を作ります。

Companyには最低限以下のカラムを用意します。

  • id
  • name

Planには以下のカラムを用意します。

  • id
  • company_id
  • name
  • basic_rates
  • usage_rates

PostgreSQLを使用している場合

マイグレーションファイルを以下のように設定することでbasic_ratesにJSONデータを、usage_ratesに配列データを格納することができます。

t.json    :basic_rates
t.string :usage_rates, array: true, default: []

MySQLやSQLiteを使用している場合

マイグレーションファイルでテキスト型のデータを作成します。

t.text :basic_rates
t.text :usage_rates

その後、モデルファイルでシリアライズします。

serialize :basic_rates,   JSON
serialize :usage_rates,  Array

一例として、東京電力エナジーパートナーの従量電灯Bプランの場合、
basic_rates = {
10: 286.00,
15: 429.00,
20: 572.00,
30: 858.00,
40: 1144.00,
50: 1430.00,
60: 1716.00,
}

usage_rates = [
{range: [1, 120], rate: 19.88},
{range: [121, 300], rate: 26.48},
{range: [301, "inf"], rate: 30.57}
]
というデータを格納します。
コントローラファイルでは現在と同様の計算メソッドが使用できます。

参考

https://dev.classmethod.jp/articles/postgresql-14-supports-json-subscripting/

https://qiita.com/yodog712/items/e3098eef8b494ab62fbd

Choose a reason for hiding this comment

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

ありがとうございます!
カラムに配列型を使うメリット・デメリットを調べてみると勉強になると思います

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copy link

Choose a reason for hiding this comment

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

node_modules は git 管理したほうがいいでしょうか?


if contract_ampere.zero? || usage.zero?
render json: { error: 'Invalid input: contract_ampere and usage are required' }, status: 400
else
Copy link

Choose a reason for hiding this comment

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

else ブロックがメソッドの大部分を占めてネストが深くなってますが、
ネストを浅く読みやすくする方法はないでしょうか?

@keishi1129
Copy link

keishi1129 commented Apr 25, 2023

@ysk91
迅速な修正ありがとうございます!モデル化もできたらしてみてください。プログラムが書きやすくなるはずですので数段レベルアップすると思います。連絡いただければ私レビューしますので!

ysk91 and others added 3 commits May 9, 2023 12:58
計算ロジックをCostモデルに持たせた
レビューコメントを追加
@ysk91
Copy link
Collaborator Author

ysk91 commented May 9, 2023

@ysk91 迅速な修正ありがとうございます!モデル化もできたらしてみてください。プログラムが書きやすくなるはずですので数段レベルアップすると思います。連絡いただければ私レビューしますので!

@keishi1129

コミット内容

学習メモ

Planモデルについて

当初、PlanモデルとPlansLoaderモジュールが同じように思えてyamlデータをモデル化するメリットが分かりませんでした。
しかし、以下のように役割を分けることで、メリットがあることが分かりました。

  • 役割分担

    • PlansLoaderモジュール: yamlデータを呼び出す(メモ化含む)
    • Planモデル: 呼び出したyamlデータに対する処理をメソッドとして定義する
  • メリット

    • Planモデル内でyamlデータの操作をメソッド化することで、他のモデルやコントローラからの呼び出しが容易になる
      • 今は全てのプランを呼び出すだけだが、プランにidを付与してPlanモデル内でfind_planメソッドのようなものを定義すれば特定のプランだけ計算することができる

Queryモデルについて

Costモデル内のバリデーション処理をQueryモデルに持たせました。
これにより、Costモデルを計算ロジックに専念させることができました。
また、バリデーションテストをquery_specにまとめることでcost_specがスッキリしたように感じます。

質問

@mochikichi

現在、api_requests_spec.rbにて計算結果が正しいことをテストしていますが、プランが変更した場合などに書き換える必要があり、DRYでないように感じます。
以前面接内で「検算シートは不要」とおっしゃっていましたが、その方法が分かりません。
何かヒントになるキーワードを教えて頂けますでしょうか?

@keishi1129
Copy link

keishi1129 commented May 10, 2023

@ysk91
早速モデル化に挑戦していただきありがとうございます。PlanモデルとPlansLoaderモジュールを分けるメリットですがおっしゃる通りかと思います。またQueryモデルの導入も素晴らしいと思います。実際業務で使われているシミュレーションAPIも同様なモデルが存在しております。

また以下の質問にも答えてしまいますと、プランが変更したときにテストを書き換える必要が出てくることこそテストの意味があるのです。とある変更して、期待通りテストが落ちてしまうことで、それまでテストが機能していたことの証明になるためです。

現在、api_requests_spec.rbにて計算結果が正しいことをテストしていますが、プランが変更した場合などに書き換える必要があり、DRYでないように感じます。

常々思いますが、疑問に思う点やそれを自己で解決されようとしている点が非常に素晴らしいなと思います。入社後もどんどん吸収していってください!

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.

5 participants