From a67f36bf6439c7aeed7f9b40d227294d61b7b0bd Mon Sep 17 00:00:00 2001 From: Zach Gollwitzer Date: Wed, 7 May 2025 13:56:20 -0400 Subject: [PATCH] Prevent account deletions when account is linked to a Plaid Item (#2218) * Prevent account deletions when account is linked to a Plaid Item * Only guard deletions in UI and controller, not at model level --- .../concerns/accountable_resource.rb | 8 +++++-- app/helpers/application_helper.rb | 4 ---- app/models/plaid_account.rb | 21 ++++++++++++------- app/views/accounts/_form.html.erb | 5 ++++- app/views/accounts/show/_menu.html.erb | 20 ++++++++++-------- app/views/layouts/shared/_htmldoc.html.erb | 4 +++- .../shared/notifications/_alert.html.erb | 2 +- 7 files changed, 39 insertions(+), 25 deletions(-) diff --git a/app/controllers/concerns/accountable_resource.rb b/app/controllers/concerns/accountable_resource.rb index 16fdbebd..1b2f0aa6 100644 --- a/app/controllers/concerns/accountable_resource.rb +++ b/app/controllers/concerns/accountable_resource.rb @@ -50,8 +50,12 @@ module AccountableResource end def destroy - @account.destroy_later - redirect_to accounts_path, notice: t("accounts.destroy.success", type: accountable_type.name.underscore.humanize) + if @account.linked? + redirect_to account_path(@account), alert: "Cannot delete a linked account" + else + @account.destroy_later + redirect_to accounts_path, notice: t("accounts.destroy.success", type: accountable_type.name.underscore.humanize) + end end private diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 999fd673..a86e374e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -45,10 +45,6 @@ module ApplicationHelper content_for(:header_description) { page_description } end - def family_stream - turbo_stream_from Current.family if Current.family - end - def page_active?(path) current_page?(path) || (request.path.start_with?(path) && path != "/") end diff --git a/app/models/plaid_account.rb b/app/models/plaid_account.rb index 65acf9ae..4f60013e 100644 --- a/app/models/plaid_account.rb +++ b/app/models/plaid_account.rb @@ -15,13 +15,20 @@ class PlaidAccount < ApplicationRecord class << self def find_or_create_from_plaid_data!(plaid_data, family) - find_or_create_by!(plaid_id: plaid_data.account_id) do |a| - a.account = family.accounts.new( - name: plaid_data.name, - balance: plaid_data.balances.current || plaid_data.balances.available, - currency: plaid_data.balances.iso_currency_code, - accountable: TYPE_MAPPING[plaid_data.type].new - ) + PlaidAccount.transaction do + plaid_account = find_or_create_by!(plaid_id: plaid_data.account_id) + + internal_account = family.accounts.find_or_initialize_by(plaid_account_id: plaid_account.id) + + internal_account.name = plaid_data.name + internal_account.balance = plaid_data.balances.current || plaid_data.balances.available + internal_account.currency = plaid_data.balances.iso_currency_code + internal_account.accountable = TYPE_MAPPING[plaid_data.type].new + + internal_account.save! + plaid_account.save! + + plaid_account end end end diff --git a/app/views/accounts/_form.html.erb b/app/views/accounts/_form.html.erb index 8cdb39ff..8a67d20f 100644 --- a/app/views/accounts/_form.html.erb +++ b/app/views/accounts/_form.html.erb @@ -6,7 +6,10 @@ <%= form.hidden_field :return_to, value: params[:return_to] %> <%= form.text_field :name, placeholder: t(".name_placeholder"), required: "required", label: t(".name_label") %> - <%= form.money_field :balance, label: t(".balance"), required: true, default_currency: Current.family.currency %> + + <% unless account.linked? %> + <%= form.money_field :balance, label: t(".balance"), required: true, default_currency: Current.family.currency %> + <% end %> <%= yield form %> diff --git a/app/views/accounts/show/_menu.html.erb b/app/views/accounts/show/_menu.html.erb index 56d78f62..0691f34e 100644 --- a/app/views/accounts/show/_menu.html.erb +++ b/app/views/accounts/show/_menu.html.erb @@ -13,13 +13,15 @@ ) %> <% end %> - <% menu.with_item( - variant: "button", - text: "Delete account", - href: account_path(account), - method: :delete, - icon: "trash-2", - confirm: CustomConfirm.for_resource_deletion("account", high_severity: true), - data: { turbo_frame: :_top } - ) %> + <% unless account.linked? %> + <% menu.with_item( + variant: "button", + text: "Delete account", + href: account_path(account), + method: :delete, + icon: "trash-2", + confirm: CustomConfirm.for_resource_deletion("account", high_severity: true), + data: { turbo_frame: :_top } + ) %> + <% end %> <% end %> diff --git a/app/views/layouts/shared/_htmldoc.html.erb b/app/views/layouts/shared/_htmldoc.html.erb index 9c3908c3..fadcd396 100644 --- a/app/views/layouts/shared/_htmldoc.html.erb +++ b/app/views/layouts/shared/_htmldoc.html.erb @@ -30,7 +30,9 @@ - <%= family_stream %> + <% if Current.family %> + <%= turbo_stream_from Current.family %> + <% end %> <%= turbo_frame_tag "modal" %> <%= turbo_frame_tag "drawer" %> diff --git a/app/views/shared/notifications/_alert.html.erb b/app/views/shared/notifications/_alert.html.erb index 06ccde60..d53793df 100644 --- a/app/views/shared/notifications/_alert.html.erb +++ b/app/views/shared/notifications/_alert.html.erb @@ -1,6 +1,6 @@ <%# locals: (message:) %> -<%= tag.div class: "flex gap-3 rounded-lg bg-container-inset p-4 group w-full md:max-w-80 shadow-border-lg", +<%= tag.div class: "flex gap-3 rounded-lg bg-container p-4 group w-full md:max-w-80 shadow-border-lg", data: { controller: "element-removal" } do %>