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

【serverside_challenge_2 : sasaki】プランごとの電気料金を返すAPIの実装 #59

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a22158e
プランごとの電気料金を返すAPIを実装する
tkm0on May 10, 2024
fc49369
電力会社の料金プランの情報をymlファイルにまとめた
tkm0on May 11, 2024
5a2ba3b
Rspecの導入とAPIのミニマムなテストを実装
tkm0on May 11, 2024
8e0bdbb
Rspecのテストが通るように本体の実装をした
tkm0on May 11, 2024
bd7226e
料金プランの計算処理に関する実装をした
tkm0on May 13, 2024
345d841
rubocopの導入と指摘箇所の修正をした
tkm0on May 14, 2024
f25b7e4
ElectricityRateSimulationモデルのSpecを作成した
tkm0on May 14, 2024
7e9c825
Provider, ElectricityPlan, BasicRate, UsageRateのモデルを作成した
tkm0on May 15, 2024
c5c6401
seed処理の実装をした
tkm0on May 15, 2024
8c5258a
DBでデータを管理したことに伴う実装の修正とテストコードの作成
tkm0on May 17, 2024
bc981cd
YAMLで管理していたデータ部分を削除した
tkm0on May 17, 2024
a063add
ActiveModelクラスでnumericalityバリデーションが動作するように修正した
tkm0on May 17, 2024
4cf9fd4
DBテーブルの制約を修正した
tkm0on May 17, 2024
3cdf916
rubocop_todoの対応をした
tkm0on May 18, 2024
88cf20d
フロントエンド用にDocker環境とNextjsを準備した
tkm0on May 18, 2024
45e8b05
入力フォーム処理と画面表示ができるとこまで実装した
tkm0on May 21, 2024
f633fda
chakra-uiを使ってデザインを整えた
tkm0on May 21, 2024
c664e2a
frontendのREADMEを修正した
tkm0on May 21, 2024
4970a87
バックエンド処理のリファクタリングをした
tkm0on May 21, 2024
bfecff0
paramsメソッドの呼び出し回数を減らした
tkm0on May 26, 2024
511703a
バリデーションメッセージの日本語化をした
tkm0on May 26, 2024
90bc889
ElectricityPlanインスタンスをSimulationResultのコンストラクタとして渡すことにした
tkm0on May 26, 2024
3956ec8
使用量が入力されたときのバリデーションルールをフロントエンド側に追加した
tkm0on May 27, 2024
58a4e83
simulationResultをmapしたときのresultの型定義をした
tkm0on May 27, 2024
6476a78
従量料金計算メソッドの計算終了フラグを削除した
tkm0on May 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions serverside_challenge_2/challenge/.rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require spec_helper
27 changes: 27 additions & 0 deletions serverside_challenge_2/challenge/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
inherit_from: .rubocop_todo.yml

require: rubocop-rails # 必須

AllCops:
# 除外するディレクトリ(自動生成されるファイル)
Exclude:
- 'db/**/*'
- 'config/**/*'
- 'bin/**/*'
- 'test/**/*'
- 'spec/rails_helper.rb'
- 'spec/spec_helper.rb'
- 'Gemfile'
- 'Rakefile'
NewCops: enable

Style/Documentation:
Enabled: false

Lint/EmptyBlock:
Exclude:
- 'spec/factories/**/*'

