Skip to content

Conversation

@sokie
Copy link
Collaborator

@sokie sokie commented Nov 26, 2025

Screenshot 2025-11-26 at 11 06 49 Screenshot 2025-11-26 at 12 01 55 Screenshot 2025-11-26 at 12 05 29

Summary by CodeRabbit

  • New Features
    • Full Enable Banking integration: OAuth bank connections, automatic account discovery, continuous sync, and transaction import.
    • New Settings panel and modal flows to configure credentials, select banks, authorize/re-authorize, and manage connections.
    • Guided account setup modal with account type/subtype selection and bulk creation.
    • Inline UI updates, per-connection status badges (connected/expired/syncing), and accounts list empty-state now includes Enable Banking items.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds an "Enable Banking" integration: new AR models, import/process/sync pipeline, provider HTTP client and adapter, controller/routes for OAuth and account setup, views/partials, a DB migration, and small updates to Accounts and Settings controllers/views.

Changes

Cohort / File(s) Summary
Controllers & Routes
app/controllers/enable_banking_items_controller.rb, app/controllers/accounts_controller.rb, app/controllers/settings/providers_controller.rb, config/routes.rb
New EnableBankingItemsController (create/update/destroy, sync, OAuth flows, account setup/reauthorize/linking) and route entries; AccountsController#index and Settings::ProvidersController#prepare_show_context now surface @enable_banking_items and exclude enable_banking from provider config lists.
Core Models & Associations
app/models/enable_banking_item.rb, app/models/enable_banking_account.rb, app/models/account.rb, app/models/family.rb, app/models/family/enable_banking_connectable.rb
New EnableBankingItem and EnableBankingAccount AR models (snapshots, session/oauth lifecycle, import/sync helpers); Account.create_from_enable_banking_account added; Family includes EnableBankingConnectable with has_many enable_banking_items.
Processing, Importer & Sync
app/models/enable_banking_account/processor.rb, app/models/enable_banking_account/transactions/processor.rb, app/models/enable_banking_entry/processor.rb, app/models/enable_banking_item/importer.rb, app/models/enable_banking_item/syncer.rb, app/models/enable_banking_item/sync_complete_event.rb
Processing pipeline for accounts and transactions, importer that syncs sessions/accounts/balances/transactions, multi‑phase Syncer, and sync-complete broadcasting.
Provider Client & Adapter
app/models/provider/enable_banking.rb, app/models/provider/enable_banking_adapter.rb, app/models/enable_banking_item/provided.rb
New Provider::EnableBanking HTTP client (JWT from certificate, endpoints, error mapping) and Provider::EnableBankingAdapter; EnableBankingItem::Provided helper to instantiate provider.
Views & UI
app/views/settings/providers/_enable_banking_panel.html.erb, app/views/settings/providers/show.html.erb, app/views/accounts/index.html.erb, app/views/enable_banking_items/_enable_banking_item.html.erb, app/views/enable_banking_items/_subtype_select.html.erb, app/views/enable_banking_items/select_bank.html.erb, app/views/enable_banking_items/setup_accounts.html.erb
New settings panel and provider section, account index now considers enable_banking items, plus partials and modals for listing items, selecting ASPSPs, subtype selectors, and account setup flows.
DB Migration
db/migrate/20251126094446_create_enable_banking_items_and_accounts.rb
Creates enable_banking_items and enable_banking_accounts tables (UUID PKs, JSONB payloads, balances, OAuth/session fields, indexes, FKs).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant Controller as EnableBankingItemsController
    participant ProviderClient as Provider::EnableBanking
    participant EnableAPI as Enable Banking API
    participant Item as EnableBankingItem
    participant Syncer as EnableBankingItem::Syncer
    participant Importer as EnableBankingItem::Importer

    Note over User,Browser: User starts bank connect
    Browser->>Controller: POST /enable_banking_items/:id/authorize (aspsp_name)
    Controller->>ProviderClient: start_authorization(aspsp_name, redirect_url, state)
    ProviderClient->>EnableAPI: request auth
    EnableAPI-->>ProviderClient: auth redirect
    ProviderClient-->>Controller: redirect_url
    Controller-->>Browser: redirect user to bank

    Note over EnableAPI,Controller: Bank redirects back with code
    Browser->>Controller: GET /enable_banking_items/callback?code=...
    Controller->>ProviderClient: create_session(code)
    ProviderClient->>EnableAPI: exchange code
    EnableAPI-->>ProviderClient: session payload
    ProviderClient-->>Controller: session created
    Controller->>Item: complete_authorization(code/session)
    Controller->>Syncer: trigger perform_sync

    Syncer->>ProviderClient: get_session / get_account_balances / get_account_transactions
    ProviderClient->>EnableAPI: fetch data
    EnableAPI-->>ProviderClient: payloads
    Syncer->>Importer: import(session_payload)
    Importer->>Importer: upsert accounts -> spawn processors -> import transactions
    Importer-->>Syncer: import result
    Syncer-->>Controller: sync complete
    Controller-->>Browser: update UI (Turbo/TurboStream)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

  • Pay attention to:
    • Provider::EnableBanking (certificate parsing, JWT generation, HTTP/error mapping).
    • OAuth flow (redirect URL/state, controller <-> model interactions).
    • Transaction import/processing (currency/date parsing, dedupe, error handling).
    • Migration and associations (UUID keys, JSONB usage, account_provider relations).
    • Background scheduling, broadcasts, and concurrency around syncs.

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • jjmata
  • luckyPipewrench

Poem

🐰 I hopped through code with tiny paws so bright,

Connected banks, exchanged codes by moonlight.
Accounts now chatter, transactions sing,
Sync bells ring softly — what a lovely thing.
Hooray — Enable Banking leaps into sight! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.86% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Initial enable banking implementation' clearly describes the main change: introducing Enable Banking as a new payment provider integration alongside existing providers like Plaid and SimpleFin.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (22)
app/models/enable_banking_account/transactions/processor.rb (2)

28-30: Consider distinguishing "skipped" from "failed" for nil results.

Per the EnableBankingEntry::Processor (lines 18-22), returning nil indicates the transaction was skipped because there's no linked account—an expected condition, not a failure. Counting it as failed with error message "No linked account" may inflate failure metrics.

Consider tracking a separate skipped_count or only incrementing failed_count for actual exceptions:

       if result.nil?
-        failed_count += 1
-        errors << { index: index, transaction_id: transaction_data[:transaction_id], error: "No linked account" }
+        # Transaction skipped - no linked account (expected for unlinked accounts)
+        Rails.logger.debug "EnableBankingAccount::Transactions::Processor - Skipped transaction #{transaction_data[:transaction_id]} (no linked account)"
       else
         imported_count += 1
       end

36-36: Consider using with_indifferent_access for cleaner key access.

The transaction data access pattern is verbose. If raw_transactions_payload stores hashes, converting once would simplify:

-      transaction_id = transaction_data.try(:[], :transaction_id) || transaction_data.try(:[], "transaction_id") || "unknown"
+      transaction_id = transaction_data.with_indifferent_access[:transaction_id] || "unknown"

This aligns with the pattern in EnableBankingEntry::Processor (line 60).

app/models/enable_banking_entry/processor.rb (2)

76-80: Hardcoded strings should use i18n.

Per coding guidelines, user-facing strings must use localization. These fallback names may appear in the UI:

       counterparty = if amount_value.negative?
-        creditor.presence || "Unknown Recipient"
+        creditor.presence || I18n.t("enable_banking.transaction.unknown_recipient")
       else
-        debtor.presence || "Unknown Sender"
+        debtor.presence || I18n.t("enable_banking.transaction.unknown_sender")
       end

37-39: Consider preserving the original exception cause.

Wrapping exceptions loses the original backtrace. While the error is logged beforehand, consider using raise StandardError, "...", e.backtrace or Ruby 2.1+ cause chaining for better debugging:

     rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
       Rails.logger.error "EnableBankingEntry::Processor - Failed to save transaction #{external_id}: #{e.message}"
-      raise StandardError.new("Failed to import transaction: #{e.message}")
+      raise StandardError, "Failed to import transaction: #{e.message}", cause: e
app/views/settings/providers/show.html.erb (1)

30-34: Consider adding collapsible options for consistency.

The Enable Banking section is missing collapsible: true, open: false options that are present in the SimpleFIN and Lunch Flow sections (lines 17, 23). Additionally, consider using "Enable Banking" (two words with space) for the title to match the "Lunch Flow" and "SimpleFIN" naming style.

Apply this diff for consistency:

-<%= settings_section title: "EnableBanking" do %>
+<%= settings_section title: "Enable Banking", collapsible: true, open: false do %>
   <turbo-frame id="enable_banking-providers-panel">
     <%= render "settings/providers/enable_banking_panel" %>
   </turbo-frame>
 <% end %>
app/views/enable_banking_items/setup_accounts.html.erb (1)

10-19: Clarify form submission behavior with local: true and turbo_frame.

The form has local: true (disables Turbo for the form) but also specifies turbo_frame: "_top". This combination may be redundant since local: true already bypasses Turbo Drive. If you want a full page redirect after submission, local: true alone suffices. If you want Turbo to handle it but target the top frame, remove local: true.

If the intent is a full page navigation after form submission:

-                 local: true,
                  data: {
                    controller: "loading-button",
                    action: "submit->loading-button#showLoading",
                    loading_button_loading_text_value: "Creating Accounts...",
                    turbo_frame: "_top"
                  },

Or if you truly want to bypass Turbo entirely, remove the turbo_frame data attribute.

app/models/enable_banking_item/syncer.rb (2)

8-18: Duplicate session validation.

Session validity is checked here (line 10) and again inside import_latest_enable_banking_data (see relevant code snippet lines 104-107). While defensive, this duplication means the session is validated twice. Consider documenting that this is intentional for early-fail behavior, or removing one check.

If you want to keep both checks, add a comment explaining the early-fail strategy:

 def perform_sync(sync)
-  # Check if session is valid before syncing
+  # Early-fail check: validate session before any work.
+  # Note: import_latest_enable_banking_data also validates, but we check here
+  # to provide immediate feedback and avoid unnecessary API setup.
   unless enable_banking_item.session_valid?

24-24: Simplify chained joins.

The query chains .joins(:account_provider).joins(:account) which creates two separate INNER JOINs. This is functionally correct but could be simplified for readability.

-linked_accounts = enable_banking_item.enable_banking_accounts.joins(:account_provider).joins(:account).merge(Account.visible)
+linked_accounts = enable_banking_item.enable_banking_accounts
+                    .joins(:account_provider, :account)
+                    .merge(Account.visible)
app/models/provider/enable_banking_adapter.rb (1)

40-59: Consider extracting repeated metadata retrieval pattern.

The institution_domain, institution_name, and institution_url methods all repeat the same pattern of fetching and checking provider_account.institution_metadata. Consider extracting to a memoized helper method.

+  def institution_metadata_hash
+    @institution_metadata_hash ||= provider_account.institution_metadata
+  end
+
   def institution_domain
-    metadata = provider_account.institution_metadata
-    return nil unless metadata.present?
-
-    metadata["domain"]
+    return nil unless institution_metadata_hash.present?
+    institution_metadata_hash["domain"]
   end

   def institution_name
-    metadata = provider_account.institution_metadata
-    return nil unless metadata.present?
-
-    metadata["name"] || metadata["aspsp_name"] || item&.aspsp_name
+    return nil unless institution_metadata_hash.present?
+    institution_metadata_hash["name"] || institution_metadata_hash["aspsp_name"] || item&.aspsp_name
   end

   def institution_url
-    metadata = provider_account.institution_metadata
-    return nil unless metadata.present?
-
-    metadata["url"] || item&.institution_url
+    return nil unless institution_metadata_hash.present?
+    institution_metadata_hash["url"] || item&.institution_url
   end
app/models/enable_banking_account/processor.rb (1)

32-36: Redundant nil check for current_account.

The current_account.blank? check on line 33 is redundant since the process method already returns early if current_account is not present (lines 11-14). This dead code path will never execute.

     def process_account!
-      if enable_banking_account.current_account.blank?
-        Rails.logger.error("Enable Banking account #{enable_banking_account.id} has no associated Account")
-        return
-      end
-
       account = enable_banking_account.current_account
       balance = enable_banking_account.current_balance || 0
app/views/settings/providers/_enable_banking_panel.html.erb (2)

87-90: Use functional design tokens instead of raw color values.

Per coding guidelines, prefer functional design system tokens (e.g., bg-primary, text-on-primary) over raw Tailwind colors like bg-gray-900, hover:bg-gray-800, bg-warning. Reference maybe-design-system.css for available tokens.

Also applies to: 124-142


39-71: Consider extracting country options to a constant or helper.

The hardcoded country list could be moved to a model constant (e.g., EnableBankingItem::SUPPORTED_COUNTRIES) or a helper method for better maintainability and reusability.

app/controllers/enable_banking_items_controller.rb (1)

305-324: Consider wrapping account creation loop in a transaction.

