diff --git a/app/controllers/concerns/dashboard_builder.rb b/app/controllers/concerns/dashboard_builder.rb
index b387e7f..ae6f97a 100644
--- a/app/controllers/concerns/dashboard_builder.rb
+++ b/app/controllers/concerns/dashboard_builder.rb
@@ -1,13 +1,19 @@
# frozen_string_literal: true
module DashboardBuilder
+ extend ActiveSupport::Concern
include TaxedIncome
include TotalCost
+ include SaveIncome
def build_dashboard_variables!
@incomes = Income.order_by_type
@fixed_expenses = FixedExpense.get_ordered
+ @savings_rate = SavingsRate.savings
+ @investing_rate = SavingsRate.investing
build_taxed_income_vars!
+ build_savings_vars!
build_total_cost_vars!
+ Rails.logger.debug "\n *** Building Vars!!\n "
end
end
diff --git a/app/controllers/concerns/save_income.rb b/app/controllers/concerns/save_income.rb
new file mode 100644
index 0000000..4e0ffb7
--- /dev/null
+++ b/app/controllers/concerns/save_income.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module SaveIncome
+ extend ActiveSupport::Concern
+ include TaxedIncome
+
+ def build_savings_vars!
+ @hourly_saving = hourly_saving
+ @hourly_invest = hourly_investing
+ @salary_saving = salary_saving
+ @salary_invest = salary_investing
+ end
+
+ def salary_investing
+ SavingsCalculator.new(tax_on_salary, set_invest_rate)
+ end
+
+ def salary_saving
+ SavingsCalculator.new(tax_on_salary, set_save_rate)
+ end
+
+ def hourly_investing
+ SavingsCalculator.new(tax_on_hourly, set_invest_rate)
+ end
+
+ def hourly_saving
+ SavingsCalculator.new(tax_on_hourly, set_save_rate)
+ end
+
+ private
+
+ def set_save_rate
+ SavingsRate.savings.rate
+ end
+
+ def set_invest_rate
+ SavingsRate.investing.rate
+ end
+end
diff --git a/app/controllers/incomes_controller.rb b/app/controllers/incomes_controller.rb
index aab5e66..8e4501c 100644
--- a/app/controllers/incomes_controller.rb
+++ b/app/controllers/incomes_controller.rb
@@ -1,6 +1,7 @@
class IncomesController < ApplicationController
include DashboardBuilder
include TotalCost
+ include SaveIncome
before_action :set_income, only: %i[show edit update destroy]
@@ -42,7 +43,6 @@ def update
respond_to do |format|
if @income.update_from_dashboard(params: params)
build_dashboard_variables!
- format.html { redirect_to root_path, notice: "Income was successfully updated." }
format.turbo_stream
else
format.html { render :edit, status: :unprocessable_entity }
@@ -93,13 +93,17 @@ def income_params
params.require(:income).permit(:income_type, :rate, :hours, :weekly_income)
end
- def build_locals(income)
+ def build_locals(taxed_income)
+ income = taxed_income.income
build_total_cost_vars!
+ build_savings_vars!
{
total_annual_cost: @total_annual_cost,
total_monthly_cost: @total_monthly_cost,
total_bi_weekly_cost: @total_bi_weekly_cost,
- income: income
+ income: taxed_income,
+ investing_amount: income.is_hourly? ? @hourly_invest : @salary_invest,
+ savings_amount: income.is_hourly? ? @hourly_saving : @salary_saving
}
end
end
diff --git a/app/controllers/savings_rates_controller.rb b/app/controllers/savings_rates_controller.rb
new file mode 100644
index 0000000..be14377
--- /dev/null
+++ b/app/controllers/savings_rates_controller.rb
@@ -0,0 +1,74 @@
+class SavingsRatesController < ApplicationController
+ include DashboardBuilder
+
+ before_action :set_savings_rate, only: %i[show edit update destroy]
+
+ # GET /savings_rates or /savings_rates.json
+ def index
+ @savings_rates = SavingsRate.all
+ end
+
+ # GET /savings_rates/1 or /savings_rates/1.json
+ def show
+ end
+
+ # GET /savings_rates/new
+ def new
+ @savings_rate = SavingsRate.new
+ end
+
+ # GET /savings_rates/1/edit
+ def edit
+ end
+
+ # POST /savings_rates or /savings_rates.json
+ def create
+ @savings_rate = SavingsRate.new(savings_rate_params)
+
+ respond_to do |format|
+ if @savings_rate.save
+ format.html { redirect_to savings_rate_url(@savings_rate), notice: "Savings rate was successfully created." }
+ format.json { render :show, status: :created, location: @savings_rate }
+ else
+ format.html { render :new, status: :unprocessable_entity }
+ format.json { render json: @savings_rate.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT /savings_rates/1 or /savings_rates/1.json
+ def update
+ respond_to do |format|
+ if @savings_rate.update_from_dashboard(params: savings_rate_params)
+ # calculate new savings amounts for both hourly and salary and new guilt free
+ build_dashboard_variables!
+ format.turbo_stream
+ else
+ format.html { render :edit, status: :unprocessable_entity }
+ format.json { render json: @savings_rate.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /savings_rates/1 or /savings_rates/1.json
+ def destroy
+ @savings_rate.destroy
+
+ respond_to do |format|
+ format.html { redirect_to savings_rates_url, notice: "Savings rate was successfully destroyed." }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_savings_rate
+ @savings_rate = SavingsRate.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def savings_rate_params
+ params.require(:savings_rate).permit(:name, :rate)
+ end
+end
diff --git a/app/helpers/savings_rates_helper.rb b/app/helpers/savings_rates_helper.rb
new file mode 100644
index 0000000..7635e12
--- /dev/null
+++ b/app/helpers/savings_rates_helper.rb
@@ -0,0 +1,2 @@
+module SavingsRatesHelper
+end
diff --git a/app/models/savings_rate.rb b/app/models/savings_rate.rb
new file mode 100644
index 0000000..08c932a
--- /dev/null
+++ b/app/models/savings_rate.rb
@@ -0,0 +1,31 @@
+# == Schema Information
+#
+# Table name: savings_rates
+#
+# id :bigint not null, primary key
+# name :string
+# rate :float
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+class SavingsRate < ApplicationRecord
+ SAVING_TYPES = %w[savings investing]
+ validates :name, presence: true, inclusion: SAVING_TYPES
+
+ def self.savings
+ find_by(name: "savings")
+ end
+
+ def self.investing
+ find_by(name: "investing")
+ end
+
+ def display_rate
+ (rate * 100)
+ end
+
+ def update_from_dashboard(params:)
+ rate = params[:rate].to_f / 100
+ update(rate: rate)
+ end
+end
diff --git a/app/services/savings_calculator.rb b/app/services/savings_calculator.rb
new file mode 100644
index 0000000..7cfe974
--- /dev/null
+++ b/app/services/savings_calculator.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+class SavingsCalculator
+ attr_reader :saving_amount,
+ :annual_saving,
+ :biannual_saving,
+ :quarterly_saving,
+ :monthly_saving,
+ :bi_weekly_saving,
+ :weekly_saving,
+ :daily_saving
+
+ def initialize(income_type, saving_rate)
+ @annual_income = income_type.annual_income
+ @saving_rate = saving_rate
+ @annual_saving = calculate_savings
+ @bi_weekly_saving = calculate_bi_weekly_saving
+ @daily_saving = calculate_daily_saving
+ @weekly_saving = calculate_weekly_saving
+ @monthly_saving = calculate_monthly_saving
+ @quarterly_saving = calculate_quarterly_saving
+ @biannual_saving = calculate_biannual_saving
+ end
+
+ def calculate_savings
+ @annual_income * @saving_rate
+ end
+
+ def calculate_daily_saving
+ @annual_saving / 365
+ end
+
+ def calculate_weekly_saving
+ @annual_saving / 52
+ end
+
+ def calculate_bi_weekly_saving
+ @annual_saving / 26
+ end
+
+ def calculate_monthly_saving
+ @annual_saving / 12
+ end
+
+ def calculate_quarterly_saving
+ @annual_saving / 4
+ end
+
+ def calculate_biannual_saving
+ @annual_saving / 2
+ end
+end
diff --git a/app/views/budget/_hourly_budget.html.erb b/app/views/budget/_hourly_budget.html.erb
index 9d9007d..bd5b031 100644
--- a/app/views/budget/_hourly_budget.html.erb
+++ b/app/views/budget/_hourly_budget.html.erb
@@ -1,7 +1,16 @@
<%= turbo_frame_tag "hourly_budget" do %>
+
Hourly
<%= render partial: "budget/budget_headings" %>
- <%= render partial: "shared/budget", locals: { total_annual_cost: total_annual_cost, total_monthly_cost: total_monthly_cost, total_bi_weekly_cost: total_bi_weekly_cost, income: income } %>
+ <%= render partial: "shared/budget",
+ locals: {
+ total_annual_cost: total_annual_cost,
+ total_monthly_cost: total_monthly_cost,
+ total_bi_weekly_cost: total_bi_weekly_cost,
+ income: income,
+ investing_amount: investing_amount,
+ savings_amount: savings_amount
+ } %>
<% end %>
\ No newline at end of file
diff --git a/app/views/budget/_salary_budget.html.erb b/app/views/budget/_salary_budget.html.erb
index 939cc68..d6f13e0 100644
--- a/app/views/budget/_salary_budget.html.erb
+++ b/app/views/budget/_salary_budget.html.erb
@@ -2,6 +2,15 @@
Salary
<%= render partial: "budget/budget_headings" %>
- <%= render partial: "shared/budget", locals: { total_annual_cost: total_annual_cost, total_monthly_cost: total_monthly_cost, total_bi_weekly_cost: total_bi_weekly_cost, income: income } %>
+ <% Rails.logger.debug "*** Salary_budget #{savings_amount.daily_saving}" %>
+ <%= render partial: "shared/budget",
+ locals: {
+ total_annual_cost: total_annual_cost,
+ total_monthly_cost: total_monthly_cost,
+ total_bi_weekly_cost: total_bi_weekly_cost,
+ income: income,
+ investing_amount: investing_amount,
+ savings_amount: savings_amount
+ } %>
<% end %>
diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb
index cf1aac1..2e6eccf 100644
--- a/app/views/dashboard/index.html.erb
+++ b/app/views/dashboard/index.html.erb
@@ -7,12 +7,45 @@
-
Final Budget
-
- <%= render partial: "components/income_switch" %>
+
+
Final Budget
+
Savings Rate %
+
Investing Rate %
+
+
+
+ <%= render partial: "components/income_switch" %>
+
+
+
+ <%= form_with model: @savings_rate, local: true do |form| %>
+
+ <%= form.number_field :rate, in: 0.00..50.00, step: 0.25, value: @savings_rate.display_rate, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-1/4 h-7" %>
+ <%= form.submit "Save", class:"btn btn-primary ml-4 mt-2 py-0.5 my-auto"%>
+
+ <% end %>
+
+
+
+ <%= form_with model: @investing_rate, local: true do |form| %>
+
+ <%= form.number_field :rate, in: 0.00..50.00, step: 0.25, value: @investing_rate.display_rate, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-1/4 h-7" %>
+ <%= form.submit "Invest", class:"btn btn-primary ml-4 mt-2 py-0.5 my-auto"%>
+
+ <% end %>
+
+
- <%= render partial: "budget/salary_budget", locals: { total_annual_cost: @total_annual_cost, total_monthly_cost: @total_monthly_cost, total_bi_weekly_cost: @total_bi_weekly_cost, income: @salary_taxed } %>
+ <%= render partial: "budget/salary_budget",
+ locals: {
+ total_annual_cost: @total_annual_cost,
+ total_monthly_cost: @total_monthly_cost,
+ total_bi_weekly_cost: @total_bi_weekly_cost, income: @salary_taxed,
+ investing_amount: @salary_invest,
+ savings_amount: @salary_saving
+ }
+ %>
@@ -32,7 +65,8 @@
<%= turbo_frame_tag "taxed_incomes" do %>
- <%= render partial: "shared/taxed_incomes", locals: { salary_taxed: @salary_taxed, hourly_taxed: @hourly_taxed} %>
+ <%= render partial: "shared/taxed_incomes",
+ locals: { salary_taxed: @salary_taxed, hourly_taxed: @hourly_taxed} %>
<% end %>
@@ -50,7 +84,11 @@
<% end %>
<%= turbo_frame_tag "total_costs" do %>
- <%= render partial: "shared/total_costs", locals: { total_annual_cost: @total_annual_cost, total_monthly_cost: @total_monthly_cost, total_bi_weekly_cost: @total_bi_weekly_cost } %>
+ <%= render partial: "shared/total_costs",
+ locals: {
+ total_annual_cost: @total_annual_cost,
+ total_monthly_cost: @total_monthly_cost,
+ total_bi_weekly_cost: @total_bi_weekly_cost } %>
<% end %>
diff --git a/app/views/fixed_expenses/update.turbo_stream.erb b/app/views/fixed_expenses/update.turbo_stream.erb
index b431b7a..770766e 100644
--- a/app/views/fixed_expenses/update.turbo_stream.erb
+++ b/app/views/fixed_expenses/update.turbo_stream.erb
@@ -1,13 +1,37 @@
<%= turbo_stream.update @fixed_expense %>
<%= turbo_stream.replace "total_costs" do %>
- <%= render partial: "shared/total_costs", locals: { total_annual_cost: @total_annual_cost, total_monthly_cost: @total_monthly_cost, total_bi_weekly_cost: @total_bi_weekly_cost } %>
+ <%= render partial: "shared/total_costs",
+ locals: {
+ total_annual_cost: @total_annual_cost,
+ total_monthly_cost: @total_monthly_cost,
+ total_bi_weekly_cost: @total_bi_weekly_cost
+ }
+ %>
<% end %>
<%= turbo_stream.replace "salary_budget" do %>
- <%= render partial: "budget/salary_budget", locals: {total_annual_cost: @total_annual_cost, total_monthly_cost: @total_monthly_cost, total_bi_weekly_cost: @total_bi_weekly_cost, income: @salary_taxed} %>
+ <%= render partial: "budget/salary_budget",
+ locals: {
+ total_annual_cost: @total_annual_cost,
+ total_monthly_cost: @total_monthly_cost,
+ total_bi_weekly_cost: @total_bi_weekly_cost,
+ income: @salary_taxed,
+ investing_amount: @salary_invest,
+ savings_amount: @salary_saving
+ }
+ %>
<% end %>
<%= turbo_stream.replace "hourly_budget" do %>
- <%= render partial: "budget/hourly_budget", locals: {total_annual_cost: @total_annual_cost, total_monthly_cost: @total_monthly_cost, total_bi_weekly_cost: @total_bi_weekly_cost, income: @hourly_taxed} %>
+ <%= render partial: "budget/hourly_budget",
+ locals: {
+ total_annual_cost: @total_annual_cost,
+ total_monthly_cost: @total_monthly_cost,
+ total_bi_weekly_cost: @total_bi_weekly_cost,
+ income: @hourly_taxed,
+ investing_amount: @hourly_invest,
+ savings_amount: @hourly_saving
+ }
+ %>
<% end %>
diff --git a/app/views/incomes/update.turbo_stream.erb b/app/views/incomes/update.turbo_stream.erb
index 435496e..b8aa8ff 100644
--- a/app/views/incomes/update.turbo_stream.erb
+++ b/app/views/incomes/update.turbo_stream.erb
@@ -1,13 +1,36 @@
<%= turbo_stream.update @income %>
<%= turbo_stream.replace "taxed_incomes" do %>
- <%= render partial: "shared/taxed_incomes", locals: { salary_taxed: @salary_taxed, hourly_taxed: @hourly_taxed } %>
+ <%= render partial: "shared/taxed_incomes",
+ locals: {
+ salary_taxed: @salary_taxed,
+ hourly_taxed: @hourly_taxed
+ }
+ %>
<% end %>
<%= turbo_stream.replace "salary_budget" do %>
- <%= render partial: "budget/salary_budget", locals: {total_annual_cost: @total_annual_cost, total_monthly_cost: @total_monthly_cost, total_bi_weekly_cost: @total_bi_weekly_cost, income: @salary_taxed} %>
+ <%= render partial: "budget/salary_budget",
+ locals: {
+ total_annual_cost: @total_annual_cost,
+ total_monthly_cost: @total_monthly_cost,
+ total_bi_weekly_cost: @total_bi_weekly_cost,
+ income: @salary_taxed,
+ investing_amount: @salary_invest,
+ savings_amount: @salary_saving
+ }
+ %>
<% end %>
<%= turbo_stream.replace "hourly_budget" do %>
- <%= render partial: "budget/hourly_budget", locals: {total_annual_cost: @total_annual_cost, total_monthly_cost: @total_monthly_cost, total_bi_weekly_cost: @total_bi_weekly_cost, income: @hourly_taxed} %>
+ <%= render partial: "budget/hourly_budget",
+ locals: {
+ total_annual_cost: @total_annual_cost,
+ total_monthly_cost: @total_monthly_cost,
+ total_bi_weekly_cost: @total_bi_weekly_cost,
+ income: @hourly_taxed,
+ investing_amount: @hourly_invest,
+ savings_amount: @hourly_saving
+ }
+ %>
<% end %>
\ No newline at end of file
diff --git a/app/views/savings_rates/_form.html.erb b/app/views/savings_rates/_form.html.erb
new file mode 100644
index 0000000..f1cc3f2
--- /dev/null
+++ b/app/views/savings_rates/_form.html.erb
@@ -0,0 +1,27 @@
+<%= form_with(model: savings_rate, class: "contents") do |form| %>
+ <% if savings_rate.errors.any? %>
+
+
<%= pluralize(savings_rate.errors.count, "error") %> prohibited this savings_rate from being saved:
+
+
+ <% savings_rate.errors.each do |error| %>
+ - <%= error.full_message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name %>
+ <%= form.text_field :name, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
+
+
+
+ <%= form.label :rate %>
+ <%= form.text_field :rate, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
+
+
+
+ <%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
+
+<% end %>
diff --git a/app/views/savings_rates/_savings_rate.html.erb b/app/views/savings_rates/_savings_rate.html.erb
new file mode 100644
index 0000000..129ad18
--- /dev/null
+++ b/app/views/savings_rates/_savings_rate.html.erb
@@ -0,0 +1,17 @@
+
+
+ Name:
+ <%= savings_rate.name %>
+
+
+
+ Rate:
+ <%= savings_rate.rate %>
+
+
+ <% if action_name != "show" %>
+ <%= link_to "Show this savings rate", savings_rate, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+ <%= link_to 'Edit this savings rate', edit_savings_rate_path(savings_rate), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %>
+
+ <% end %>
+
diff --git a/app/views/savings_rates/_savings_rate.json.jbuilder b/app/views/savings_rates/_savings_rate.json.jbuilder
new file mode 100644
index 0000000..0d63a68
--- /dev/null
+++ b/app/views/savings_rates/_savings_rate.json.jbuilder
@@ -0,0 +1,2 @@
+json.extract! savings_rate, :id, :name, :rate, :created_at, :updated_at
+json.url savings_rate_url(savings_rate, format: :json)
diff --git a/app/views/savings_rates/edit.html.erb b/app/views/savings_rates/edit.html.erb
new file mode 100644
index 0000000..51e011c
--- /dev/null
+++ b/app/views/savings_rates/edit.html.erb
@@ -0,0 +1,8 @@
+
+
Editing savings rate
+
+ <%= render "form", savings_rate: @savings_rate %>
+
+ <%= link_to "Show this savings rate", @savings_rate, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+ <%= link_to "Back to savings rates", savings_rates_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+
diff --git a/app/views/savings_rates/index.html.erb b/app/views/savings_rates/index.html.erb
new file mode 100644
index 0000000..66414e1
--- /dev/null
+++ b/app/views/savings_rates/index.html.erb
@@ -0,0 +1,14 @@
+
+ <% if notice.present? %>
+
<%= notice %>
+ <% end %>
+
+
+
Savings rates
+ <%= link_to 'New savings rate', new_savings_rate_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
+
+
+
+ <%= render @savings_rates %>
+
+
diff --git a/app/views/savings_rates/index.json.jbuilder b/app/views/savings_rates/index.json.jbuilder
new file mode 100644
index 0000000..b3f558b
--- /dev/null
+++ b/app/views/savings_rates/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @savings_rates, partial: "savings_rates/savings_rate", as: :savings_rate
diff --git a/app/views/savings_rates/new.html.erb b/app/views/savings_rates/new.html.erb
new file mode 100644
index 0000000..0402756
--- /dev/null
+++ b/app/views/savings_rates/new.html.erb
@@ -0,0 +1,7 @@
+
+
New savings rate
+
+ <%= render "form", savings_rate: @savings_rate %>
+
+ <%= link_to 'Back to savings rates', savings_rates_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+
diff --git a/app/views/savings_rates/show.html.erb b/app/views/savings_rates/show.html.erb
new file mode 100644
index 0000000..b26171e
--- /dev/null
+++ b/app/views/savings_rates/show.html.erb
@@ -0,0 +1,15 @@
+
+
+ <% if notice.present? %>
+
<%= notice %>
+ <% end %>
+
+ <%= render @savings_rate %>
+
+ <%= link_to 'Edit this savings_rate', edit_savings_rate_path(@savings_rate), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+
+ <%= button_to 'Destroy this savings_rate', savings_rate_path(@savings_rate), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
+
+ <%= link_to 'Back to savings_rates', savings_rates_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+
+
diff --git a/app/views/savings_rates/show.json.jbuilder b/app/views/savings_rates/show.json.jbuilder
new file mode 100644
index 0000000..1ce72f9
--- /dev/null
+++ b/app/views/savings_rates/show.json.jbuilder
@@ -0,0 +1 @@
+json.partial! "savings_rates/savings_rate", savings_rate: @savings_rate
diff --git a/app/views/savings_rates/update.turbo_stream.erb b/app/views/savings_rates/update.turbo_stream.erb
new file mode 100644
index 0000000..b4779e5
--- /dev/null
+++ b/app/views/savings_rates/update.turbo_stream.erb
@@ -0,0 +1,27 @@
+<%= turbo_stream.update @savings_rate %>
+
+<%= turbo_stream.replace "salary_budget" do %>
+ <%= render partial: "budget/salary_budget",
+ locals: {
+ total_annual_cost: @total_annual_cost,
+ total_monthly_cost: @total_monthly_cost,
+ total_bi_weekly_cost: @total_bi_weekly_cost,
+ income: @salary_taxed,
+ investing_amount: @salary_invest,
+ savings_amount: @salary_saving
+ }
+ %>
+<% end %>
+
+<%= turbo_stream.replace "hourly_budget" do %>
+ <%= render partial: "budget/hourly_budget",
+ locals: {
+ total_annual_cost: @total_annual_cost,
+ total_monthly_cost: @total_monthly_cost,
+ total_bi_weekly_cost: @total_bi_weekly_cost,
+ income: @hourly_taxed,
+ investing_amount: @hourly_invest,
+ savings_amount: @hourly_saving
+ }
+ %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/shared/_budget.html.erb b/app/views/shared/_budget.html.erb
index 7a1eb2a..e42b434 100644
--- a/app/views/shared/_budget.html.erb
+++ b/app/views/shared/_budget.html.erb
@@ -1,8 +1,13 @@
Daily
+
<%= render partial: "shared/total", locals: { total: total_annual_cost / 365 } %>
-
0
-
0
+
+ <%= humanized_money_with_symbol(investing_amount.daily_saving) %>
+
+
+ <%= humanized_money_with_symbol(savings_amount.daily_saving) %>
+
<%= humanized_money_with_symbol((income.daily_income) - total_annual_cost / 365) %>
@@ -11,8 +16,8 @@
Weekly
<%= render partial: "shared/total", locals: { total: total_annual_cost / 52 } %>
-
0
-
0
+
<%= humanized_money_with_symbol(investing_amount.weekly_saving) %>
+
<%= humanized_money_with_symbol(savings_amount.weekly_saving) %>
<%= humanized_money_with_symbol((income.weekly_income) - total_annual_cost / 52) %>
@@ -21,8 +26,8 @@
Bi-Weekly
<%= render partial: "shared/total", locals: { total: total_bi_weekly_cost } %>
-
0
-
0
+
<%= humanized_money_with_symbol(investing_amount.bi_weekly_saving) %>
+
<%= humanized_money_with_symbol(savings_amount.bi_weekly_saving) %>
<%= humanized_money_with_symbol((income.bi_weekly_net_income) - total_bi_weekly_cost) %>
@@ -31,8 +36,8 @@
Monthly
<%= render partial: "shared/total", locals: { total: total_monthly_cost } %>
-
0
-
0
+
<%= humanized_money_with_symbol(investing_amount.monthly_saving) %>
+
<%= humanized_money_with_symbol(savings_amount.monthly_saving) %>
<%= humanized_money_with_symbol((income.monthly_income) - total_monthly_cost) %>
@@ -41,8 +46,8 @@
Quarterly
<%= render partial: "shared/total", locals: { total: total_annual_cost / 4 } %>
-
0
-
0
+
<%= humanized_money_with_symbol(investing_amount.quarterly_saving) %>
+
<%= humanized_money_with_symbol(savings_amount.quarterly_saving) %>
<%= humanized_money_with_symbol((income.quarterly_income) - total_annual_cost / 4) %>
@@ -51,8 +56,8 @@
Biannual
<%= render partial: "shared/total", locals: { total: total_annual_cost / 2 } %>
-
0
-
0
+
<%= humanized_money_with_symbol(investing_amount.biannual_saving) %>
+
<%= humanized_money_with_symbol(savings_amount.biannual_saving) %>
<%= humanized_money_with_symbol((income.biannual_income) - total_annual_cost / 2) %>
@@ -62,8 +67,8 @@
Annually
<%= render partial: "shared/total", locals: { total: total_annual_cost } %>
-
0
-
0
+
<%= humanized_money_with_symbol(investing_amount.annual_saving) %>
+
<%= humanized_money_with_symbol(savings_amount.annual_saving) %>
<%= humanized_money_with_symbol(income.total_net_income - total_annual_cost ) %>
diff --git a/config/routes.rb b/config/routes.rb
index d07e0e3..0fca9f2 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,5 @@
Rails.application.routes.draw do
+ resources :savings_rates
resources :federal_tax_brackets
resources :fixed_expenses
root "dashboard#index"
diff --git a/db/migrate/20231117231745_create_savings_rates.rb b/db/migrate/20231117231745_create_savings_rates.rb
new file mode 100644
index 0000000..f26953d
--- /dev/null
+++ b/db/migrate/20231117231745_create_savings_rates.rb
@@ -0,0 +1,10 @@
+class CreateSavingsRates < ActiveRecord::Migration[7.0]
+ def change
+ create_table :savings_rates do |t|
+ t.string :name
+ t.float :rate
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4c2a184..98d2dd2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_06_11_195916) do
+ActiveRecord::Schema[7.0].define(version: 2023_11_17_231745) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -50,4 +50,11 @@
t.datetime "updated_at", null: false
end
+ create_table "savings_rates", force: :cascade do |t|
+ t.string "name"
+ t.float "rate"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
end
diff --git a/spec/factories/savings_rates.rb b/spec/factories/savings_rates.rb
new file mode 100644
index 0000000..b1b36d7
--- /dev/null
+++ b/spec/factories/savings_rates.rb
@@ -0,0 +1,16 @@
+# == Schema Information
+#
+# Table name: savings_rates
+#
+# id :bigint not null, primary key
+# name :string
+# rate :float
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+FactoryBot.define do
+ factory :savings_rate do
+ name { "savings" }
+ rate { 1.5 }
+ end
+end
diff --git a/spec/features/dashboard/dashboard_spec.rb b/spec/features/dashboard/dashboard_spec.rb
index 6c1fdf1..7283ba2 100644
--- a/spec/features/dashboard/dashboard_spec.rb
+++ b/spec/features/dashboard/dashboard_spec.rb
@@ -7,6 +7,8 @@
let!(:fed2) { create(:federal_tax_bracket, :tier_2) }
let!(:fed3) { create(:federal_tax_bracket, :tier_3) }
let!(:fixed_expenses) { create_list(:fixed_expense, 2) }
+ let!(:saving_rate) { create(:savings_rate) }
+ let!(:investing_rate) { create(:savings_rate, name: "investing", rate: 0.02) }
describe "GET /index" do
it "routes to root path" do
diff --git a/spec/helpers/savings_rates_helper_spec.rb b/spec/helpers/savings_rates_helper_spec.rb
new file mode 100644
index 0000000..2f2bf94
--- /dev/null
+++ b/spec/helpers/savings_rates_helper_spec.rb
@@ -0,0 +1,15 @@
+require "rails_helper"
+
+# Specs in this file have access to a helper object that includes
+# the SavingsRatesHelper. For example:
+#
+# describe SavingsRatesHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe SavingsRatesHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/savings_rate_spec.rb b/spec/models/savings_rate_spec.rb
new file mode 100644
index 0000000..7928deb
--- /dev/null
+++ b/spec/models/savings_rate_spec.rb
@@ -0,0 +1,15 @@
+# == Schema Information
+#
+# Table name: savings_rates
+#
+# id :bigint not null, primary key
+# name :string
+# rate :float
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+require "rails_helper"
+
+RSpec.describe SavingsRate, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/requests/savings_rates_spec.rb b/spec/requests/savings_rates_spec.rb
new file mode 100644
index 0000000..442165b
--- /dev/null
+++ b/spec/requests/savings_rates_spec.rb
@@ -0,0 +1,130 @@
+require "rails_helper"
+
+# This spec was generated by rspec-rails when you ran the scaffold generator.
+# It demonstrates how one might use RSpec to test the controller code that
+# was generated by Rails when you ran the scaffold generator.
+#
+# It assumes that the implementation code is generated by the rails scaffold
+# generator. If you are using any extension libraries to generate different
+# controller code, this generated spec may or may not pass.
+#
+# It only uses APIs available in rails and/or rspec-rails. There are a number
+# of tools you can use to make these specs even more expressive, but we're
+# sticking to rails and rspec-rails APIs to keep things simple and stable.
+
+RSpec.describe "/savings_rates", type: :request do
+ # This should return the minimal set of attributes required to create a valid
+ # SavingsRate. As you add validations to SavingsRate, be sure to
+ # adjust the attributes here as well.
+ let(:valid_attributes) {
+ skip("Add a hash of attributes valid for your model")
+ }
+
+ let(:invalid_attributes) {
+ skip("Add a hash of attributes invalid for your model")
+ }
+
+ describe "GET /index" do
+ it "renders a successful response" do
+ SavingsRate.create! valid_attributes
+ get savings_rates_url
+ expect(response).to be_successful
+ end
+ end
+
+ describe "GET /show" do
+ it "renders a successful response" do
+ savings_rate = SavingsRate.create! valid_attributes
+ get savings_rate_url(savings_rate)
+ expect(response).to be_successful
+ end
+ end
+
+ describe "GET /new" do
+ it "renders a successful response" do
+ get new_savings_rate_url
+ expect(response).to be_successful
+ end
+ end
+
+ describe "GET /edit" do
+ it "renders a successful response" do
+ savings_rate = SavingsRate.create! valid_attributes
+ get edit_savings_rate_url(savings_rate)
+ expect(response).to be_successful
+ end
+ end
+
+ describe "POST /create" do
+ context "with valid parameters" do
+ it "creates a new SavingsRate" do
+ expect {
+ post savings_rates_url, params: {savings_rate: valid_attributes}
+ }.to change(SavingsRate, :count).by(1)
+ end
+
+ it "redirects to the created savings_rate" do
+ post savings_rates_url, params: {savings_rate: valid_attributes}
+ expect(response).to redirect_to(savings_rate_url(SavingsRate.last))
+ end
+ end
+
+ context "with invalid parameters" do
+ it "does not create a new SavingsRate" do
+ expect {
+ post savings_rates_url, params: {savings_rate: invalid_attributes}
+ }.to change(SavingsRate, :count).by(0)
+ end
+
+ it "renders a response with 422 status (i.e. to display the 'new' template)" do
+ post savings_rates_url, params: {savings_rate: invalid_attributes}
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe "PATCH /update" do
+ context "with valid parameters" do
+ let(:new_attributes) {
+ skip("Add a hash of attributes valid for your model")
+ }
+
+ it "updates the requested savings_rate" do
+ savings_rate = SavingsRate.create! valid_attributes
+ patch savings_rate_url(savings_rate), params: {savings_rate: new_attributes}
+ savings_rate.reload
+ skip("Add assertions for updated state")
+ end
+
+ it "redirects to the savings_rate" do
+ savings_rate = SavingsRate.create! valid_attributes
+ patch savings_rate_url(savings_rate), params: {savings_rate: new_attributes}
+ savings_rate.reload
+ expect(response).to redirect_to(savings_rate_url(savings_rate))
+ end
+ end
+
+ context "with invalid parameters" do
+ it "renders a response with 422 status (i.e. to display the 'edit' template)" do
+ savings_rate = SavingsRate.create! valid_attributes
+ patch savings_rate_url(savings_rate), params: {savings_rate: invalid_attributes}
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe "DELETE /destroy" do
+ it "destroys the requested savings_rate" do
+ savings_rate = SavingsRate.create! valid_attributes
+ expect {
+ delete savings_rate_url(savings_rate)
+ }.to change(SavingsRate, :count).by(-1)
+ end
+
+ it "redirects to the savings_rates list" do
+ savings_rate = SavingsRate.create! valid_attributes
+ delete savings_rate_url(savings_rate)
+ expect(response).to redirect_to(savings_rates_url)
+ end
+ end
+end
diff --git a/spec/routing/savings_rates_routing_spec.rb b/spec/routing/savings_rates_routing_spec.rb
new file mode 100644
index 0000000..d30e805
--- /dev/null
+++ b/spec/routing/savings_rates_routing_spec.rb
@@ -0,0 +1,37 @@
+require "rails_helper"
+
+RSpec.describe SavingsRatesController, type: :routing do
+ describe "routing" do
+ it "routes to #index" do
+ expect(get: "/savings_rates").to route_to("savings_rates#index")
+ end
+
+ it "routes to #new" do
+ expect(get: "/savings_rates/new").to route_to("savings_rates#new")
+ end
+
+ it "routes to #show" do
+ expect(get: "/savings_rates/1").to route_to("savings_rates#show", id: "1")
+ end
+
+ it "routes to #edit" do
+ expect(get: "/savings_rates/1/edit").to route_to("savings_rates#edit", id: "1")
+ end
+
+ it "routes to #create" do
+ expect(post: "/savings_rates").to route_to("savings_rates#create")
+ end
+
+ it "routes to #update via PUT" do
+ expect(put: "/savings_rates/1").to route_to("savings_rates#update", id: "1")
+ end
+
+ it "routes to #update via PATCH" do
+ expect(patch: "/savings_rates/1").to route_to("savings_rates#update", id: "1")
+ end
+
+ it "routes to #destroy" do
+ expect(delete: "/savings_rates/1").to route_to("savings_rates#destroy", id: "1")
+ end
+ end
+end
diff --git a/spec/views/savings_rates/edit.html.tailwindcss_spec.rb b/spec/views/savings_rates/edit.html.tailwindcss_spec.rb
new file mode 100644
index 0000000..430e9ff
--- /dev/null
+++ b/spec/views/savings_rates/edit.html.tailwindcss_spec.rb
@@ -0,0 +1,19 @@
+require "rails_helper"
+
+RSpec.describe "savings_rates/edit", type: :view do
+ let(:savings_rate) { create(:savings_rate) }
+
+ before(:each) do
+ assign(:savings_rate, savings_rate)
+ end
+
+ it "renders the edit savings_rate form" do
+ render
+
+ assert_select "form[action=?][method=?]", savings_rate_path(savings_rate), "post" do
+ assert_select "input[name=?]", "savings_rate[name]"
+
+ assert_select "input[name=?]", "savings_rate[rate]"
+ end
+ end
+end
diff --git a/spec/views/savings_rates/index.html.tailwindcss_spec.rb b/spec/views/savings_rates/index.html.tailwindcss_spec.rb
new file mode 100644
index 0000000..873e010
--- /dev/null
+++ b/spec/views/savings_rates/index.html.tailwindcss_spec.rb
@@ -0,0 +1,23 @@
+require "rails_helper"
+
+RSpec.describe "savings_rates/index", type: :view do
+ before(:each) do
+ assign(:savings_rates, [
+ SavingsRate.create!(
+ name: "savings",
+ rate: 2.5
+ ),
+ SavingsRate.create!(
+ name: "investing",
+ rate: 2.5
+ )
+ ])
+ end
+
+ it "renders a list of savings_rates" do
+ render
+ cell_selector = (Rails::VERSION::STRING >= "7") ? "div>p" : "tr>td"
+ assert_select cell_selector, text: Regexp.new("Name".to_s), count: 2
+ assert_select cell_selector, text: Regexp.new(2.5.to_s), count: 2
+ end
+end
diff --git a/spec/views/savings_rates/new.html.tailwindcss_spec.rb b/spec/views/savings_rates/new.html.tailwindcss_spec.rb
new file mode 100644
index 0000000..16f6ed9
--- /dev/null
+++ b/spec/views/savings_rates/new.html.tailwindcss_spec.rb
@@ -0,0 +1,20 @@
+require "rails_helper"
+
+RSpec.describe "savings_rates/new", type: :view do
+ before(:each) do
+ assign(:savings_rate, SavingsRate.new(
+ name: "investing",
+ rate: 1.5
+ ))
+ end
+
+ it "renders new savings_rate form" do
+ render
+
+ assert_select "form[action=?][method=?]", savings_rates_path, "post" do
+ assert_select "input[name=?]", "savings_rate[name]"
+
+ assert_select "input[name=?]", "savings_rate[rate]"
+ end
+ end
+end
diff --git a/spec/views/savings_rates/show.html.tailwindcss_spec.rb b/spec/views/savings_rates/show.html.tailwindcss_spec.rb
new file mode 100644
index 0000000..cf3ae3b
--- /dev/null
+++ b/spec/views/savings_rates/show.html.tailwindcss_spec.rb
@@ -0,0 +1,16 @@
+require "rails_helper"
+
+RSpec.describe "savings_rates/show", type: :view do
+ before(:each) do
+ assign(:savings_rate, SavingsRate.create!(
+ name: "savings",
+ rate: 2.5
+ ))
+ end
+
+ it "renders attributes in
" do
+ render
+ expect(rendered).to match(/Name/)
+ expect(rendered).to match(/2.5/)
+ end
+end