Metrics/BlockLength:
Exclude:
- 'spec/**/*'
26 changes: 26 additions & 0 deletions serverside_challenge_2/challenge/.rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 999999`
# on 2024-05-17 06:56:05 UTC using RuboCop version 1.63.5.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 4
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/UniqueValidationWithoutIndex:
Exclude:
- 'app/models/basic_rate.rb'
- 'app/models/electricity_plan.rb'
- 'app/models/provider.rb'
- 'app/models/usage_rate.rb'

# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiterals:
Exclude:
# - 'app/mailers/application_mailer.rb'
# - 'config.ru'
10 changes: 9 additions & 1 deletion serverside_challenge_2/challenge/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,23 @@ gem "bootsnap", require: false
# gem "image_processing", "~> 1.2"

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem "rack-cors"
gem 'rack-cors'

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri mingw x64_mingw ]
gem 'rspec-rails'
gem 'factory_bot_rails'
end

group :development do
# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
# gem "spring"
gem 'rubocop', require: false
gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false
end

group :test do
gem 'shoulda-matchers'
end
67 changes: 67 additions & 0 deletions serverside_challenge_2/challenge/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ GEM
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
ast (2.4.2)
bootsnap (1.18.3)
msgpack (~> 1.2)
builder (3.2.4)
Expand All @@ -75,7 +76,13 @@ GEM
debug (1.9.1)
irb (~> 1.10)
reline (>= 0.3.8)
diff-lcs (1.5.1)
erubi (1.12.0)
factory_bot (6.4.6)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.3)
factory_bot (~> 6.4)
railties (>= 5.0.0)
globalid (1.2.1)
activesupport (>= 6.1)
i18n (1.14.1)
Expand All @@ -84,6 +91,8 @@ GEM
irb (1.11.2)
rdoc
reline (>= 0.4.2)
json (2.7.2)
language_server-protocol (3.17.0.3)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
Expand Down Expand Up @@ -111,13 +120,19 @@ GEM
racc (~> 1.4)
nokogiri (1.16.2-x86_64-linux)
racc (~> 1.4)
parallel (1.24.0)
parser (3.3.1.0)
ast (~> 2.4.1)
racc
pg (1.5.4)
psych (5.1.2)
stringio
puma (5.6.8)
nio4r (~> 2.0)
racc (1.7.3)
rack (2.2.8)
rack-cors (2.0.2)
rack (>= 2.0.0)
rack-test (2.1.0)
rack (>= 1.3)
rails (7.0.8)
Expand Down Expand Up @@ -148,16 +163,61 @@ GEM
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
rainbow (3.1.1)
rake (13.1.0)
rdoc (6.6.2)
psych (>= 4.0.0)
regexp_parser (2.9.1)
reline (0.4.2)
io-console (~> 0.5)
rexml (3.2.6)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (6.1.2)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-core (~> 3.13)
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.1)
rubocop (1.63.5)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3)
parser (>= 3.3.1.0)
rubocop-performance (1.21.0)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.24.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (1.13.0)
shoulda-matchers (6.2.0)
activesupport (>= 5.2.0)
stringio (3.1.0)
thor (1.3.0)
timeout (0.4.1)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
Expand All @@ -170,9 +230,16 @@ PLATFORMS
DEPENDENCIES
bootsnap
debug
factory_bot_rails
pg (~> 1.1)
puma (~> 5.0)
rack-cors
rails (~> 7.0.8)
rspec-rails
rubocop
rubocop-performance
rubocop-rails
shoulda-matchers
tzinfo-data

RUBY VERSION
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module ApplicationCable
class Channel < ActionCable::Channel::Base
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module ApplicationCable
class Connection < ActionCable::Connection::Base
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module Api
class ElectricityRateSimulationsController < ApplicationController
def index
params = electricity_rate_simulation_params
electricity_rate_simulation = ElectricityRateSimulation.new(params['amperage'], params['usage_kwh'])

raise ActiveModel::ValidationError, electricity_rate_simulation if electricity_rate_simulation.invalid?

simulation_result = electricity_rate_simulation.execute

render json: simulation_result
end

private

def electricity_rate_simulation_params
params.require(%i[amperage usage_kwh])
params.permit(:amperage, :usage_kwh)
end
end
end
Original file line number Diff line number Diff line change
@@ -1,2 +1,16 @@
# frozen_string_literal: true

class ApplicationController < ActionController::API
rescue_from ActionController::ParameterMissing, with: :parameter_missing
rescue_from ActiveModel::ValidationError, with: :validation_error

private

def parameter_missing(error)
render json: { error: error.message }, status: :bad_request
end

def validation_error(error)
render json: { error: error.model.errors.full_messages }, status: :unprocessable_entity
end
end
2 changes: 2 additions & 0 deletions serverside_challenge_2/challenge/app/jobs/application_job.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# frozen_string_literal: true

class ApplicationMailer < ActionMailer::Base
default from: "[email protected]"
layout "mailer"
default from: '[email protected]'
layout 'mailer'
end
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
end
11 changes: 11 additions & 0 deletions serverside_challenge_2/challenge/app/models/basic_rate.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class BasicRate < ApplicationRecord
belongs_to :electricity_plan

validates :amperage, presence: true, uniqueness: { scope: :electricity_plan_id }
validates :rate, presence: true
validates :rate, numericality: { greater_than_or_equal_to: 0 }

scope :find_rate_by_amperage, ->(amperage) { find_by(amperage:).rate }
end
21 changes: 21 additions & 0 deletions serverside_challenge_2/challenge/app/models/electricity_plan.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

class ElectricityPlan < ApplicationRecord
belongs_to :provider
has_many :basic_rates, dependent: :destroy
has_many :usage_rates, dependent: :destroy

validates :name, presence: true
validates :name, uniqueness: { scope: :provider_id }

def calculate_total_amount(amperage, usage_kwh)
total_price = BigDecimal(0)

# 基本料金
total_price += basic_rates.find_rate_by_amperage(amperage)
# 従量料金
total_price += UsageRate.calculate_total(usage_kwh, usage_rates)

total_price.floor
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

class ElectricityRateSimulation
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveRecord::AttributeMethods::BeforeTypeCast

attribute :amperage, :integer
attribute :usage_kwh, :integer

validates :amperage, presence: true, numericality: { only_integer: true }
validates :amperage, inclusion: { in: [10, 15, 20, 30, 40, 50, 60] }
validates :usage_kwh, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }

def initialize(amperage, usage_kwh)
super(amperage:, usage_kwh:)
end

def execute
ElectricityPlan.preload(:provider, :basic_rates, :usage_rates).map do |plan|
calculate_rate_plan(plan)
end
end

def calculate_rate_plan(plan)
return SimulationResult.not_found_basic_amperage(plan) if plan.basic_rates.find_by(amperage:).blank?

total_amount = plan.calculate_total_amount(amperage, usage_kwh)

SimulationResult.new(plan, total_amount)
end
end
8 changes: 8 additions & 0 deletions serverside_challenge_2/challenge/app/models/provider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

class Provider < ApplicationRecord
has_many :electricity_plans, dependent: :destroy

validates :name, presence: true
validates :name, uniqueness: true
end
16 changes: 16 additions & 0 deletions serverside_challenge_2/challenge/app/models/simulation_result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

class SimulationResult
attr_reader :provider_name, :plan_name, :total_amount, :error_message

def initialize(plan, total_amount, error_message = nil)
@provider_name = plan.provider&.name
@plan_name = plan.name
@total_amount = total_amount
@error_message = error_message
end

def self.not_found_basic_amperage(plan)
new(plan, nil, 'アンペア数に対応する基本料金が見つからないため料金を計算できません')
end
end
Loading