The loop creates multiple accounts and AccountProviders. If one fails partway through, the system could be left in an inconsistent state with some accounts created and others not. Consider wrapping in a transaction for atomicity.

+    ActiveRecord::Base.transaction do
       account_types.each do |enable_banking_account_id, selected_type|
         enable_banking_account = @enable_banking_item.enable_banking_accounts.find(enable_banking_account_id)
         # ... rest of loop
       end
+    end
app/models/provider/enable_banking.rb (2)

175-193: JWT generated on every request - consider caching for performance.

A new JWT is generated for every API call via auth_headers. While the 1-hour expiry (line 188) is reasonable, generating JWTs is computationally expensive due to RSA signing. For high-frequency sync operations, consider caching the JWT until close to expiry.

def generate_jwt
  @jwt_cache ||= {}
  now = Time.current.to_i
  
  # Return cached JWT if it has more than 5 minutes remaining
  if @jwt_cache[:token] && @jwt_cache[:exp] > now + 300
    return @jwt_cache[:token]
  end
  
  exp = now + 3600
  # ... generate new JWT ...
  @jwt_cache = { token: token, exp: exp }
  token
end

209-221: Consider sanitizing response body in error messages.

Error messages include the raw response body (e.g., line 209, 217, 221). If the API returns sensitive information in error responses, this could be logged or exposed. Consider truncating or sanitizing the body before including in exceptions.

+    def sanitize_response_body(body, max_length: 500)
+      return "" if body.blank?
+      body.to_s.truncate(max_length)
+    end
+
     when 400
-      raise EnableBankingError.new("Bad request to Enable Banking API: #{response.body}", :bad_request)
+      raise EnableBankingError.new("Bad request to Enable Banking API: #{sanitize_response_body(response.body)}", :bad_request)
app/models/enable_banking_account.rb (3)

7-9: Redundant association definition.

Both account and linked_account are defined with identical through: :account_provider, source: :account configuration. Consider removing linked_account if it serves the same purpose, or document why both are needed.

   # New association through account_providers
   has_one :account_provider, as: :provider, dependent: :destroy
   has_one :account, through: :account_provider, source: :account
-  has_one :linked_account, through: :account_provider, source: :account

26-34: Extract duplicate account_id_data parsing logic.

The logic for extracting account_id_data from raw_account_id (lines 26-34) is duplicated in build_account_name (lines 66-73). Extract this into a private helper method.

+  def extract_account_id_data(snapshot)
+    raw_account_id = snapshot[:account_id]
+    if raw_account_id.is_a?(Hash)
+      raw_account_id
+    elsif raw_account_id.is_a?(Array) && raw_account_id.first.is_a?(Hash)
+      raw_account_id.find { |item| item[:iban].present? } || {}
+    else
+      {}
+    end
+  end

Then use account_id_data = extract_account_id_data(snapshot) in both upsert_enable_banking_snapshot! and build_account_name.


54-60: Inconsistent persistence pattern.

This method uses assign_attributes + save! while upsert_enable_banking_snapshot! uses update!. Consider using consistent patterns across similar methods for maintainability.

   def upsert_enable_banking_transactions_snapshot!(transactions_snapshot)
-    assign_attributes(
-      raw_transactions_payload: transactions_snapshot
-    )
-
-    save!
+    update!(raw_transactions_payload: transactions_snapshot)
   end
app/models/enable_banking_item/importer.rb (2)

109-112: Consider logging exception class in broad rescue.

The broad rescue => e on line 109 could catch unexpected errors. Including e.class in the log message (like line 110 does for transactions) would improve debugging.

     rescue => e
-      Rails.logger.error "EnableBankingItem::Importer - Unexpected error fetching session: #{e.class} - #{e.message}"
+      Rails.logger.error "EnableBankingItem::Importer - Unexpected error fetching session: #{e.class} - #{e.message}\n#{e.backtrace&.first(5)&.join("\n")}"
       nil
     end

114-122: Redundant save! call after upsert_enable_banking_snapshot!.

The upsert_enable_banking_snapshot! method (in EnableBankingAccount) already calls update! which persists the record. The subsequent save! on line 121 is redundant.

     def import_account(account_data)
       uid = account_data[:uid]
 
       enable_banking_account = enable_banking_item.enable_banking_accounts.find_by(uid: uid.to_s)
       return unless enable_banking_account
 
       enable_banking_account.upsert_enable_banking_snapshot!(account_data)
-      enable_banking_account.save!
     end
app/models/enable_banking_item.rb (2)

169-199: Consider extracting status message generation.

The status message logic (lines 179-185 and 191-197) is duplicated. Consider extracting to a private helper for DRY.

+  private
+
+    def format_sync_status(total, linked, unlinked)
+      if total == 0
+        "No accounts found"
+      elsif unlinked == 0
+        "#{linked} #{'account'.pluralize(linked)} synced"
+      else
+        "#{linked} synced, #{unlinked} need setup"
+      end
+    end

246-257: Use Time.zone.parse for timezone-aware parsing.

Time.parse returns a local time. Using Time.zone.parse ensures consistency with Rails' timezone handling.

     def parse_session_expiry(session_result)
       # Enable Banking sessions typically last 90 days
       # The exact expiry depends on the ASPSP consent
       if session_result[:access].present? && session_result[:access][:valid_until].present?
-        Time.parse(session_result[:access][:valid_until])
+        Time.zone.parse(session_result[:access][:valid_until])
       else
         90.days.from_now
       end

state: @enable_banking_item.id
)

redirect_to redirect_url, allow_other_host: true
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Address Brakeman warning: Possible unprotected redirect.

The redirect_to redirect_url, allow_other_host: true on lines 124 and 185 triggers Brakeman's unprotected redirect warning. The redirect_url comes from the Enable Banking API response. While this is typical for OAuth flows, consider validating that the URL matches expected Enable Banking domains before redirecting.

+  ALLOWED_REDIRECT_HOSTS = ["enablebanking.com"].freeze
+
+  def validate_redirect_url!(url)
+    uri = URI.parse(url)
+    unless ALLOWED_REDIRECT_HOSTS.any? { |host| uri.host&.end_with?(host) }
+      raise Provider::EnableBanking::EnableBankingError.new("Invalid redirect URL", :invalid_redirect)
+    end
+    url
+  end

   # In authorize action:
-  redirect_to redirect_url, allow_other_host: true
+  redirect_to validate_redirect_url!(redirect_url), allow_other_host: true

Also applies to: 185-185

🧰 Tools
🪛 GitHub Actions: Pull Request

[warning] 124-124: Brakeman warning: Possible unprotected redirect. (redirect_to) in the application.

🤖 Prompt for AI Agents
In app/controllers/enable_banking_items_controller.rb around lines 124 and 185,
the unguarded redirect_to using redirect_url (from the Enable Banking API)
triggers a Brakeman possible unprotected redirect warning; validate the
redirect_url before calling redirect_to by parsing it into a URI and confirming
its scheme is http or https and its host matches a whitelist of expected Enable
Banking domains (e.g., enable-banking domains you trust); only call redirect_to
redirect_url, allow_other_host: true when validation passes, otherwise redirect
to a safe internal fallback or render an error, and optionally log the invalid
URL for investigation.

Comment on lines +202 to +207
enable_banking_item = Current.family.enable_banking_items.where.not(session_id: nil).first

unless enable_banking_item.present?
redirect_to settings_providers_path, alert: t(".no_session", default: "No active Enable Banking connection. Please connect a bank first.")
return
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Non-deterministic query when multiple Enable Banking items exist.

Using .first without ordering on line 202 could return different items if multiple items with active sessions exist. Consider making this deterministic with .order(:created_at).first or using @enable_banking_item from params instead.

-    enable_banking_item = Current.family.enable_banking_items.where.not(session_id: nil).first
+    enable_banking_item = Current.family.enable_banking_items.where.not(session_id: nil).order(:created_at).first
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
enable_banking_item = Current.family.enable_banking_items.where.not(session_id: nil).first
unless enable_banking_item.present?
redirect_to settings_providers_path, alert: t(".no_session", default: "No active Enable Banking connection. Please connect a bank first.")
return
end
enable_banking_item = Current.family.enable_banking_items.where.not(session_id: nil).order(:created_at).first
unless enable_banking_item.present?
redirect_to settings_providers_path, alert: t(".no_session", default: "No active Enable Banking connection. Please connect a bank first.")
return
end
🤖 Prompt for AI Agents
In app/controllers/enable_banking_items_controller.rb around lines 202 to 207,
the query uses .first on
Current.family.enable_banking_items.where.not(session_id: nil) which is
non-deterministic when multiple active sessions exist; make it deterministic by
ordering (e.g. .order(:created_at).first or .order(created_at: :desc).first
depending on desired semantics) or by selecting the specific item from params
(e.g. use params[:id] to find the exact enable_banking_item) and then proceed
with the present? check and redirect logic accordingly.

Comment on lines +146 to +148
def log_invalid_currency(currency_value)
Rails.logger.warn("Invalid currency code '#{currency_value}' in Enable Banking transaction #{external_id}, falling back to account currency")
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unused method log_invalid_currency.

This method is defined but never called. Either remove it or integrate it with the currency method to log when an invalid currency falls back to account currency or EUR.

     def currency
       tx_amount = data[:transaction_amount] || {}
-      parse_currency(tx_amount[:currency]) || parse_currency(data[:currency]) || account&.currency || "EUR"
+      parsed = parse_currency(tx_amount[:currency]) || parse_currency(data[:currency])
+      if parsed
+        parsed
+      else
+        log_invalid_currency(tx_amount[:currency] || data[:currency])
+        account&.currency || "EUR"
+      end
     end

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/models/enable_banking_entry/processor.rb around lines 146–148, the helper
method log_invalid_currency is defined but never used; either remove it or call
it from the currency resolution logic so an invalid currency code is logged when
you fall back to the account currency or EUR. Update the currency method to
detect an invalid currency_value, invoke log_invalid_currency(currency_value) at
that point (or inline the same Rails.logger.warn message), and ensure the log
contains the invalid value and external_id; if you choose to remove the method,
delete the unused definition and add a one-line warning in the currency fallback
branch instead.

Comment on lines +54 to +65
begin
# For string UIDs, we don't have account data to update - skip the import_account call
# The account data will be fetched via balances/transactions endpoints
if account_data.is_a?(Hash)
import_account(account_data)
end
accounts_updated += 1
rescue => e
accounts_failed += 1
Rails.logger.error "EnableBankingItem::Importer - Failed to update account #{uid}: #{e.message}"
end
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect counter increment for string UIDs.

When account_data is a string (line 41-42), import_account is skipped but accounts_updated is still incremented (line 60). This inflates the success count for accounts that weren't actually updated.

         begin
           # For string UIDs, we don't have account data to update - skip the import_account call
           # The account data will be fetched via balances/transactions endpoints
           if account_data.is_a?(Hash)
             import_account(account_data)
+            accounts_updated += 1
           end
-          accounts_updated += 1
         rescue => e
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
begin
# For string UIDs, we don't have account data to update - skip the import_account call
# The account data will be fetched via balances/transactions endpoints
if account_data.is_a?(Hash)
import_account(account_data)
end
accounts_updated += 1
rescue => e
accounts_failed += 1
Rails.logger.error "EnableBankingItem::Importer - Failed to update account #{uid}: #{e.message}"
end
end
begin
# For string UIDs, we don't have account data to update - skip the import_account call
# The account data will be fetched via balances/transactions endpoints
if account_data.is_a?(Hash)
import_account(account_data)
accounts_updated += 1
end
rescue => e
accounts_failed += 1
Rails.logger.error "EnableBankingItem::Importer - Failed to update account #{uid}: #{e.message}"
end
🤖 Prompt for AI Agents
In app/models/enable_banking_item/importer.rb around lines 54–65, the code
unconditionally increments accounts_updated even when account_data is a string
and import_account is skipped; move the accounts_updated += 1 so it only runs
after a successful import_account call (e.g., place the increment inside the if
account_data.is_a?(Hash) block immediately after import_account), keep the
rescue behaviour to increment accounts_failed and log the error, and ensure you
don't count skipped string UIDs as updated.

Comment on lines +157 to +170
# Paginate through all transactions
loop do
transactions_data = enable_banking_provider.get_account_transactions(
account_id: enable_banking_account.uid,
date_from: start_date,
continuation_key: continuation_key
)

transactions = transactions_data[:transactions] || []
all_transactions.concat(transactions)

continuation_key = transactions_data[:continuation_key]
break if continuation_key.blank?
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add pagination safety limit to prevent infinite loops.

The pagination loop relies on continuation_key being blank to exit. If the API misbehaves and returns the same key repeatedly, this could loop indefinitely. Consider adding a maximum iteration safeguard.

       all_transactions = []
       continuation_key = nil
+      max_pages = 100  # Safety limit
+      page_count = 0
 
       # Paginate through all transactions
       loop do
+        page_count += 1
+        if page_count > max_pages
+          Rails.logger.warn "EnableBankingItem::Importer - Pagination limit reached for account #{enable_banking_account.uid}"
+          break
+        end
+
         transactions_data = enable_banking_provider.get_account_transactions(
           account_id: enable_banking_account.uid,
           date_from: start_date,
           continuation_key: continuation_key
         )
🤖 Prompt for AI Agents
In app/models/enable_banking_item/importer.rb around lines 157 to 170, the
pagination loop can infinite-loop if the provider returns the same
continuation_key repeatedly; add a max-iteration safeguard (e.g. MAX_PAGES = 100
or configurable) and a counter initialized before the loop, increment it each
iteration, and break/raise/log a clear error when the counter exceeds the max to
stop the loop; also consider logging the last continuation_key and response for
debugging when aborting.

(enable_banking_account.name.downcase.include?("checking") ? "checking" :
enable_banking_account.name.downcase.include?("savings") ? "savings" : "") : "" %>
<%= select_tag "account_subtypes[#{enable_banking_account.id}]",
options_for_select([["Select #{account_type == 'Depository' ? 'subtype' : 'type'}", ""]] + subtype_config[:options], selected_value),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use i18n for user-facing dropdown text.

The hardcoded "Select subtype/type" text should use the t() helper for proper localization support, per the coding guideline: "All user-facing strings must use localization (i18n) via the t() helper."

Apply this diff:

     <%= select_tag "account_subtypes[#{enable_banking_account.id}]",
-                  options_for_select([["Select #{account_type == 'Depository' ? 'subtype' : 'type'}", ""]] + subtype_config[:options], selected_value),
+                  options_for_select([[t(".select_option", type: (account_type == 'Depository' ? t(".subtype") : t(".type"))), ""]] + subtype_config[:options], selected_value),
                   { class: "appearance-none bg-container border border-primary rounded-md px-3 py-2 text-sm leading-6 text-primary focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none w-full" } %>

Add to locale file:

en:
  enable_banking_items:
    subtype_select:
      select_option: "Select %{type}"
      subtype: "subtype"
      type: "type"
🤖 Prompt for AI Agents
In app/views/enable_banking_items/_subtype_select.html.erb around line 9, the
dropdown label uses a hardcoded "Select subtype/type" string; replace it with
the i18n t() helper using the provided keys
(enable_banking_items.subtype_select.select_option and
enable_banking_items.subtype_select.subtype or .type) and interpolate the
correct type label (based on account_type == 'Depository' ?
t('enable_banking_items.subtype_select.subtype') :
t('enable_banking_items.subtype_select.type')) so options_for_select uses t(...)
instead of the literal string, and add the given locale entries to the en locale
file.

Comment on lines +25 to +27
<% if aspsp[:logo].present? %>
<img src="<%= aspsp[:logo] %>" alt="<%= aspsp[:name] %>" class="w-10 h-10 rounded object-contain">
<% else %>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Script executed:

#!/bin/bash
# Find the controller handling select_bank action
fd -t f "enable_banking_items_controller" --type rb

# Search for @aspsps assignment
rg -n "@aspsps\s*=" --type=rb

# Search for select_bank action
rg -n "def select_bank" --type=rb

Repository: we-promise/sure


<shell_result>
Repository: we-promise/sure
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository we-promise/sure: Clone operation failed
</shell_result>


Enable Banking API logo URL source documentation


<web_search_result>
Do you mean the Enable (enablebanking.com) API, or a different "Enable Banking" product? Which documentation or endpoint (logo_url field, bank list, or app branding) do you need the logo URL source for?
</web_search_result>


Rails ERB img src attribute XSS security sanitization


<web_search_result>
Short answer

  • Rails' sanitize helpers strip dangerous attributes (e.g., onerror) but will allow an img tag and its src value unless you restrict it. The sanitizer also removes href/src values using clearly unsafe protocols like javascript:. [1][2][3]

Risks to watch for

  • src can still be abused (data:, base64-encoded HTML, or unusual schemes) and historically rails-html-sanitizer had protocol-related issues and CVEs when configurations or allowed-tags were changed; rails-ujs has also had DOM XSS issues (contenteditable + data-* attributes). Upgrade and be cautious. [3][4]

Practical protections (recommended)

  1. Do not use raw/html_safe with user-supplied HTML. Escape by default. [1][5]
  2. Sanitize user HTML when you must render it: sanitize(user_html) and explicitly pass tags/attributes if you need them. [1]
  3. Validate or whitelist img src URLs server-side before rendering. Example approach:
    • Parse and check scheme: allow only http, https (and optionally data:image for images) using URI.parse and permit-list the scheme.
    • Reject or rewrite values with scheme == "javascript" or unexpected data payloads.
      Example (illustrative):
      uri = URI.parse(src) rescue nil
      allowed = %w(http https)
      safe = uri && allowed.include?(uri.scheme)
  4. If you must allow rich pasted HTML or contenteditable areas, upgrade Rails to fixed versions and/or avoid enabling rails-ujs behavior on contenteditable elements. [4]
  5. Use a CSP (Content Security Policy) to mitigate impact of injected scripts.

References

  • Rails sanitize docs (Action View Helpers — sanitize). [1]
  • ActionView sanitize helper comment showing protocol stripping for href/src. [3]
  • rails-html-sanitizer / protocol-related CVEs and guidance. [2]
  • rails-ujs DOM XSS advisory (CVE-2023-23913) and upgrade/workaround recommendation. [4]

If you want, I can: (A) give a tiny helper method you can drop into a Rails app to validate/whitelist img src values, or (B) scan a code example to point out XSS risks.
</web_search_result>


Based on my research, I can now provide a more informed assessment:

Key Findings:

  1. Enable Banking API (enablebanking.com) is a legitimate, trusted banking data aggregation service
  2. Rails img src security: Rails ERB does NOT automatically sanitize src attributes by default. While Rails' sanitizer removes javascript: protocol, it allows standard URLs including potentially malicious data: or base64-encoded URLs
  3. Best practice: Validate/whitelist image URLs server-side before rendering

Context for this review:

  • The aspsp[:logo] data comes from the Enable Banking API (trusted source), NOT user input
  • While the URL is rendered without explicit sanitization, the lower risk comes from the trusted API source
  • However, the code doesn't follow Rails security best practices for URL validation

Here is the rewritten review comment:


Consider validating Enable Banking API logo URLs before rendering.

The bank logo URL from aspsp[:logo] is rendered directly in the src attribute without validation. While the data comes from the trusted Enable Banking API (not user input), Rails security best practices recommend validating or whitelisting image URLs server-side before rendering them. This mitigates risk against potential API response tampering or accidental malformed URLs.

Consider adding a helper method to validate the URL scheme (allowing only http and https) before rendering:

def safe_image_url(url)
  uri = URI.parse(url) rescue nil
  return url if uri && %w(http https).include?(uri.scheme)
  nil
end

Then use <img src="<%= safe_image_url(aspsp[:logo]) %>" ...> in the template.

🤖 Prompt for AI Agents
In app/views/enable_banking_items/select_bank.html.erb around lines 25–27, the
template renders aspsp[:logo] directly into the img src without validating the
URL; add server-side validation/whitelisting for the logo URL (allow only http
and https schemes), implement a helper that parses the URL and returns it only
if it has an allowed scheme (otherwise return nil or a safe placeholder), and
update the template to use that helper so the img tag is only rendered with a
validated URL or a fallback.

Comment on lines +2 to +7
<% dialog.with_header(title: "Set Up Your Enable Banking Accounts") do %>
<div class="flex items-center gap-2">
<%= icon "building-2", class: "text-primary" %>
<span class="text-primary">Choose the correct account types for your imported accounts</span>
</div>
<% end %>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Hardcoded strings should use i18n.

Multiple user-facing strings in this view are hardcoded in English. As per coding guidelines, all user-facing strings must use localization.

Key strings needing i18n:

  • Dialog title: "Set Up Your Enable Banking Accounts"
  • Info header: "Choose the correct account type..."
  • Account type descriptions (lines 30-34)
  • Field labels and help text
  • Button text: "Create Accounts", "Cancel"

Example:

<% dialog.with_header(title: t(".title")) do %>

Comment on lines +2 to +17
<div class="prose prose-sm text-secondary">
<p class="text-primary font-medium">Setup instructions:</p>
<ol>
<li>Visit your <a href="https://enablebanking.com" target="_blank" rel="noopener noreferrer" class="link">Enable Banking</a> developer account to get your credentials</li>
<li>Select your country code from the dropdown below</li>
<li>Enter your Application ID and paste your Client Certificate (including the private key)</li>
<li>Click Save Configuration, then use "Add Connection" to link your bank</li>
</ol>

<p class="text-primary font-medium">Field descriptions:</p>
<ul>
<li><strong>Country Code:</strong> ISO 3166-1 alpha-2 country code (e.g., GB, DE, FR) - determines available banks</li>
<li><strong>Application ID:</strong> The ID generated in your Enable Banking developer account</li>
<li><strong>Client Certificate:</strong> The certificate generated when you created your application (must include the private key)</li>
</ul>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

User-facing strings should use i18n localization.

Per coding guidelines, all user-facing strings must use the t() helper. The setup instructions and field descriptions are hardcoded in English.

Consider extracting these strings to locale files:

<p class="text-primary font-medium"><%= t(".setup_instructions") %></p>
<ol>
  <li><%= t(".instruction_step_1_html", link: link_to("Enable Banking", "https://enablebanking.com", target: "_blank", rel: "noopener noreferrer", class: "link")) %></li>
  <li><%= t(".instruction_step_2") %></li>
  <!-- etc. -->
</ol>

Comment on lines +16 to +18
t.string :status, default: "good"
t.boolean :scheduled_for_deletion, default: false
t.boolean :pending_account_setup, default: false
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add null constraints to columns with defaults.

The status, scheduled_for_deletion, and pending_account_setup columns have default values but are missing null: false constraints. Per the coding guideline: "Use simple validations (null checks, unique indexes) in database layer," these should be non-nullable for data integrity.

Apply this diff:

-      t.string :status, default: "good"
-      t.boolean :scheduled_for_deletion, default: false
-      t.boolean :pending_account_setup, default: false
+      t.string :status, default: "good", null: false
+      t.boolean :scheduled_for_deletion, default: false, null: false
+      t.boolean :pending_account_setup, default: false, null: false

Based on learnings, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR if needed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
t.string :status, default: "good"
t.boolean :scheduled_for_deletion, default: false
t.boolean :pending_account_setup, default: false
t.string :status, default: "good", null: false
t.boolean :scheduled_for_deletion, default: false, null: false
t.boolean :pending_account_setup, default: false, null: false

@sokie
Copy link
Collaborator Author

sokie commented Nov 26, 2025

Added support for multiple connections.
Settings also cannot be changed after having 1 connection.

Screenshot 2025-11-26 at 13 15 34 Screenshot 2025-11-26 at 13 15 43

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (6)
app/views/enable_banking_items/_enable_banking_item.html.erb (2)

23-46: Hardcoded strings should use i18n.

Multiple user-facing strings are hardcoded (e.g., "Deletion in progress", "Syncing...", "Re-authorization required", "Never synced"). Per coding guidelines, use the t() helper for all user-facing strings.


89-92: Move query logic out of the view template.

The left_joins(:account_provider).where(account_providers: { id: nil }).count query belongs in the model. Per coding guidelines: "Keep domain logic out of view templates."

app/controllers/enable_banking_items_controller.rb (3)

124-124: Address Brakeman warning: Possible unprotected redirect.

The redirect URL comes from the Enable Banking API. Validate that it matches expected Enable Banking domains before redirecting.


197-197: Address Brakeman warning for reauthorization redirect.

Same unprotected redirect issue as Line 124.


214-214: Non-deterministic query when multiple Enable Banking items exist.

Using .first without ordering could return different items. Add .order(:created_at).first for deterministic behavior.

app/views/settings/providers/_enable_banking_panel.html.erb (1)

2-17: User-facing strings should use i18n localization.

Setup instructions and field descriptions are hardcoded in English. Use the t() helper per coding guidelines.

🧹 Nitpick comments (7)
app/controllers/enable_banking_items_controller.rb (3)

3-3: Consider using a cryptographic state token for CSRF mitigation.

Skipping CSRF verification for the OAuth callback is necessary, but using @enable_banking_item.id as the state (Line 121) is guessable. Consider using a cryptographically random token stored in the session or database to validate the callback origin.

   # In authorize action:
-  state: @enable_banking_item.id
+  state: SecureRandom.urlsafe_base64(32)
   # Store state in enable_banking_item.authorization_state

128-131: Consider logging backtrace for unexpected errors.

The bare rescue => e catches all StandardError subclasses, which is appropriate for user-facing error handling, but the log message omits the backtrace, making debugging harder.

   rescue => e
-    Rails.logger.error "Unexpected error in authorize: #{e.class}: #{e.message}"
+    Rails.logger.error "Unexpected error in authorize: #{e.class}: #{e.message}\n#{e.backtrace&.first(10)&.join("\n")}"
     redirect_to settings_providers_path, alert: t(".unexpected_error", default: "An unexpected error occurred. Please try again.")

271-277: Account type options should use i18n.

These user-facing strings are hardcoded. Per coding guidelines, use the t() helper.

@account_type_options = [
  [ t(".account_types.checking_or_savings"), "Depository" ],
  [ t(".account_types.credit_card"), "CreditCard" ],
  # ...
]
app/views/settings/providers/_enable_banking_panel.html.erb (4)

100-101: Use design system tokens for button styling.

Per coding guidelines, prefer functional design tokens instead of raw Tailwind color values like bg-gray-900.

-  class: "inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium text-white bg-gray-900 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-gray-900 focus:ring-offset-2 transition-colors"
+  class: "btn btn--primary"

Or use the appropriate design system classes from maybe-design-system.css.


121-136: Multiple hardcoded strings need i18n.

Lines 121 ("Connected Bank"), 123 ("Unknown"), 129-130 ("Connection", "Session expired..."), 135-136 ("Configured", "Ready to connect...") should use the t() helper.


143-169: Action button text should use i18n.

Button labels "Sync", "Re-authorize", "Connect Bank", "Remove" on lines 147, 154, 160, 168 are hardcoded.


177-183: "Add Connection" button styling and text need updates.

Use design system tokens instead of bg-gray-900 and localize "Add Connection" text.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecd8515 and c6b547c.

📒 Files selected for processing (5)
  • app/controllers/enable_banking_items_controller.rb (1 hunks)
  • app/views/accounts/index.html.erb (2 hunks)
  • app/views/enable_banking_items/_enable_banking_item.html.erb (1 hunks)
  • app/views/settings/providers/_enable_banking_panel.html.erb (1 hunks)
  • config/routes.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/views/accounts/index.html.erb
  • config/routes.rb
🧰 Additional context used
📓 Path-based instructions (21)
{app/**/*.{erb,html},app/javascript/**/*.js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer Hotwire-first approach: use Turbo + Stimulus for reactive UI without heavy JavaScript

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
app/views/**/_*.html.erb

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use partials only for simple, static HTML with minimal logic in specific contexts

app/views/**/_*.html.erb: Use underscore prefix for partial files (e.g., _trend_change.html.erb, _form_errors.html.erb, _sync_indicator.html.erb)
Place context-specific partials in relevant controller view directory (e.g., accounts/_account_sidebar_tabs.html.erb)

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
app/views/**/*.erb

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/views/**/*.erb: Always use the icon helper for icons in views, never use lucide_icon directly
Use Tailwind design tokens (e.g., text-primary, bg-container, border-secondary) instead of raw color names
Pass data to Stimulus controllers via data-*-value attributes instead of inline JavaScript
Prefer semantic HTML elements (dialog, details, summary) over JavaScript components
Use Turbo Frames for page sections instead of client-side solutions
Use server-side formatting for currencies, numbers, and dates instead of client-side

Views should use ERB and avoid heavy logic; prefer helpers and components instead (ERB checked by erb-lint per .erb_lint.yml)

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
**/*.{css,erb}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Tailwind CSS v4.x with custom design system for styling

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
**/*.{rb,erb,html.erb}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{rb,erb,html.erb}: Use Current.user for the current user context in controllers and views. Do NOT use current_user.
Use Current.family for the current family context. Do NOT use current_family.

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
  • app/controllers/enable_banking_items_controller.rb
**/*.{erb,html,vue,jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{erb,html,vue,jsx,tsx}: Use Tailwind CSS v4.x with custom design system defined in app/assets/tailwind/maybe-design-system.css. Always use functional tokens (e.g., text-primary not text-white).
Prefer semantic HTML elements over JavaScript components (e.g., use <dialog> for modals, <details><summary> for disclosures).

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
**/*.{erb,html.erb}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{erb,html.erb}: Use icon helper for icons, never use lucide_icon directly.
Leverage Turbo frames for page sections over client-side solutions.
Pass data to Stimulus controllers via data-*-value attributes, not inline JavaScript.

**/*.{erb,html.erb}: Use ViewComponents when: element has complex logic or styling patterns, will be reused across multiple views/contexts, needs structured styling with variants/sizes, requires interactive behavior or Stimulus controllers, has configurable slots or complex APIs, or needs accessibility features or ARIA support
Use Partials when: element is primarily static HTML with minimal logic, used in only one or few specific contexts, is simple template content, doesn't need variants/sizes/complex configuration, or is more about content organization than reusable functionality
Prefer components over partials: if a component is available in app/components, use it; if not, look for a partial; if neither exists, decide between component or partial based on complexity and reusability criteria
Keep domain logic out of views: compute values like button classes, conditional logic, and data transformations in the component file, not the template file
Always use the declarative approach when integrating Stimulus controllers in views: the ERB template should declare what happens using data-* attributes, and the Stimulus controller should respond
Component controllers in app/components/ should only be used within their component templates; global controllers in app/javascript/controllers/ can be used across any view
Pass data from Rails to Stimulus using data-*-value attributes, not inline JavaScript

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
**/*.{erb,html.erb,rb}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{erb,html.erb,rb}: All user-facing strings must use localization (i18n) via the t() helper. Update locale files for each new or changed element.
Use server-side formatting for currencies, numbers, and dates. Do not format in JavaScript.

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
  • app/controllers/enable_banking_items_controller.rb
app/views/**/_*.{erb,html.erb}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Partials when element is primarily static HTML with minimal logic, used in only one or few contexts, is simple template content, or doesn't need variants/sizes/complex configuration.

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
{app/components/**/*.html.erb,app/views/**/*.{erb,html.erb}}

📄 CodeRabbit inference engine (CLAUDE.md)

Keep domain logic out of view templates. Logic belongs in component files, not template files.

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
**/*.erb

📄 CodeRabbit inference engine (CLAUDE.md)

Run bundle exec erb_lint ./app/**/*.erb -a for ERB linting with auto-correct before pull requests.

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/general-rules.mdc)

**/*.{rb,erb}: Use Current.user for accessing the current user. Do NOT use current_user
Use Current.family for accessing the current family. Do NOT use current_family

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
  • app/controllers/enable_banking_items_controller.rb
**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

**/*.html.erb: Prefer native HTML elements over JavaScript-based components. Use semantic HTML: for modals,

for disclosures
Leverage Turbo frames to break up pages instead of JavaScript-driven client-side solutions
Use the icon helper from application_helper.rb for icons. Never use lucide_icon helper directly

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
**/*.{rb,js,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, dates, and other values server-side, then pass to Stimulus controllers for display only

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
  • app/controllers/enable_banking_items_controller.rb
**/*.{rb,html.erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Use ActiveRecord validations for complex validations and business logic. Simple validations may be mirrored in ActiveRecord for form error handling convenience, but prioritize client-side form validation when possible

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
  • app/controllers/enable_banking_items_controller.rb
{app/views/**,app/helpers/**,app/javascript/controllers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

{app/views/**,app/helpers/**,app/javascript/controllers/**}: Reference maybe-design-system.css for base primitives, functional tokens, and component tokens before writing styles
Prefer using functional design system tokens (e.g., text-primary, bg-container, border-primary) from maybe-design-system.css instead of raw Tailwind color values

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
{app/views/**,app/helpers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
**/*.rb

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rb: Application supports two modes: 'managed' and 'self_hosted' via Rails.application.config.app_mode
Use Current.user and Current.family instead of current_user / current_family for authentication context
Optimize database queries with proper indexes to prevent N+1 queries using includes/joins

**/*.rb: Use Rails pluralization in i18n: t("transactions.count", count: @transactions.count).
Run bin/rubocop -f github -a for Ruby linting with auto-correct before pull requests.

**/*.rb: Ruby code should use 2-space indent, snake_case for methods and variables, and CamelCase for classes and modules, following Rails conventions for folders and file names
Prefer environment variables over hard-coded values for configuration

Files:

  • app/controllers/enable_banking_items_controller.rb
app/controllers/**/*.rb

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Rails strong parameters and CSRF protection throughout the application

app/controllers/**/*.rb: Use strong parameters and CSRF protection throughout the application.
Use query params for state over localStorage/sessions.

Files:

  • app/controllers/enable_banking_items_controller.rb
app/**/*.{rb,js,ts,jsx,tsx,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Code should be organized in app/ directory (Rails MVC, services, jobs, mailers, components), with JS in app/javascript/, and styles/assets in app/assets/ (Tailwind, images, fonts)

Files:

  • app/controllers/enable_banking_items_controller.rb
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Run bin/brakeman security scan before major PRs to check for static analysis of common Rails issues

Files:

  • app/controllers/enable_banking_items_controller.rb
🧠 Learnings (31)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Use Plaid integration for real-time bank account syncing via `PlaidItem`, `Sync` models, and background jobs for data updates.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:57:20.307Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-11-24T16:57:20.307Z
Learning: Applies to app/views/**/_*.html.erb : Place context-specific partials in relevant controller view directory (e.g., accounts/_account_sidebar_tabs.html.erb)

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/views/**/*.html.erb : Use partials when content is primarily static, simple, context-specific, and without variants or complex configuration

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to app/views/**/_*.{erb,html.erb} : Use Partials when element is primarily static HTML with minimal logic, used in only one or few contexts, is simple template content, or doesn't need variants/sizes/complex configuration.

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to config/locales/**/*.yml : Use hierarchical i18n keys by feature: `accounts.index.title`, `transactions.form.amount_label`. Use `t()` helper for all user-facing strings with interpolation for dynamic content.

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
📚 Learning: 2025-11-24T16:56:13.394Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-11-24T16:56:13.394Z
Learning: Applies to **/*.html.erb : Prefer native HTML elements over JavaScript-based components. Use semantic HTML: <dialog> for modals, <details><summary> for disclosures

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
📚 Learning: 2025-11-24T16:57:20.307Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-11-24T16:57:20.307Z
Learning: Applies to **/*.{erb,html.erb} : Use Partials when: element is primarily static HTML with minimal logic, used in only one or few specific contexts, is simple template content, doesn't need variants/sizes/complex configuration, or is more about content organization than reusable functionality

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-11-24T16:54:59.185Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T16:54:59.185Z
Learning: Applies to app/views/**/_*.html.erb : Use partials only for simple, static HTML with minimal logic in specific contexts

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-11-24T16:54:59.185Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T16:54:59.185Z
Learning: Applies to app/views/**/*.erb : Prefer semantic HTML elements (dialog, details, summary) over JavaScript components

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-11-24T16:57:20.307Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-11-24T16:57:20.307Z
Learning: Applies to app/views/**/_*.html.erb : Use underscore prefix for partial files (e.g., _trend_change.html.erb, _form_errors.html.erb, _sync_indicator.html.erb)

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to **/*.{erb,html.erb,rb} : All user-facing strings must use localization (i18n) via the `t()` helper. Update locale files for each new or changed element.

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
📚 Learning: 2025-10-07T09:51:02.265Z
Learnt from: jjmata
Repo: we-promise/sure PR: 189
File: app/views/password_resets/new.html.erb:14-14
Timestamp: 2025-10-07T09:51:02.265Z
Learning: In the we-promise/sure repository, use i18n methods (like `t()` or `I18n.t`) for all user-facing strings to maintain proper internationalization infrastructure. While translations should be provided, extensive localization work is not a priority at this stage of development.

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
📚 Learning: 2025-10-01T11:40:36.711Z
Learnt from: jjmata
Repo: we-promise/sure PR: 173
File: app/mailers/email_confirmation_mailer.rb:9-9
Timestamp: 2025-10-01T11:40:36.711Z
Learning: In the we-promise/sure repository, i18n (internationalization) is used throughout the application for translations. When making branding configurable (e.g., product names), it's appropriate to add parameters like `product: product_name` to existing `t()` translation calls rather than replacing them with hardcoded strings.

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to **/*.rb : Use Rails pluralization in i18n: `t("transactions.count", count: transactions.count)`.

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
📚 Learning: 2025-11-24T16:56:13.394Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-11-24T16:56:13.394Z
Learning: Applies to **/*.{rb,js,erb} : Format currencies, numbers, dates, and other values server-side, then pass to Stimulus controllers for display only

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
📚 Learning: 2025-11-24T16:54:59.185Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T16:54:59.185Z
Learning: Applies to app/views/**/*.erb : Use server-side formatting for currencies, numbers, and dates instead of client-side

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/views/settings/providers/_enable_banking_panel.html.erb
📚 Learning: 2025-11-24T16:57:20.307Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-11-24T16:57:20.307Z
Learning: Applies to **/*.{erb,html.erb} : Keep domain logic out of views: compute values like button classes, conditional logic, and data transformations in the component file, not the template file

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Keep domain logic out of ERB templates; compute classes and logic in component/Ruby code, not inline in the template

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-11-24T16:57:34.442Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:57:34.442Z
Learning: Applies to app/views/**/*.erb : Views should use ERB and avoid heavy logic; prefer helpers and components instead (ERB checked by `erb-lint` per `.erb_lint.yml`)

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to {app/components/**/*.html.erb,app/views/**/*.{erb,html.erb}} : Keep domain logic out of view templates. Logic belongs in component files, not template files.

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-11-24T16:54:59.185Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T16:54:59.185Z
Learning: Applies to **/*.rb : Optimize database queries with proper indexes to prevent N+1 queries using includes/joins

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-11-24T16:56:13.394Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-11-24T16:56:13.394Z
Learning: Use Rails concerns for code organization around model 'traits' and shared functionality, not just for moving code to another location. Prefer models answering questions about themselves with methods like account.balance_series over service-like patterns

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/views/enable_banking_items/_enable_banking_item.html.erb
  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to **/*.{erb,html.erb,rb} : Use server-side formatting for currencies, numbers, and dates. Do not format in JavaScript.

Applied to files:

  • app/views/settings/providers/_enable_banking_panel.html.erb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to app/models/**/*.rb : All monetary values stored in base currency (user's primary currency). Use `Money` objects for currency conversion and formatting.

Applied to files:

  • app/views/settings/providers/_enable_banking_panel.html.erb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Use Plaid integration for real-time bank account syncing via `PlaidItem`, `Sync` models, and background jobs for data updates.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to app/controllers/**/*.rb : Use strong parameters and CSRF protection throughout the application.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:54:59.185Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T16:54:59.185Z
Learning: Applies to app/controllers/**/*.rb : Use Rails strong parameters and CSRF protection throughout the application

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
🧬 Code graph analysis (1)
app/controllers/enable_banking_items_controller.rb (2)
app/models/enable_banking_item.rb (4)
  • revoke_session (225-242)
  • credentials_configured? (41-43)
  • start_authorization (59-77)
  • complete_authorization (80-98)
app/models/provider/enable_banking.rb (2)
  • get_aspsps (21-31)
  • start_authorization (40-63)
🪛 GitHub Actions: Pull Request
app/controllers/enable_banking_items_controller.rb

[warning] 124-124: Brakeman: Possible unprotected redirect. Code: redirect_to(Current.family.enable_banking_items.find(params[:id]).start_authorization(...), :allow_other_host => true)


[warning] 197-197: Brakeman: Possible unprotected redirect. Code: redirect_to(Current.family.enable_banking_items.find(params[:id]).start_authorization(...), :allow_other_host => true)

🔇 Additional comments (8)
app/views/enable_banking_items/_enable_banking_item.html.erb (2)

1-17: LGTM - Header structure is clean and semantic.

The local declaration is proper, and the header section uses semantic HTML with <details> and <summary> elements following coding guidelines. The conditional logo/initials fallback is well-implemented.


52-79: Action buttons implementation looks correct.

The conditional rendering based on requires_update? and the development-mode refresh are appropriate. The turbo: false for OAuth redirect and CustomConfirm.for_resource_deletion for deletion are good patterns.

app/controllers/enable_banking_items_controller.rb (6)

5-37: LGTM - Create action is well-structured.

Uses Current.family correctly, applies strong parameters, and handles both Turbo frame and regular requests with appropriate flash messages using t() helper.


39-68: Update action follows correct patterns.

Consistent with create action, uses strong parameters and handles Turbo/non-Turbo responses appropriately.


70-85: Destroy and sync actions are well-implemented.

The destroy action properly revokes the session before scheduling deletion. The sync action correctly prevents duplicate jobs by checking syncing? first.


87-106: Select bank action handles errors appropriately.

Validates credentials first, catches API errors gracefully with logging, and falls back to an empty list. The layout: false is correct for modal rendering.


134-174: Callback action handles OAuth flow correctly.

The error parameter handling, state validation, and authorization completion flow are well-structured. Consider the earlier suggestion about using cryptographic state tokens for additional security.


348-372: Private methods are well-structured.

Strong parameters include appropriate fields, and the callback URL generation correctly handles development vs production environments.

client_certificate: @enable_banking_item.client_certificate
)

redirect_to select_bank_enable_banking_item_path(new_item), data: { turbo_frame: "modal" }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The data: { turbo_frame: "modal" } on redirect_to has no effect.

Turbo frame data attributes only work on links and forms, not server-side redirects. The browser will follow the redirect normally.

-    redirect_to select_bank_enable_banking_item_path(new_item), data: { turbo_frame: "modal" }
+    redirect_to select_bank_enable_banking_item_path(new_item)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
redirect_to select_bank_enable_banking_item_path(new_item), data: { turbo_frame: "modal" }
redirect_to select_bank_enable_banking_item_path(new_item)
🤖 Prompt for AI Agents
In app/controllers/enable_banking_items_controller.rb around line 185, the
controller is calling redirect_to with data: { turbo_frame: "modal" } which has
no effect because Turbo frame attributes only apply to links/forms in the
browser, not server-side redirects; remove the data: { turbo_frame: "modal" }
from the redirect and either (a) if you want to open/update a modal frame as a
result of this action, respond with a Turbo Stream (render a turbo_stream
response that targets the "modal" frame and replaces/updates its contents) or
(b) keep the redirect but ensure the originating link/form includes
data-turbo-frame="modal" so the browser issues a frame-targeted request; pick
the appropriate approach and implement the corresponding server response
(turbo_stream render for modal update, or plain redirect without data).

Comment on lines +224 to +251
selected_uids.each do |uid|
enable_banking_account = enable_banking_item.enable_banking_accounts.find_by(uid: uid)
next unless enable_banking_account

# Check if already linked
if enable_banking_account.account_provider.present?
already_linked_accounts << enable_banking_account.name
next
end

# Create the internal Account
account = Account.create_and_sync(
family: Current.family,
name: enable_banking_account.name,
balance: enable_banking_account.current_balance || 0,
currency: enable_banking_account.currency || "EUR",
accountable_type: accountable_type,
accountable_attributes: {}
)

# Link account to enable_banking_account via account_providers
AccountProvider.create!(
account: account,
provider: enable_banking_account
)

created_accounts << account
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Wrap account creation loop in a database transaction.

If AccountProvider.create! fails midway through the loop, some accounts will be created without their provider links, leaving the database in an inconsistent state.

+  ActiveRecord::Base.transaction do
     selected_uids.each do |uid|
       enable_banking_account = enable_banking_item.enable_banking_accounts.find_by(uid: uid)
       next unless enable_banking_account
       # ... rest of loop
     end
+  end
🤖 Prompt for AI Agents
In app/controllers/enable_banking_items_controller.rb around lines 224 to 251,
the loop creating Account and AccountProvider should be executed inside an
ActiveRecord transaction so partial failures don't leave created accounts
without provider links; wrap the selected_uids.each block in a transaction (e.g.
ActiveRecord::Base.transaction do ... end), ensure account creation raises on
failure (use a bang or raise if create_and_sync returns a failed record) so the
transaction will roll back, and keep AccountProvider.create! as-is so any
exception will abort and rollback the whole operation.

Comment on lines 317 to 336
account_types.each do |enable_banking_account_id, selected_type|
enable_banking_account = @enable_banking_item.enable_banking_accounts.find(enable_banking_account_id)
selected_subtype = account_subtypes[enable_banking_account_id]

# Default subtype for CreditCard since it only has one option
selected_subtype = "credit_card" if selected_type == "CreditCard" && selected_subtype.blank?

# Create account with user-selected type and subtype
account = Account.create_from_enable_banking_account(
enable_banking_account,
selected_type,
selected_subtype
)

# Link account via AccountProvider
AccountProvider.create!(
account: account,
provider: enable_banking_account
)
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Wrap account setup loop in a database transaction.

Similar to link_accounts, if account creation or provider linking fails partway through, the database will be left in an inconsistent state with some accounts created but not all.

+  ActiveRecord::Base.transaction do
     account_types.each do |enable_banking_account_id, selected_type|
       # ... account creation logic
     end
+  end
🤖 Prompt for AI Agents
In app/controllers/enable_banking_items_controller.rb around lines 317 to 336,
the account creation and AccountProvider linking loop must be wrapped in a
database transaction so partial failures don't leave the DB in an inconsistent
state; surround the loop with an ActiveRecord transaction (e.g.,
ActiveRecord::Base.transaction do ... end) and ensure failures raise to trigger
rollback by using bang methods or raising if create_from_enable_banking_account
returns an invalid record, so any exception during account creation or
AccountProvider.create! will roll back all changes.

Comment on lines +26 to +31
<%
enable_banking_item = Current.family.enable_banking_items.first_or_initialize(name: "Enable Banking Connection")
is_new_record = enable_banking_item.new_record?
# Check if there are any authenticated connections (have session_id)
has_authenticated_connections = Current.family.enable_banking_items.where.not(session_id: nil).exists?
%>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Move query logic to the controller.

The first_or_initialize and where.not(...).exists? queries are domain logic that should be computed in the controller and passed as locals.

# In controller:
@enable_banking_item = Current.family.enable_banking_items.first_or_initialize(name: "Enable Banking Connection")
@has_authenticated_connections = Current.family.enable_banking_items.where.not(session_id: nil).exists?
🤖 Prompt for AI Agents
In app/views/settings/providers/_enable_banking_panel.html.erb around lines 26
to 31 the view is performing ActiveRecord queries (first_or_initialize and
where.not(...).exists?) that belong in the controller; move these queries into
the corresponding controller action (e.g. set @enable_banking_item =
Current.family.enable_banking_items.first_or_initialize(name: "Enable Banking
Connection") and @has_authenticated_connections =
Current.family.enable_banking_items.where.not(session_id: nil).exists?) and pass
them to the view (as instance vars or locals), then update the partial to
reference those variables and remove the query logic from the template; ensure
the controller always sets those variables (or provide safe nil/defaults) so the
view does not run queries.

Comment on lines +108 to +113
<%
# Find the first item with valid session to use for "Add Connection" button
item_for_new_connection = items.find(&:session_valid?)
# Check if any item needs initial connection (configured but no session yet)
item_needing_connection = items.find { |i| !i.session_valid? && !i.session_expired? }
%>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Move item filtering logic to the controller.

The items.find(&:session_valid?) and items.find { |i| ... } logic should be computed in the controller and passed as locals.

# In controller:
@item_for_new_connection = items.find(&:session_valid?)
@item_needing_connection = items.find { |i| !i.session_valid? && !i.session_expired? }
🤖 Prompt for AI Agents
In app/views/settings/providers/_enable_banking_panel.html.erb around lines 108
to 113, the view currently runs items.find(&:session_valid?) and items.find {
|i| !i.session_valid? && !i.session_expired? } — move this filtering into the
controller so the view receives precomputed values; in the controller compute
@item_for_new_connection = items.find(&:session_valid?) and
@item_needing_connection = items.find { |i| !i.session_valid? &&
!i.session_expired? } (or pass them as locals when rendering the partial), then
remove the inline find calls from the partial and reference the passed-in
variables instead.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
app/views/enable_banking_items/setup_accounts.html.erb (1)

2-7: Hardcoded strings should use i18n.

As previously noted, multiple user-facing strings are hardcoded. Per coding guidelines, all user-facing strings must use t() helper with corresponding locale entries.

Also applies to: 26-35, 45-54, 63-68, 82-83, 105-105, 113-113

app/models/enable_banking_entry/processor.rb (1)

142-149: Wire log_invalid_currency into currency fallback logic

log_invalid_currency is still unused; this was flagged previously. You can keep the helper and improve observability by invoking it when both transaction-level and entry-level currency codes fail parse_currency, while preserving the existing fallback behavior:

-    def currency
-      tx_amount = data[:transaction_amount] || {}
-      parse_currency(tx_amount[:currency]) || parse_currency(data[:currency]) || account&.currency || "EUR"
-    end
-
-    def log_invalid_currency(currency_value)
-      Rails.logger.warn("Invalid currency code '#{currency_value}' in Enable Banking transaction #{external_id}, falling back to account currency")
-    end
+    def currency
+      tx_amount = data[:transaction_amount] || {}
+      tx_currency = tx_amount[:currency]
+      entry_currency = data[:currency]
+
+      if tx_currency.present?
+        parsed = parse_currency(tx_currency)
+        return parsed if parsed
+
+        log_invalid_currency(tx_currency)
+      end
+
+      if entry_currency.present?
+        parsed = parse_currency(entry_currency)
+        return parsed if parsed
+
+        log_invalid_currency(entry_currency)
+      end
+
+      account&.currency || "EUR"
+    end
+
+    def log_invalid_currency(currency_value)
+      Rails.logger.warn(
+        "Invalid currency code '#{currency_value}' in Enable Banking transaction #{external_id}, falling back to account currency"
+      )
+    end

This keeps the resolution order (transaction_amount.currencydata.currencyaccount"EUR"), logs truly invalid codes, and avoids silently swallowing bad data.

🧹 Nitpick comments (3)
app/models/enable_banking_account.rb (2)

6-17: Redundant associations and method.

linked_account (line 9) and current_account (lines 15-17) both resolve to the same account association. If this redundancy is intentional for API consistency with other banking integrations, consider adding a comment explaining the purpose. Otherwise, remove the duplicates.


19-43: Consider extracting the type mappings to a frozen constant.

The type_mappings hash is recreated on every method call. For marginal efficiency and clearer documentation, extract it to a class constant.

+  # Map PSD2 cash_account_type codes to user-friendly names
+  # Based on ISO 20022 External Cash Account Type codes
+  ACCOUNT_TYPE_MAPPINGS = {
+    "CACC" => "Current/Checking Account",
+    "SVGS" => "Savings Account",
+    "CARD" => "Card Account",
+    "CRCD" => "Credit Card",
+    "LOAN" => "Loan Account",
+    "MORT" => "Mortgage Account",
+    "ODFT" => "Overdraft Account",
+    "CASH" => "Cash Account",
+    "TRAN" => "Transacting Account",
+    "SALA" => "Salary Account",
+    "MOMA" => "Money Market Account",
+    "NREX" => "Non-Resident External Account",
+    "TAXE" => "Tax Account",
+    "TRAS" => "Cash Trading Account",
+    "ONDP" => "Overnight Deposit"
+  }.freeze
+
-  # Map PSD2 cash_account_type codes to user-friendly names
-  # Based on ISO 20022 External Cash Account Type codes
   def account_type_display
     return nil unless account_type.present?
-
-    type_mappings = {
-      "CACC" => "Current/Checking Account",
-      ...
-    }
-
-    type_mappings[account_type.upcase] || account_type.titleize
+    ACCOUNT_TYPE_MAPPINGS[account_type.upcase] || account_type.titleize
   end
app/models/enable_banking_entry/processor.rb (1)

63-67: Clarify external ID missing-field error message

external_id correctly falls back to entry_reference, but the raised error only mentions transaction_id when both are missing. Consider updating the message so it reflects both accepted fields, e.g.:

-      raise ArgumentError, "Enable Banking transaction missing required field 'transaction_id'" unless id
+      raise ArgumentError, "Enable Banking transaction missing required field 'transaction_id' or 'entry_reference'" unless id

This will make debugging malformed payloads easier when only entry_reference is relevant.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6b547c and 532954b.

📒 Files selected for processing (4)
  • app/models/enable_banking_account.rb (1 hunks)
  • app/models/enable_banking_account/processor.rb (1 hunks)
  • app/models/enable_banking_entry/processor.rb (1 hunks)
  • app/views/enable_banking_items/setup_accounts.html.erb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/models/enable_banking_account/processor.rb
🧰 Additional context used
📓 Path-based instructions (20)
{app/**/*.{erb,html},app/javascript/**/*.js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer Hotwire-first approach: use Turbo + Stimulus for reactive UI without heavy JavaScript

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
app/views/**/*.erb

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/views/**/*.erb: Always use the icon helper for icons in views, never use lucide_icon directly
Use Tailwind design tokens (e.g., text-primary, bg-container, border-secondary) instead of raw color names
Pass data to Stimulus controllers via data-*-value attributes instead of inline JavaScript
Prefer semantic HTML elements (dialog, details, summary) over JavaScript components
Use Turbo Frames for page sections instead of client-side solutions
Use server-side formatting for currencies, numbers, and dates instead of client-side

Views should use ERB and avoid heavy logic; prefer helpers and components instead (ERB checked by erb-lint per .erb_lint.yml)

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
**/*.{css,erb}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Tailwind CSS v4.x with custom design system for styling

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
**/*.{rb,erb,html.erb}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{rb,erb,html.erb}: Use Current.user for the current user context in controllers and views. Do NOT use current_user.
Use Current.family for the current family context. Do NOT use current_family.

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
**/*.{erb,html,vue,jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{erb,html,vue,jsx,tsx}: Use Tailwind CSS v4.x with custom design system defined in app/assets/tailwind/maybe-design-system.css. Always use functional tokens (e.g., text-primary not text-white).
Prefer semantic HTML elements over JavaScript components (e.g., use <dialog> for modals, <details><summary> for disclosures).

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
**/*.{erb,html.erb}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{erb,html.erb}: Use icon helper for icons, never use lucide_icon directly.
Leverage Turbo frames for page sections over client-side solutions.
Pass data to Stimulus controllers via data-*-value attributes, not inline JavaScript.

**/*.{erb,html.erb}: Use ViewComponents when: element has complex logic or styling patterns, will be reused across multiple views/contexts, needs structured styling with variants/sizes, requires interactive behavior or Stimulus controllers, has configurable slots or complex APIs, or needs accessibility features or ARIA support
Use Partials when: element is primarily static HTML with minimal logic, used in only one or few specific contexts, is simple template content, doesn't need variants/sizes/complex configuration, or is more about content organization than reusable functionality
Prefer components over partials: if a component is available in app/components, use it; if not, look for a partial; if neither exists, decide between component or partial based on complexity and reusability criteria
Keep domain logic out of views: compute values like button classes, conditional logic, and data transformations in the component file, not the template file
Always use the declarative approach when integrating Stimulus controllers in views: the ERB template should declare what happens using data-* attributes, and the Stimulus controller should respond
Component controllers in app/components/ should only be used within their component templates; global controllers in app/javascript/controllers/ can be used across any view
Pass data from Rails to Stimulus using data-*-value attributes, not inline JavaScript

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
**/*.{erb,html.erb,rb}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{erb,html.erb,rb}: All user-facing strings must use localization (i18n) via the t() helper. Update locale files for each new or changed element.
Use server-side formatting for currencies, numbers, and dates. Do not format in JavaScript.

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
{app/components/**/*.html.erb,app/views/**/*.{erb,html.erb}}

📄 CodeRabbit inference engine (CLAUDE.md)

Keep domain logic out of view templates. Logic belongs in component files, not template files.

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
**/*.erb

📄 CodeRabbit inference engine (CLAUDE.md)

Run bundle exec erb_lint ./app/**/*.erb -a for ERB linting with auto-correct before pull requests.

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/general-rules.mdc)

**/*.{rb,erb}: Use Current.user for accessing the current user. Do NOT use current_user
Use Current.family for accessing the current family. Do NOT use current_family

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

**/*.html.erb: Prefer native HTML elements over JavaScript-based components. Use semantic HTML: for modals,

for disclosures
Leverage Turbo frames to break up pages instead of JavaScript-driven client-side solutions
Use the icon helper from application_helper.rb for icons. Never use lucide_icon helper directly

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
**/*.{rb,js,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, dates, and other values server-side, then pass to Stimulus controllers for display only

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
**/*.{rb,html.erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Use ActiveRecord validations for complex validations and business logic. Simple validations may be mirrored in ActiveRecord for form error handling convenience, but prioritize client-side form validation when possible

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
{app/views/**,app/helpers/**,app/javascript/controllers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

{app/views/**,app/helpers/**,app/javascript/controllers/**}: Reference maybe-design-system.css for base primitives, functional tokens, and component tokens before writing styles
Prefer using functional design system tokens (e.g., text-primary, bg-container, border-primary) from maybe-design-system.css instead of raw Tailwind color values

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
{app/views/**,app/helpers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • app/views/enable_banking_items/setup_accounts.html.erb
**/*.rb

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rb: Application supports two modes: 'managed' and 'self_hosted' via Rails.application.config.app_mode
Use Current.user and Current.family instead of current_user / current_family for authentication context
Optimize database queries with proper indexes to prevent N+1 queries using includes/joins

**/*.rb: Use Rails pluralization in i18n: t("transactions.count", count: @transactions.count).
Run bin/rubocop -f github -a for Ruby linting with auto-correct before pull requests.

**/*.rb: Ruby code should use 2-space indent, snake_case for methods and variables, and CamelCase for classes and modules, following Rails conventions for folders and file names
Prefer environment variables over hard-coded values for configuration

Files:

  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
app/models/**/*.rb

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/models/**/*.rb: Keep business logic in models using skinny controllers and fat models pattern
Store all monetary values in base currency (user's primary currency) and use Money objects for conversion
Use ActiveRecord validations for form convenience and complex business logic validations
Use Rails concerns and POROs for organizing business logic instead of services folder

app/models/**/*.rb: All monetary values stored in base currency (user's primary currency). Use Money objects for currency conversion and formatting.
Place business logic in app/models/ folder. Avoid creating app/services/ folder. Use Rails concerns and POROs for organization.
Models should answer questions about themselves using instance methods: use account.balance_series not AccountSeries.new(account).call.

Files:

  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
{app/models/**/*.rb,db/migrate/**/*.rb}

📄 CodeRabbit inference engine (CLAUDE.md)

Simple validations (null checks, unique indexes) should be in the database. ActiveRecord validations for form convenience (prefer client-side when possible). Complex validations and business logic in ActiveRecord.

Files:

  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
app/**/*.{rb,js,ts,jsx,tsx,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Code should be organized in app/ directory (Rails MVC, services, jobs, mailers, components), with JS in app/javascript/, and styles/assets in app/assets/ (Tailwind, images, fonts)

Files:

  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Run bin/brakeman security scan before major PRs to check for static analysis of common Rails issues

Files:

  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
🧠 Learnings (28)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Use Plaid integration for real-time bank account syncing via `PlaidItem`, `Sync` models, and background jobs for data updates.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-11-24T16:57:20.307Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-11-24T16:57:20.307Z
Learning: Applies to app/views/**/_*.html.erb : Place context-specific partials in relevant controller view directory (e.g., accounts/_account_sidebar_tabs.html.erb)

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-09-23T22:22:00.149Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:00.149Z
Learning: Applies to app/{views,components}/**/*.html.erb : In ERB views, wire Stimulus interactions declaratively using data-action to call controller methods

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/views/**/*.html.erb : Use ViewComponents when elements have complex logic/styling, are reused across contexts, need variants/slots/accessibility, or require Stimulus interactivity

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-11-24T16:57:20.307Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-11-24T16:57:20.307Z
Learning: Applies to **/*.{erb,html.erb} : Always use the declarative approach when integrating Stimulus controllers in views: the ERB template should declare what happens using data-* attributes, and the Stimulus controller should respond

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to config/locales/**/*.yml : Use hierarchical i18n keys by feature: `accounts.index.title`, `transactions.form.amount_label`. Use `t()` helper for all user-facing strings with interpolation for dynamic content.

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-11-24T16:56:13.394Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-11-24T16:56:13.394Z
Learning: Applies to **/*.html.erb : Prefer native HTML elements over JavaScript-based components. Use semantic HTML: <dialog> for modals, <details><summary> for disclosures

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-11-24T16:57:20.307Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-11-24T16:57:20.307Z
Learning: Applies to **/*.{erb,html.erb} : Use ViewComponents when: element has complex logic or styling patterns, will be reused across multiple views/contexts, needs structured styling with variants/sizes, requires interactive behavior or Stimulus controllers, has configurable slots or complex APIs, or needs accessibility features or ARIA support

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Pass data from Rails to Stimulus using data-*-value attributes, not inline JavaScript

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to **/*.{erb,html.erb,rb} : All user-facing strings must use localization (i18n) via the `t()` helper. Update locale files for each new or changed element.

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-10-07T09:51:02.265Z
Learnt from: jjmata
Repo: we-promise/sure PR: 189
File: app/views/password_resets/new.html.erb:14-14
Timestamp: 2025-10-07T09:51:02.265Z
Learning: In the we-promise/sure repository, use i18n methods (like `t()` or `I18n.t`) for all user-facing strings to maintain proper internationalization infrastructure. While translations should be provided, extensive localization work is not a priority at this stage of development.

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-11-24T16:54:59.185Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T16:54:59.185Z
Learning: Applies to app/views/**/*.erb : Use server-side formatting for currencies, numbers, and dates instead of client-side

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-10-01T11:40:36.711Z
Learnt from: jjmata
Repo: we-promise/sure PR: 173
File: app/mailers/email_confirmation_mailer.rb:9-9
Timestamp: 2025-10-01T11:40:36.711Z
Learning: In the we-promise/sure repository, i18n (internationalization) is used throughout the application for translations. When making branding configurable (e.g., product names), it's appropriate to add parameters like `product: product_name` to existing `t()` translation calls rather than replacing them with hardcoded strings.

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to **/*.rb : Use Rails pluralization in i18n: `t("transactions.count", count: transactions.count)`.

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-11-24T16:56:13.394Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-11-24T16:56:13.394Z
Learning: Applies to **/*.{rb,js,erb} : Format currencies, numbers, dates, and other values server-side, then pass to Stimulus controllers for display only

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/views/enable_banking_items/setup_accounts.html.erb
  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
📚 Learning: 2025-11-24T16:56:30.657Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-11-24T16:56:30.657Z
Learning: Account model is a Rails delegated type with subtypes: Depository, Investment, Crypto, Property, Vehicle, OtherAsset (assets) and CreditCard, Loan, OtherLiability (liabilities)

Applied to files:

  • app/models/enable_banking_account.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Use Plaid integration for real-time bank account syncing via `PlaidItem`, `Sync` models, and background jobs for data updates.

Applied to files:

  • app/models/enable_banking_account.rb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • app/models/enable_banking_account.rb
📚 Learning: 2025-11-24T16:56:30.657Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-11-24T16:56:30.657Z
Learning: Entry model is a Rails delegated type with three subtypes: Valuation (absolute account value), Transaction (balance modification), and Trade (buy/sell for investment accounts)

Applied to files:

  • app/models/enable_banking_account.rb
📚 Learning: 2025-11-24T16:56:30.657Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-11-24T16:56:30.657Z
Learning: Syncable models (Account, PlaidItem, Family) must implement sync operations tracked via Sync records that log status, errors, and maintain an audit trail

Applied to files:

  • app/models/enable_banking_account.rb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to app/models/**/*.rb : All monetary values stored in base currency (user's primary currency). Use `Money` objects for currency conversion and formatting.

Applied to files:

  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
📚 Learning: 2025-11-24T16:54:59.185Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T16:54:59.185Z
Learning: Applies to app/models/**/*.rb : Store all monetary values in base currency (user's primary currency) and use Money objects for conversion

Applied to files:

  • app/models/enable_banking_account.rb
  • app/models/enable_banking_entry/processor.rb
📚 Learning: 2025-11-24T16:56:30.657Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-11-24T16:56:30.657Z
Learning: Account sync must perform transfer auto-matching, calculate daily balance records from start_date to Date.current, calculate holdings, and optionally enrich transaction data

Applied to files:

  • app/models/enable_banking_entry/processor.rb
🧬 Code graph analysis (1)
app/models/enable_banking_entry/processor.rb (2)
app/models/enable_banking_item/importer.rb (1)
  • initialize (4-7)
app/models/account/provider_import_adapter.rb (1)
  • import_transaction (21-82)
🔇 Additional comments (7)
app/models/enable_banking_account.rb (2)

80-86: LGTM!

The method correctly uses save! to match the bang method naming convention, ensuring failures are raised. The two-step approach with assign_attributes is fine here.


112-114: I'll verify the review comment by investigating the log_invalid_currency method and how it's called in the codebase.
<function_calls>


#!/bin/bash

Search for CurrencyNormalizable concern and log_invalid_currency calls

echo "=== Searching for CurrencyNormalizable concern ==="
fd -e rb | xargs rg -l "CurrencyNormalizable" | head -20

echo -e "\n=== Searching for log_invalid_currency method calls ==="
rg -n "log_invalid_currency" --type ruby -A 2 -B 2

echo -e "\n=== Checking enable_banking_account.rb for currency setup ==="
cat -n app/models/enable_banking_account.rb | grep -A 5 -B 5 "currency|CurrencyNormalizable"


</function_calls>

app/views/enable_banking_items/setup_accounts.html.erb (4)

9-19: Declarative Stimulus integration is correctly implemented.

The form properly uses data attributes to wire up the loading-button controller with submit->loading-button#showLoading action. The turbo_frame: "_top" correctly ensures the response replaces the full page.


48-54: Date field implementation follows best practices.

Server-side date handling with proper min/max constraints is correct. The default of 3 months with a maximum of 1 year aligns with reasonable sync limits.


80-97: Stimulus controller integration is well-structured.

The account-type-selector controller is properly scoped per account using data-account-type-selector-account-id-value. The declarative action binding (change->account-type-selector#updateSubtype) and target (subtypeContainer) follow conventions.


103-117: Button and link components properly configured.

The DS::Button and DS::Link components are used correctly with appropriate variants. The Stimulus target binding (loading_button_target: "button") properly connects to the loading-button controller.

app/models/enable_banking_entry/processor.rb (1)

18-45: Transaction import flow and error handling look solid

The guard for missing account, the single entry point via import_adapter.import_transaction, and the tiered rescue logic (argument vs. AR vs. generic errors) are all appropriate and consistent with the Account::ProviderImportAdapter contract. No changes needed here.

Comment on lines +1 to +12
class EnableBankingAccount < ApplicationRecord
include CurrencyNormalizable

belongs_to :enable_banking_item

# New association through account_providers
has_one :account_provider, as: :provider, dependent: :destroy
has_one :account, through: :account_provider, source: :account
has_one :linked_account, through: :account_provider, source: :account

validates :name, :currency, presence: true
validates :uid, presence: true, uniqueness: { scope: :enable_banking_item_id }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Script executed:

#!/bin/bash
# Search for PlaidAccount model and check for has_balance validation
fd -t f "plaid_account.rb" -x cat -n {}

Repository: we-promise/sure


<shell_result>
Repository: we-promise/sure
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository we-promise/sure: Clone operation failed
</shell_result>


Script executed:

[skip_cloning]
# Search for documentation or references about has_balance validation in banking integrations
curl -s https://api.github.com/repos/we-promise/sure/contents/app/models/plaid_account.rb 2>/dev/null || echo "API call failed"

Repository: we-promise/sure


<shell_result>
Repository: we-promise/sure
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository we-promise/sure: Clone operation failed
</shell_result>


Premature approval without complete verification; potential missing balance validation.

The review comment contains a structural contradiction: it includes while simultaneously requesting verification via. More critically, the learnings confirm that EnableBankingAccount should implement a has_balance validation method (referenced at lines 9-20), but this validation is absent from the 12-line snippet shown.

The snippet displays only the class header and basic validations (name, currency, uid) but does not show the has_balance validation mentioned in the learnings. Since the learnings reference lines 9-20 of this same file and the snippet only shows lines 1-12, the validation may exist elsewhere in the model, or it may be missing entirely.

The review should not approve the code until:

  1. Verification confirms whether has_balance validation is present in the full model
  2. If missing, this validation must be added to match the Plaid integration pattern documented in the learnings

Comment on lines +52 to +60
raw_account_id = snapshot[:account_id]
account_id_data = if raw_account_id.is_a?(Hash)
raw_account_id
elsif raw_account_id.is_a?(Array) && raw_account_id.first.is_a?(Hash)
# If it's an array of hashes, find the one with iban
raw_account_id.find { |item| item[:iban].present? } || {}
else
{}
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract duplicated account_id parsing logic.

The logic for extracting account_id_data from raw_account_id (lines 52-60) is duplicated in build_account_name (lines 92-99). Extract this to a private helper method to adhere to DRY and prevent future inconsistencies.

+ private
+
+   def extract_account_id_data(snapshot)
+     raw_account_id = snapshot[:account_id]
+     if raw_account_id.is_a?(Hash)
+       raw_account_id
+     elsif raw_account_id.is_a?(Array) && raw_account_id.first.is_a?(Hash)
+       raw_account_id.find { |item| item[:iban].present? } || {}
+     else
+       {}
+     end
+   end

Then use it in both places:

# In upsert_enable_banking_snapshot!
account_id_data = extract_account_id_data(snapshot)

# In build_account_name
account_id_data = extract_account_id_data(snapshot)

Also applies to: 92-99

🤖 Prompt for AI Agents
In app/models/enable_banking_account.rb around lines 52-60 and 92-99, the logic
that extracts account_id_data from snapshot[:account_id] is duplicated; extract
this into a private helper method (e.g. extract_account_id_data(snapshot)) that
returns the hash when snapshot[:account_id] is a Hash, returns the first hash
containing an :iban when it's an Array, or returns {} otherwise, then replace
the existing duplicated blocks in upsert_enable_banking_snapshot! and
build_account_name to call this helper; add the new private method near other
private methods and ensure tests/usage still pass.

Comment on lines +73 to +75
<% if enable_banking_account.current_balance.present? %>
<p>Balance: <%= number_to_currency(enable_banking_account.current_balance, unit: enable_banking_account.currency) %></p>
<% end %>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect number_to_currency usage with currency code.

The unit: option expects a currency symbol (e.g., "$", "€"), not a currency code (e.g., "EUR", "USD"). This will display "Balance: EUR100.00" instead of "Balance: €100.00".

Per coding guidelines, monetary values should use Money objects for proper currency conversion and formatting. Consider using a helper that handles currency codes correctly.

-                    <p>Balance: <%= number_to_currency(enable_banking_account.current_balance, unit: enable_banking_account.currency) %></p>
+                    <p>Balance: <%= format_money(Money.new(enable_banking_account.current_balance * 100, enable_banking_account.currency)) %></p>

Or verify how other banking integrations format balances in views:

#!/bin/bash
# Check how PlaidAccount balances are formatted in views
rg -n "current_balance|format_money|number_to_currency" --type erb -A2 -B2
🤖 Prompt for AI Agents
In app/views/enable_banking_items/setup_accounts.html.erb around lines 73–75,
number_to_currency is being passed a currency code (e.g., "USD"/"EUR") as the
unit which prints "EUR100.00" instead of a symbol; change the view to format
balances via a Money-aware helper or a Money object so the correct currency
symbol and formatting are used. Replace the direct number_to_currency call with
the project’s money formatting helper (or construct a Money.new/monetize value
with the account’s cents and currency code and call its formatting helper),
ensuring you pass the amount in smallest units (cents) and use the helper that
resolves the currency code to the proper symbol and locale-aware formatting.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (5)
app/controllers/enable_banking_items_controller.rb (5)

176-186: Remove Turbo frame data from redirect_to; it has no effect.

redirect_to select_bank_enable_banking_item_path(new_item), data: { turbo_frame: "modal" } won’t target a Turbo frame—data-turbo-frame only affects links/forms in the browser, not server-side redirects. Either:

  • Drop the data option and rely on the original link/form having data-turbo-frame="modal", or
  • Respond with a Turbo Stream that updates the modal frame instead of redirecting.

Minimal fix:

-    redirect_to select_bank_enable_banking_item_path(new_item), data: { turbo_frame: "modal" }
+    redirect_to select_bank_enable_banking_item_path(new_item)

109-132: Validate external redirect URL before calling redirect_to (Brakeman warning).

redirect_url here comes from the Enable Banking API and is used with redirect_to redirect_url, allow_other_host: true, which Brakeman correctly flags as an unprotected redirect. Add a strict allowlist + scheme check and reuse it for both authorize and reauthorize.

Example:

+  ALLOWED_REDIRECT_HOSTS = %w[enablebanking.com sandbox.enablebanking.com].freeze
+
+  private
+
+    def validate_redirect_url!(url)
+      uri = URI.parse(url)
+      unless uri.is_a?(URI::HTTP) && ALLOWED_REDIRECT_HOSTS.any? { |host| uri.host&.end_with?(host) }
+        Rails.logger.warn("Rejected Enable Banking redirect URL: #{url}")
+        raise Provider::EnableBanking::EnableBankingError.new("Invalid redirect URL", :invalid_redirect)
+      end
+      url
+    rescue URI::InvalidURIError
+      raise Provider::EnableBanking::EnableBankingError.new("Invalid redirect URL", :invalid_redirect)
+    end
-      redirect_to redirect_url, allow_other_host: true
+      redirect_to validate_redirect_url!(redirect_url), allow_other_host: true

Also apply the same helper in reauthorize.


188-202: Apply redirect URL validation in reauthorize as well.

Same untrusted redirect_url pattern as in authorize; make sure the validate_redirect_url! helper (or equivalent) is used here too so both code paths satisfy Brakeman and your security posture.

-      redirect_to redirect_url, allow_other_host: true
+      redirect_to validate_redirect_url!(redirect_url), allow_other_host: true

204-263: Multi-connection semantics and data consistency in link_accounts.

Two related points:

  1. Picking the item with .first is ambiguous with multiple connections.
    enable_banking_item = Current.family.enable_banking_items.where.not(session_id: nil).first becomes non-deterministic once there are multiple active sessions, which undercuts the “multiple connections” goal. Prefer tying this action to a specific item (e.g., member route + before_action :set_enable_banking_item) or passing an explicit enable_banking_item_id param.

    Example (if link_accounts is made a member action):

  • before_action :set_enable_banking_item, only: [ :update, :destroy, :sync, :select_bank, :authorize, :reauthorize, :setup_accounts, :complete_account_setup, :new_connection ]
  • before_action :set_enable_banking_item, only: [ :update, :destroy, :sync, :select_bank, :authorize, :reauthorize, :setup_accounts, :complete_account_setup, :new_connection, :link_accounts ]
    
    ```diff
    
  • enable_banking_item = Current.family.enable_banking_items.where.not(session_id: nil).first
  • unless enable_banking_item.present?
  • unless @enable_banking_item.session_id.present?
    redirect_to settings_providers_path, alert: t(".no_session", default: "No active Enable Banking connection. Please connect a bank first.")
    return
    end

2. **Wrap account creation/linking in a transaction.**  
If `Account.create_and_sync` or `AccountProvider.create!` fails mid-loop, you’ll end up with partially-created accounts and broken provider links. Wrap `selected_uids.each` in an `ActiveRecord::Base.transaction` and make sure failures raise so the whole operation rolls back.

Example:

```diff
-    selected_uids.each do |uid|
+    ActiveRecord::Base.transaction do
+      selected_uids.each do |uid|
      # existing loop body...
-      created_accounts << account
-    end
+        created_accounts << account
+      end
+    end

309-365: Wrap account setup loop in a transaction to avoid partial state.

complete_account_setup creates accounts and AccountProvider links inside a plain account_types.each loop. If Account.create_from_enable_banking_account or AccountProvider.create! raises for any item, some accounts will be created while others are not, leaving inconsistent state.

Wrap the loop in a transaction and ensure failures raise:

-    account_types.each do |enable_banking_account_id, selected_type|
+    ActiveRecord::Base.transaction do
+      account_types.each do |enable_banking_account_id, selected_type|
         # existing loop body...
-      created_count += 1
-    end
+        created_count += 1
+      end
+    end

If create_from_enable_banking_account can return invalid records without raising, consider switching to a bang variant or manually raising on failure so the transaction rolls back.

🧹 Nitpick comments (2)
app/controllers/enable_banking_items_controller.rb (2)

39-68: Create/update flows are consistent but could be DRY’d up.

create and update duplicate the same turbo_stream panel refresh and error-handling logic. Consider extracting a small private helper (e.g. render_providers_panel(success: ..., error_message: ...)) to keep these actions lean and reduce maintenance surface.


135-174: State handling is functional; consider opaque tokens for defense in depth.

Using state as the raw enable_banking_item.id and scoping lookup through Current.family is workable, but it does expose predictable IDs in the OAuth state parameter. If you want to tighten this further, consider issuing a random, opaque state token tied to the item (or session) and validating that instead of a bare id.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 532954b and 00fa322.

📒 Files selected for processing (2)
  • app/controllers/enable_banking_items_controller.rb (1 hunks)
  • app/views/enable_banking_items/setup_accounts.html.erb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/views/enable_banking_items/setup_accounts.html.erb
🧰 Additional context used
📓 Path-based instructions (9)
**/*.rb

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rb: Application supports two modes: 'managed' and 'self_hosted' via Rails.application.config.app_mode
Use Current.user and Current.family instead of current_user / current_family for authentication context
Optimize database queries with proper indexes to prevent N+1 queries using includes/joins

**/*.rb: Use Rails pluralization in i18n: t("transactions.count", count: @transactions.count).
Run bin/rubocop -f github -a for Ruby linting with auto-correct before pull requests.

**/*.rb: Ruby code should use 2-space indent, snake_case for methods and variables, and CamelCase for classes and modules, following Rails conventions for folders and file names
Prefer environment variables over hard-coded values for configuration

Files:

  • app/controllers/enable_banking_items_controller.rb
app/controllers/**/*.rb

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Rails strong parameters and CSRF protection throughout the application

app/controllers/**/*.rb: Use strong parameters and CSRF protection throughout the application.
Use query params for state over localStorage/sessions.

Files:

  • app/controllers/enable_banking_items_controller.rb
**/*.{rb,erb,html.erb}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{rb,erb,html.erb}: Use Current.user for the current user context in controllers and views. Do NOT use current_user.
Use Current.family for the current family context. Do NOT use current_family.

Files:

  • app/controllers/enable_banking_items_controller.rb
**/*.{erb,html.erb,rb}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{erb,html.erb,rb}: All user-facing strings must use localization (i18n) via the t() helper. Update locale files for each new or changed element.
Use server-side formatting for currencies, numbers, and dates. Do not format in JavaScript.

Files:

  • app/controllers/enable_banking_items_controller.rb
**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/general-rules.mdc)

**/*.{rb,erb}: Use Current.user for accessing the current user. Do NOT use current_user
Use Current.family for accessing the current family. Do NOT use current_family

Files:

  • app/controllers/enable_banking_items_controller.rb
**/*.{rb,js,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, dates, and other values server-side, then pass to Stimulus controllers for display only

Files:

  • app/controllers/enable_banking_items_controller.rb
**/*.{rb,html.erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Use ActiveRecord validations for complex validations and business logic. Simple validations may be mirrored in ActiveRecord for form error handling convenience, but prioritize client-side form validation when possible

Files:

  • app/controllers/enable_banking_items_controller.rb
app/**/*.{rb,js,ts,jsx,tsx,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Code should be organized in app/ directory (Rails MVC, services, jobs, mailers, components), with JS in app/javascript/, and styles/assets in app/assets/ (Tailwind, images, fonts)

Files:

  • app/controllers/enable_banking_items_controller.rb
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Run bin/brakeman security scan before major PRs to check for static analysis of common Rails issues

Files:

  • app/controllers/enable_banking_items_controller.rb
🧠 Learnings (13)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Use Plaid integration for real-time bank account syncing via `PlaidItem`, `Sync` models, and background jobs for data updates.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Use Plaid integration for real-time bank account syncing via `PlaidItem`, `Sync` models, and background jobs for data updates.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to app/controllers/**/*.rb : Use strong parameters and CSRF protection throughout the application.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:54:59.185Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T16:54:59.185Z
Learning: Applies to app/views/**/*.erb : Use Turbo Frames for page sections instead of client-side solutions

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to **/*.{erb,html.erb} : Leverage Turbo frames for page sections over client-side solutions.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:56:13.394Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-11-24T16:56:13.394Z
Learning: Applies to **/*.html.erb : Leverage Turbo frames to break up pages instead of JavaScript-driven client-side solutions

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:54:59.185Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T16:54:59.185Z
Learning: Applies to {app/**/*.{erb,html},app/javascript/**/*.js} : Prefer Hotwire-first approach: use Turbo + Stimulus for reactive UI without heavy JavaScript

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-11-24T16:55:43.046Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:55:43.046Z
Learning: Applies to **/*.{erb,html.erb} : Pass data to Stimulus controllers via `data-*-value` attributes, not inline JavaScript.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • app/controllers/enable_banking_items_controller.rb
🪛 GitHub Actions: Pull Request
app/controllers/enable_banking_items_controller.rb

[warning] 124-124: Brakeman warning: Possible unprotected redirect. Code: redirect_to(Current.family.enable_banking_items.find(params[:id]).start_authorization(:aspsp_name => params[:aspsp_name], :redirect_url => enable_banking_callback_url, :state => Current.family.enable_banking_items.find(params[:id]).id), :allow_other_host => true)


[warning] 197-197: Brakeman warning: Possible unprotected redirect. Code: redirect_to(Current.family.enable_banking_items.find(params[:id]).start_authorization(:aspsp_name => Current.family.enable_banking_items.find(params[:id]).aspsp_name, :redirect_url => enable_banking_callback_url, :state => Current.family.enable_banking_items.find(params[:id]).id), :allow_other_host => true)

🔇 Additional comments (3)
app/controllers/enable_banking_items_controller.rb (3)

76-85: Sync trigger behavior looks correct.

Guarding on syncing? before calling sync_later keeps the action idempotent-ish and the respond_to block covers HTML/JSON cleanly.


87-106: Bank selection flow and error handling look solid.

credentials_configured? guard, API error rescue with logging, and always assigning @aspsps (falling back to []) make the modal predictable for the view.


369-390: Strong params and callback URL helper look aligned with conventions.

enable_banking_item_params permits only the expected attributes, and enable_banking_callback_url cleanly separates production vs development (via DEV_WEBHOOKS_URL), which should play well with tools like ngrok.

Comment on lines +5 to +8
def create
@enable_banking_item = Current.family.enable_banking_items.build(enable_banking_item_params)
@enable_banking_item.name ||= "Enable Banking Connection"

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Localize default connection name instead of hard‑coding.

Line 7 and the new_connection action (line 179) set a user-facing name "Enable Banking Connection" directly. This should go through I18n to follow project guidelines and allow translation.

Example:

-    @enable_banking_item.name ||= "Enable Banking Connection"
+    @enable_banking_item.name ||= t(".default_name", default: "Enable Banking Connection")
-      name: "Enable Banking Connection",
+      name: t(".default_name", default: "Enable Banking Connection"),

Also applies to: 176-183

Comment on lines +265 to +306
# Show setup accounts modal
def setup_accounts
@enable_banking_accounts = @enable_banking_item.enable_banking_accounts
.left_joins(:account_provider)
.where(account_providers: { id: nil })

@account_type_options = [
[ "Skip this account", "skip" ],
[ "Checking or Savings Account", "Depository" ],
[ "Credit Card", "CreditCard" ],
[ "Investment Account", "Investment" ],
[ "Loan or Mortgage", "Loan" ],
[ "Other Asset", "OtherAsset" ]
]

@subtype_options = {
"Depository" => {
label: "Account Subtype:",
options: Depository::SUBTYPES.map { |k, v| [ v[:long], k ] }
},
"CreditCard" => {
label: "",
options: [],
message: "Credit cards will be automatically set up as credit card accounts."
},
"Investment" => {
label: "Investment Type:",
options: Investment::SUBTYPES.map { |k, v| [ v[:long], k ] }
},
"Loan" => {
label: "Loan Type:",
options: Loan::SUBTYPES.map { |k, v| [ v[:long], k ] }
},
"OtherAsset" => {
label: nil,
options: [],
message: "Other assets will be set up as general assets."
}
}

render layout: false
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Localize all account type/subtype labels and messages.

@account_type_options and @subtype_options currently embed user-facing English strings directly. These should go through I18n so they can be translated and adjusted consistently.

Example sketch:

-    @account_type_options = [
-      [ "Skip this account", "skip" ],
-      [ "Checking or Savings Account", "Depository" ],
-      [ "Credit Card", "CreditCard" ],
-      [ "Investment Account", "Investment" ],
-      [ "Loan or Mortgage", "Loan" ],
-      [ "Other Asset", "OtherAsset" ]
-    ]
+    @account_type_options = [
+      [ t(".account_types.skip",       default: "Skip this account"),           "skip" ],
+      [ t(".account_types.depository", default: "Checking or Savings Account"), "Depository" ],
+      [ t(".account_types.credit",     default: "Credit Card"),                 "CreditCard" ],
+      [ t(".account_types.investment", default: "Investment Account"),          "Investment" ],
+      [ t(".account_types.loan",       default: "Loan or Mortgage"),            "Loan" ],
+      [ t(".account_types.other",      default: "Other Asset"),                 "OtherAsset" ]
+    ]

Similarly for label and message entries in @subtype_options.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/controllers/enable_banking_items_controller.rb around lines 265 to 306,
the account type and subtype labels/messages are hard-coded in English; replace
these literal strings with I18n lookups (e.g.
I18n.t('enable_banking.account_types.skip') etc.) for every user-facing string
in @account_type_options and every label/message and displayed option text
inside @subtype_options, and update the option value arrays to map translated
display text to the existing keys; ensure keys are namespaced and add
corresponding entries to the locale yml files so all labels, option texts and
messages are localizable.

@sokie sokie marked this pull request as draft November 26, 2025 13:48
@jjmata jjmata added this to the v0.6.6 milestone Nov 26, 2025
value: @enable_banking_item.sync_start_date || 3.months.ago.to_date,
min: 1.year.ago.to_date,
max: Date.current,
class: "w-full max-w-xs",
Copy link
Collaborator

Choose a reason for hiding this comment

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

I noticed that this field is not properly displayed in dark mode. I've fixed it by using the following classes here: w-full max-w-xs rounded-md border border-primary px-3 py-2 text-sm bg-container-inset text-primary", which are the same used somewhere else in another date field.

label: "Client Certificate (with Private Key)",
placeholder: "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
rows: 6,
class: "font-mono text-xs",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also this one is not visible in dark mode. I've fixed it by using the following classes: form-field__input font-mono text-xs

["Sweden (SE)", "SE"],
["United Kingdom (GB)", "GB"]
], enable_banking_item.country_code),
{ include_blank: "Select country..." },
Copy link
Collaborator

Choose a reason for hiding this comment

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

The label wasn’t visible on this field so I suppose the property label: true should be added. Setting it like this: { label: true, include_blank: “Select country…” } works perfectly.

["United Kingdom (GB)", "GB"]
], enable_banking_item.country_code),
{ include_blank: "Select country..." },
{ label: "Country", class: "form-select", disabled: has_authenticated_connections && !is_new_record } %>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Using the form-field__input class instead of form-select ensures the field is displayed correctly and aligned with the rest of the elements.

iban = account_id_data[:iban] || snapshot[:iban]

if snapshot[:name].present?
snapshot[:name]
Copy link
Collaborator

Choose a reason for hiding this comment

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

The name field actually stores the account holder’s name while the “account name” is stored in product at least in my real account. I’m not sure whether it’s worth using the value of product as the name or a combination of both. It certainly provides more information about the account than just the holder’s name; this is at least what I’ve implemented.

Copy link
Collaborator Author

@sokie sokie Nov 27, 2025

Choose a reason for hiding this comment

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

on the sandbox, this is the only available field in the api. annoying

    "name": "Rabobank Nederland B.V.",
    "usage": null,
    "details": null,
    "product": null,

@alessiocappa
Copy link
Collaborator

@sokie, thanks a lot for this fantastic work! I really appreciate it! 🙏🏻

I quickly tested it using a fake account and my real one and everything seems to be working well so far. I also liked the extra setup step where you can confirm or update the mapping provided by Enable Banking. This is something I wanted to do too and it’s great to see it in your PR.

I’ve left a few comments about data mapping and some small UI fixes. Let me know if you agree with those suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants