-
Notifications
You must be signed in to change notification settings - Fork 94
Simplefin enhancements v2 #267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplefin enhancements v2 #267
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughThis PR significantly enhances the SimpleFIN sync pipeline with improved account linking workflows, balances-only syncs, per-account error resilience, encrypted access URLs, transaction metadata enrichment, and comprehensive UI updates. It refactors controllers with Turbo Streams support, introduces provider-linking logic, expands holdings and transaction processing, adds database migrations for schema changes, and includes new debugging and backfill tasks. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as User/UI
participant Ctrl as SimplefinItemsController
participant Importer as SimplefinItem::Importer
participant Provider as Provider API
participant DB as Database
participant Job as BalancesOnlyJob
Note over UI,Job: Balances-Only Sync Flow (New)
UI->>Ctrl: POST /simplefin_items/:id/balances
Ctrl->>Ctrl: Create Sync with balances_only flag
Ctrl->>Job: Enqueue BalancesOnlyJob
Ctrl->>UI: Return sync_id (JSON/Redirect)
Job->>Importer: import_balances_only
Importer->>Provider: Discover accounts (minimal)
Provider-->>Importer: Account list
Importer->>Importer: Snapshot balances for linked accounts only
Importer->>DB: Upsert minimal account + balance update
Importer->>DB: Persist sync stats
Job->>DB: Update last_synced_at
Job->>UI: Broadcast Turbo Streams update
Note over UI,Job: Full Sync Flow (Enhanced)
UI->>Ctrl: POST /simplefin_items/:id (traditional)
Ctrl->>Importer: import_latest_simplefin_data(sync:)
Importer->>Importer: Per-account error handling
Importer->>DB: Classify & track errors
Importer-->>Ctrl: Partial success + stats
Ctrl->>UI: Render with error summary
Note over UI,Job: Manual Account Linking Flow (New)
UI->>Ctrl: GET select_existing_account
Ctrl->>Ctrl: Filter available SFAs
Ctrl->>UI: Render modal with account list
UI->>Ctrl: POST link_existing_account
Ctrl->>DB: Create provider link (AccountProvider)
Ctrl->>Importer: Recompute relink candidates
Ctrl->>UI: Broadcast Turbo Streams (add account row)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Areas requiring extra attention:
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 15
🧹 Nitpick comments (18)
lib/tasks/simplefin_debug.rake (5)
16-18: Use SQL-side, case-insensitive search for account match (safer, faster).Avoid Ruby
.findscans and substring checks; sanitize and delegate to DB.- sfa = item.simplefin_accounts.order(updated_at: :desc).find do |acc| - acc.name.to_s.downcase.include?(args[:account_name].to_s.downcase) - end + pattern = "%#{ActiveRecord::Base.sanitize_sql_like(args[:account_name].to_s.downcase)}%" + sfa = item.simplefin_accounts + .where("LOWER(name) LIKE ?", pattern) + .order(updated_at: :desc) + .first
71-73: Same here: DB search instead of Ruby scan for Account.This avoids loading all accounts and mismatches.
- acct = Account.order(updated_at: :desc).find do |a| - a.name.to_s.downcase.include?(args[:account_name].to_s.downcase) - end + pattern = "%#{ActiveRecord::Base.sanitize_sql_like(args[:account_name].to_s.downcase)}%" + acct = Account.where("LOWER(name) LIKE ?", pattern) + .order(updated_at: :desc) + .first
83-94: Preload entryable to avoid N+1 when accessing was_merged.Accessing
e.entryableper row will issue extra queries without preload.- entries = acct.entries.where(entryable_type: "Transaction").order(date: :desc).limit(limit) + entries = acct.entries + .where(entryable_type: "Transaction") + .includes(:entryable) + .order(date: :desc) + .limit(limit)
45-55: PII caution: payee/description/memo in plaintext JSON.These can contain sensitive data. Consider an ENV flag to redact or skip these fields for safer logs, or ensure guidance that this task is dev-only.
Would you like a small redaction helper (e.g., mask middle of strings unless ENV["VERBOSE"]=="1")?
59-61: Avoid leaking backtraces outside dev.Printing backtraces can expose internals. Gate by environment or ENV flag.
- rescue => e - puts({ error: e.class.name, message: e.message, backtrace: e.backtrace&.take(3) }.to_json) + rescue => e + payload = { error: e.class.name, message: e.message } + payload[:backtrace] = e.backtrace&.take(3) if Rails.env.development? || ENV["DEBUG"] == "1" + puts(payload.to_json) exit 1 endAlso applies to: 97-99
lib/tasks/simplefin_backfill.rake (7)
42-44: Use zone-awaretodayfor consistency.Prefer
Time.zone.todayoverDate.todayto align withActiveSupporttime math.- window_start = days_i.days.ago.to_date - window_end = Date.today + window_start = days_i.days.ago.to_date + window_end = Time.zone.today
88-114: Consolidate date parsing; handle ms epochs.Current parsing is duplicated and assumes seconds. Add a helper to normalize String/Numeric/Date/Time and auto-downgrade millisecond epochs.
- posted_d = - case posted - when String - Date.parse(posted) rescue nil - when Numeric - Time.zone.at(posted).to_date rescue nil - when Date - posted - when Time, DateTime - posted.to_date - else - nil - end - trans_d = - case trans - when String - Date.parse(trans) rescue nil - when Numeric - Time.zone.at(trans).to_date rescue nil - when Date - trans - when Time, DateTime - trans.to_date - else - nil - end + to_date = ->(val) do + case val + when String + Date.parse(val) rescue nil + when Numeric + ts = val.to_i + ts = ts > 10**11 ? ts / 1000 : ts # ms → s + Time.zone.at(ts).to_date rescue nil + when Date + val + when Time, DateTime + val.to_date + else + nil + end + end + posted_d = to_date.call(posted) + trans_d = to_date.call(trans)
145-146: Avoidsendon private methods; expose a public matcher API.Relying on
processor.send(:name/:amount)is brittle. Prefer a public method (e.g.,processor.matching_attrs) returning{ name:, amount:, date: }, then use it in both dry-run and write paths.Would you add such a method to
SimplefinEntry::Processorand update the call here?
156-159: Add context to error logs.Include
sfa_id,account_id, andtx_idfor triage; optionally gate backtraces behind ENV.- rescue => e - total_errors += 1 - puts({ error: e.class.name, message: e.message }.to_json) + rescue => e + total_errors += 1 + puts({ error: e.class.name, message: e.message, sfa_id: sfa.id, account_id: acct.id, tx_id: t[:id] }.to_json) + puts({ backtrace: e.backtrace.first(3) }.to_json) if ENV["VERBOSE_ERRORS"] == "1"
124-131: Force semantics—clarify overwrite behavior.
forceproceeds even whenextra_hashis nil but ultimately counts as skipped if no extra is produced. If intent is “overwrite existing extra regardless,” add a branch to write/merge an empty"simplefin"hash or explicitly bypass overwrite when no metadata is derivable.
168-176: Cleanup patterns: trim parts; optional case-insensitive match.Whitespace around
|splits isn’t trimmed; also considerILIKEfor Postgres.- patterns = pattern.split("|") + patterns = pattern.split("|").map { |p| p.strip } - like_sql = patterns.map { |p| "external_id LIKE ?" }.join(" OR ") + like_sql = patterns.map { |_p| "external_id LIKE ?" }.join(" OR ")If your DB is Postgres and you want case-insensitive matches:
like_sql = patterns.map { |_p| "external_id ILIKE ?" }.join(" OR ")Also applies to: 190-196
20-24: Operational note: backfills can triggerAccount#sync_dataside effects.Ensure bulk writes won’t thrash sync pipelines. Consider batching (args: LIMIT/BATCH_SIZE), pausing auto-sync callbacks, or scheduling during low traffic.
Based on learnings
Also applies to: 124-131, 149-155
config/routes.rb (1)
5-13: Consolidate the SimpleFin routes into the existing resources block.Defining
resources :simplefin_itemstwice generates duplicate REST routes for the same controller, which makes the routing table noisier and harder to maintain. Fold the new member actions into the existing block down at Line 277 so everything lives in one definition.- resources :simplefin_items do - member do - get :errors - post :balances - get :relink - post :apply_relink - get :prelink - end - end @@ resources :simplefin_items, only: %i[index new create show edit update destroy] do member do post :sync get :setup_accounts post :complete_account_setup + get :errors + post :balances + get :relink + post :apply_relink + get :prelink end endtest/models/simplefin_item_dedupe_test.rb (1)
15-54: Strengthen assertion and consider testing data migration.The test correctly sets up duplicate SimplefinAccounts and verifies deduplication, but there are opportunities for improvement:
- Line 49: The assertion
assert_operator stats[:deleted_simplefin_accounts], :>=, 1is weaker than it should be. Since you created exactly 2 duplicate SFAs (lines 17-32), you should assert exactly 1 deletion:- assert_operator stats[:deleted_simplefin_accounts], :>=, 1 + assert_equal 1, stats[:deleted_simplefin_accounts], "Expected exactly 1 duplicate SFA to be deleted"
Data migration coverage: The
dedup_simplefin_accounts!method (shown in relevant snippets) also migrates entries and holdings from duplicate accounts. Consider adding assertions to verify:
stats[:moved_entries]stats[:moved_holdings]stats[:merged_accounts]Or create a separate test that exercises the data migration path by adding entries/holdings to the duplicate account before deduplication.
The current test provides basic coverage but could be more comprehensive for a critical deduplication operation.
test/views/transactions/merged_badge_view_test.rb (2)
14-22: Consider extracting transaction creation to fixtures.Creating the
transactionandentryinline (lines 14-22) works for this simple case, but as the guidelines state: "For tests needing many records, use Rails helpers to construct data and inline the creation in the test."Since this is a minimal setup (2 records), inline creation is acceptable. However, if multiple tests need merged transactions, consider adding a fixture for reusability.
24-27: Consider more robust HTML assertions.The assertion
assert_includes html, "Merged from pending to posted"verifies the text is present but doesn't validate the HTML structure (e.g., presence of tooltip element, badge styling, etc.).For a more robust test, consider using Capybara's CSS selectors or Nokogiri to assert on specific elements:
doc = Nokogiri::HTML(html) badge = doc.at_css('[data-controller="tooltip"]') assert badge, "Expected tooltip badge to be present" assert_includes badge.text, "Merged", "Expected badge text"This ensures the UI structure is correct, not just the text content.
lib/tasks/simplefin_unlink.rake (1)
18-21: Add ID format validation.The task validates presence but not format of
item_id. Consider adding a basic UUID validation to provide clearer error messages:unless item_id.present? && item_id.match?(/\A[a-f0-9\-]{36}\z/i) puts({ ok: false, error: "invalid_item_id", message: "item_id must be a valid UUID" }.to_json) exit 1 endThis will catch obvious typos before hitting the database and provide better feedback.
app/views/transactions/show.html.erb (1)
101-146: Move presentation logic to a helper method per coding guidelines.The section violates the guideline: "Keep heavy logic out of ERB views; prefer helpers/components instead." The
respond_to?(:extra)check is unnecessary—the schema enforces NOT NULL with default: {}, so the column always exists.Additionally, line 142's
rescueclause silently catches exceptions without logging, making debugging difficult. Rails 7.2 properly escapes attribute content intitle="<%= v %>", so the XSS concern is mitigated by default.Refactor to
app/helpers/transactions_helper.rb:def format_transaction_extra(transaction) return nil unless transaction.extra.present? extra = transaction.extra sf = extra.is_a?(Hash) ? extra["simplefin"] : nil if sf.present? { type: :simplefin, fields: extract_simplefin_fields(sf), provider_extras: sf["extra"].is_a?(Hash) ? sf["extra"] : {} } else { type: :raw, content: JSON.pretty_generate(extra) } rescue JSON::GeneratorError => e Rails.logger.warn("Failed to format transaction extra: #{e.message}") { type: :raw, content: extra.to_s } end end private def extract_simplefin_fields(sf) %w[payee description memo].each_with_object({}) do |field, hash| hash[field] = sf[field] if sf[field].present? end endThen simplify the view to render formatted data via a component. Remove the
respond_to?check sinceextrais guaranteed.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (40)
app/controllers/simplefin_items_controller.rb(6 hunks)app/models/account/provider_import_adapter.rb(2 hunks)app/models/family/auto_transfer_matchable.rb(1 hunks)app/models/holding.rb(1 hunks)app/models/simplefin_account.rb(1 hunks)app/models/simplefin_account/investments/holdings_processor.rb(4 hunks)app/models/simplefin_account/processor.rb(1 hunks)app/models/simplefin_entry/processor.rb(3 hunks)app/models/simplefin_item.rb(3 hunks)app/models/simplefin_item/importer.rb(6 hunks)app/models/simplefin_item/syncer.rb(2 hunks)app/services/simplefin_item/unlinker.rb(1 hunks)app/views/accounts/index.html.erb(1 hunks)app/views/holdings/_holding.html.erb(1 hunks)app/views/simplefin_items/_simplefin_item.html.erb(3 hunks)app/views/simplefin_items/errors.html.erb(1 hunks)app/views/simplefin_items/relink.html.erb(1 hunks)app/views/transactions/_transaction.html.erb(1 hunks)app/views/transactions/show.html.erb(1 hunks)config/database.yml(1 hunks)config/locales/views/simplefin_items/en.yml(1 hunks)config/locales/views/simplefin_items/update.en.yml(1 hunks)config/routes.rb(1 hunks)db/migrate/20251029015700_add_was_merged_to_transactions.rb(1 hunks)db/migrate/20251029190000_add_extra_to_transactions.rb(1 hunks)db/migrate/20251030172500_add_cascade_on_account_deletes.rb(1 hunks)db/schema.rb(6 hunks)lib/tasks/holdings_tools.rake(1 hunks)lib/tasks/simplefin.rake(1 hunks)lib/tasks/simplefin_backfill.rake(1 hunks)lib/tasks/simplefin_debug.rake(1 hunks)lib/tasks/simplefin_fix_was_merged.rake(1 hunks)lib/tasks/simplefin_holdings_backfill.rake(1 hunks)lib/tasks/simplefin_unlink.rake(1 hunks)lib/tasks/was_merged.rake(1 hunks)test/controllers/simplefin_items_controller_test.rb(4 hunks)test/models/account/provider_import_adapter_test.rb(7 hunks)test/models/simplefin_entry/processor_test.rb(1 hunks)test/models/simplefin_item_dedupe_test.rb(1 hunks)test/views/transactions/merged_badge_view_test.rb(1 hunks)
🧰 Additional context used
📓 Path-based instructions (22)
app/views/**/*.erb
📄 CodeRabbit inference engine (AGENTS.md)
app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.ymlAlways use the icon helper (icon) for icons; never call lucide_icon directly
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erbapp/views/transactions/show.html.erbapp/views/simplefin_items/relink.html.erb
app/views/**/*.html.*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erbapp/views/transactions/show.html.erbapp/views/simplefin_items/relink.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erbapp/views/transactions/show.html.erbapp/views/simplefin_items/relink.html.erb
app/views/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erbapp/views/transactions/show.html.erbapp/views/simplefin_items/relink.html.erb
app/{models,controllers,views}/**/*.{rb,erb}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid N+1 queries
Files:
app/views/accounts/index.html.erbapp/models/simplefin_account/processor.rbapp/models/holding.rbapp/models/simplefin_account.rbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/models/simplefin_item.rbapp/models/simplefin_account/investments/holdings_processor.rbapp/controllers/simplefin_items_controller.rbapp/models/simplefin_item/importer.rbapp/models/account/provider_import_adapter.rbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erbapp/models/simplefin_entry/processor.rbapp/models/family/auto_transfer_matchable.rbapp/models/simplefin_item/syncer.rbapp/views/transactions/show.html.erbapp/views/simplefin_items/relink.html.erb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erbapp/views/transactions/show.html.erbapp/views/simplefin_items/relink.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erbapp/views/transactions/show.html.erbapp/views/simplefin_items/relink.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erbapp/views/transactions/show.html.erbapp/views/simplefin_items/relink.html.erb
{app/views/**,app/helpers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Always generate semantic HTML
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erbapp/views/transactions/show.html.erbapp/views/simplefin_items/relink.html.erb
{app/views,app/components}/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erbapp/views/transactions/show.html.erbapp/views/simplefin_items/relink.html.erb
config/**
📄 CodeRabbit inference engine (AGENTS.md)
Store application and environment configuration under config/
Files:
config/locales/views/simplefin_items/update.en.ymlconfig/routes.rbconfig/database.ymlconfig/locales/views/simplefin_items/en.yml
app/models/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)
Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods
Use ActiveRecord validations for forms and complex domain constraints.
app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required
Files:
app/models/simplefin_account/processor.rbapp/models/holding.rbapp/models/simplefin_account.rbapp/models/simplefin_item.rbapp/models/simplefin_account/investments/holdings_processor.rbapp/models/simplefin_item/importer.rbapp/models/account/provider_import_adapter.rbapp/models/simplefin_entry/processor.rbapp/models/family/auto_transfer_matchable.rbapp/models/simplefin_item/syncer.rb
app/**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/
Files:
app/models/simplefin_account/processor.rbapp/models/holding.rbapp/models/simplefin_account.rbapp/services/simplefin_item/unlinker.rbapp/models/simplefin_item.rbapp/models/simplefin_account/investments/holdings_processor.rbapp/controllers/simplefin_items_controller.rbapp/models/simplefin_item/importer.rbapp/models/account/provider_import_adapter.rbapp/models/simplefin_entry/processor.rbapp/models/family/auto_transfer_matchable.rbapp/models/simplefin_item/syncer.rb
**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Files:
app/models/simplefin_account/processor.rbconfig/routes.rbapp/models/holding.rbdb/migrate/20251029190000_add_extra_to_transactions.rbtest/views/transactions/merged_badge_view_test.rbapp/models/simplefin_account.rbapp/services/simplefin_item/unlinker.rbtest/models/account/provider_import_adapter_test.rbapp/models/simplefin_item.rbapp/models/simplefin_account/investments/holdings_processor.rbapp/controllers/simplefin_items_controller.rbapp/models/simplefin_item/importer.rbtest/models/simplefin_item_dedupe_test.rbapp/models/account/provider_import_adapter.rbtest/controllers/simplefin_items_controller_test.rbapp/models/simplefin_entry/processor.rbapp/models/family/auto_transfer_matchable.rbtest/models/simplefin_entry/processor_test.rbapp/models/simplefin_item/syncer.rbdb/migrate/20251029015700_add_was_merged_to_transactions.rbdb/migrate/20251030172500_add_cascade_on_account_deletes.rbdb/schema.rb
**/*.{rb,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.
Files:
app/models/simplefin_account/processor.rbconfig/routes.rbapp/models/holding.rbdb/migrate/20251029190000_add_extra_to_transactions.rbtest/views/transactions/merged_badge_view_test.rbapp/models/simplefin_account.rbapp/services/simplefin_item/unlinker.rbtest/models/account/provider_import_adapter_test.rbapp/models/simplefin_item.rbapp/models/simplefin_account/investments/holdings_processor.rbapp/controllers/simplefin_items_controller.rbapp/models/simplefin_item/importer.rbtest/models/simplefin_item_dedupe_test.rbapp/models/account/provider_import_adapter.rbtest/controllers/simplefin_items_controller_test.rbapp/models/simplefin_entry/processor.rbapp/models/family/auto_transfer_matchable.rbtest/models/simplefin_entry/processor_test.rbapp/models/simplefin_item/syncer.rbdb/migrate/20251029015700_add_was_merged_to_transactions.rbdb/migrate/20251030172500_add_cascade_on_account_deletes.rbdb/schema.rb
app/{helpers,models}/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Format currencies, numbers, and dates on the server side (Ruby) before sending to the client
Files:
app/models/simplefin_account/processor.rbapp/models/holding.rbapp/models/simplefin_account.rbapp/models/simplefin_item.rbapp/models/simplefin_account/investments/holdings_processor.rbapp/models/simplefin_item/importer.rbapp/models/account/provider_import_adapter.rbapp/models/simplefin_entry/processor.rbapp/models/family/auto_transfer_matchable.rbapp/models/simplefin_item/syncer.rb
db/migrate/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place database migrations under db/migrate/
Simple validations like null checks and unique constraints should be handled by database constraints in migration files.
Files:
db/migrate/20251029190000_add_extra_to_transactions.rbdb/migrate/20251029015700_add_was_merged_to_transactions.rbdb/migrate/20251030172500_add_cascade_on_account_deletes.rb
db/migrate/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Enforce simple validations in the database (NULL constraints, unique indexes, etc.)
Files:
db/migrate/20251029190000_add_extra_to_transactions.rbdb/migrate/20251029015700_add_was_merged_to_transactions.rbdb/migrate/20251030172500_add_cascade_on_account_deletes.rb
test/**/*_test.rb
📄 CodeRabbit inference engine (AGENTS.md)
Name Minitest files with *_test.rb and mirror the app/ structure under test/
test/**/*_test.rb: ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Use Mocha for mocking in tests when necessary.
Use VCR for external API tests.
test/**/*_test.rb: Always use Minitest for tests; do not use RSpec
Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Only write tests for critical and important code paths
Avoid tests that merely verify framework/ActiveRecord behavior
Test boundaries correctly: for queries, assert returned values; for commands, assert collaborators are called with correct params
Never test the implementation details of one class in another class’s test suite
Use the mocha gem for stubs and mocks
Prefer OpenStruct for mock instances; use a mock class for complex cases
Only mock what’s necessary; don’t mock return values unless they are under test
Files:
test/views/transactions/merged_badge_view_test.rbtest/models/account/provider_import_adapter_test.rbtest/models/simplefin_item_dedupe_test.rbtest/controllers/simplefin_items_controller_test.rbtest/models/simplefin_entry/processor_test.rb
app/services/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid creating service objects; do not add business-logic files under app/services
Files:
app/services/simplefin_item/unlinker.rb
app/views/**/_*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)
Files:
app/views/simplefin_items/_simplefin_item.html.erbapp/views/transactions/_transaction.html.erbapp/views/holdings/_holding.html.erb
db/schema.rb
📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)
Treat db/schema.rb as the authoritative source of truth for model relationships
Files:
db/schema.rb
🧠 Learnings (40)
📓 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: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
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/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbapp/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Applied to files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbtest/controllers/simplefin_items_controller_test.rbapp/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated
Applied to files:
app/views/accounts/index.html.erbapp/views/simplefin_items/errors.html.erbtest/controllers/simplefin_items_controller_test.rbapp/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Applied to files:
app/models/simplefin_account/processor.rbapp/models/simplefin_account.rbtest/models/account/provider_import_adapter_test.rbapp/views/simplefin_items/_simplefin_item.html.erbapp/models/simplefin_item.rbapp/models/simplefin_account/investments/holdings_processor.rbapp/controllers/simplefin_items_controller.rbapp/models/simplefin_item/importer.rbapp/models/account/provider_import_adapter.rblib/tasks/simplefin_backfill.rakeapp/models/simplefin_entry/processor.rbapp/models/family/auto_transfer_matchable.rblib/tasks/was_merged.rakeapp/models/simplefin_item/syncer.rblib/tasks/simplefin_fix_was_merged.rake
📚 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/simplefin_account/processor.rbapp/models/simplefin_account.rbtest/models/account/provider_import_adapter_test.rbapp/models/simplefin_account/investments/holdings_processor.rbapp/controllers/simplefin_items_controller.rbapp/models/simplefin_entry/processor.rbtest/models/simplefin_entry/processor_test.rbdb/schema.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/models/simplefin_account/processor.rbapp/models/simplefin_account.rbapp/models/simplefin_account/investments/holdings_processor.rbapp/controllers/simplefin_items_controller.rbtest/models/simplefin_entry/processor_test.rbdb/schema.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator
Applied to files:
app/models/simplefin_account/processor.rbapp/models/simplefin_item.rbapp/controllers/simplefin_items_controller.rblib/tasks/was_merged.rakelib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator
Applied to files:
app/models/holding.rbtest/models/account/provider_import_adapter_test.rblib/tasks/holdings_tools.rakeapp/models/simplefin_account/investments/holdings_processor.rbapp/models/simplefin_item/importer.rbapp/views/holdings/_holding.html.erblib/tasks/simplefin_holdings_backfill.rake
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to db/migrate/**/*.rb : Enforce simple validations in the database (NULL constraints, unique indexes, etc.)
Applied to files:
db/migrate/20251029190000_add_extra_to_transactions.rbapp/models/simplefin_account.rbdb/migrate/20251030172500_add_cascade_on_account_deletes.rbdb/schema.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/simplefin_account.rbapp/controllers/simplefin_items_controller.rbdb/schema.rb
📚 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/models/simplefin_account.rbapp/controllers/simplefin_items_controller.rbtest/models/simplefin_item_dedupe_test.rbapp/models/family/auto_transfer_matchable.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/models/**/*.rb : Implement complex validations and business logic with ActiveRecord validations
Applied to files:
app/models/simplefin_account.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to db/migrate/*.rb : Simple validations like null checks and unique constraints should be handled by database constraints in migration files.
Applied to files:
app/models/simplefin_account.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
Applied to files:
app/models/simplefin_account.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Applied to files:
app/models/simplefin_account.rb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Applied to files:
app/views/simplefin_items/errors.html.erbapp/views/simplefin_items/relink.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 : Name partials with a leading underscore (e.g., _form_errors.html.erb)
Applied to files:
app/views/simplefin_items/errors.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to app/views/**/_*.html.erb : Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)
Applied to files:
app/views/simplefin_items/errors.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 ViewComponents when elements have complex logic/styling, are reused across contexts, need variants/slots/accessibility, or require Stimulus interactivity
Applied to files:
app/views/simplefin_items/errors.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/transfer.rb : Transfer auto-matching must meet all criteria: different accounts, within 4 days, same currency, opposite values
Applied to files:
test/models/account/provider_import_adapter_test.rbapp/models/family/auto_transfer_matchable.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Avoid tests that merely verify framework/ActiveRecord behavior
Applied to files:
test/models/account/provider_import_adapter_test.rbtest/models/simplefin_item_dedupe_test.rbtest/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{entry,valuation,transaction,trade}.rb : Entry amounts are signed: negative = inflow, positive = outflow (applies to Entry and its types: Valuation, Transaction, Trade)
Applied to files:
test/models/account/provider_import_adapter_test.rbapp/models/account/provider_import_adapter.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs
Applied to files:
app/models/simplefin_item.rbapp/controllers/simplefin_items_controller.rbapp/models/simplefin_item/importer.rbapp/models/family/auto_transfer_matchable.rbapp/models/simplefin_item/syncer.rblib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries
Applied to files:
app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Applied to files:
app/controllers/simplefin_items_controller.rbapp/models/simplefin_item/importer.rbapp/models/simplefin_item/syncer.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Applied to files:
test/models/simplefin_item_dedupe_test.rbtest/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/**/*_test.rb : ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Applied to files:
test/models/simplefin_item_dedupe_test.rbtest/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to test/**/*_test.rb : Name Minitest files with *_test.rb and mirror the app/ structure under test/
Applied to files:
test/models/simplefin_item_dedupe_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Never test the implementation details of one class in another class’s test suite
Applied to files:
test/models/simplefin_item_dedupe_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases; create edge cases inline in tests
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Use fixtures for test data rather than factories
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases.
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.turbo_stream.erb : Use Turbo Streams to enhance functionality but avoid hard dependencies on them
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-08-22T18:30:26.758Z
Learnt from: jjmata
Repo: we-promise/sure PR: 116
File: app/views/family_exports/_list.html.erb:16-28
Timestamp: 2025-08-22T18:30:26.758Z
Learning: In the family exports UI (app/views/family_exports/_list.html.erb), raw color utilities like bg-green-500/5, text-green-500, bg-red-500/5, text-red-500 are preferred over design system tokens for status indicators to maintain UI legibility and consistency with the existing import UI patterns. The user has indicated that design token replacements would make the UI illegible in this context.
Applied to files:
app/views/holdings/_holding.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/simplefin_items/relink.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to db/schema.rb : Treat db/schema.rb as the authoritative source of truth for model relationships
Applied to files:
db/schema.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to db/seeds.rb : Keep seed data definitions in db/seeds.rb
Applied to files:
db/schema.rb
📚 Learning: 2025-08-22T23:27:46.033Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: db/migrate/20250813144520_add_institution_fields_to_simplefin_items.rb:6-6
Timestamp: 2025-08-22T23:27:46.033Z
Learning: Institution URLs in financial contexts are typically short (under 100 characters) and don't require text columns - varchar(255) via :string is sufficient for SimpleFin institution URLs.
Applied to files:
db/schema.rb
🧬 Code graph analysis (15)
app/models/simplefin_account/processor.rb (1)
app/models/simplefin_account.rb (2)
current_account(16-18)ensure_account_provider!(22-34)
db/migrate/20251029190000_add_extra_to_transactions.rb (1)
db/migrate/20251029015700_add_was_merged_to_transactions.rb (2)
change(1-5)change(2-4)
app/services/simplefin_item/unlinker.rb (1)
app/models/simplefin_item/importer.rb (1)
initialize(5-9)
test/models/account/provider_import_adapter_test.rb (1)
app/models/account/provider_import_adapter.rb (2)
import_holding(232-294)import_transaction(20-101)
app/models/simplefin_item.rb (3)
app/models/simplefin_item/provided.rb (1)
simplefin_provider(4-6)app/models/simplefin_item/importer.rb (1)
import(11-32)app/models/simplefin_account.rb (1)
current_account(16-18)
app/models/simplefin_account/investments/holdings_processor.rb (3)
app/models/simplefin_entry/processor.rb (6)
account(47-49)currency(97-99)import_adapter(43-45)amount(78-95)date(105-120)external_id(55-59)app/models/simplefin_account/transactions/processor.rb (1)
account(39-41)app/models/account/provider_import_adapter.rb (1)
import_holding(232-294)
app/controllers/simplefin_items_controller.rb (6)
app/controllers/accounts_controller.rb (3)
before_action(1-85)sync(30-46)family(78-80)app/models/simplefin_item/importer.rb (1)
import_balances_only(35-68)app/models/simplefin_item.rb (2)
dedup_simplefin_accounts!(163-226)merge_duplicate_provider_accounts!(230-274)app/services/simplefin_item/unlinker.rb (1)
unlink_all!(11-32)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)app/models/simplefin_account.rb (1)
current_account(16-18)
app/models/simplefin_item/importer.rb (3)
app/models/account/provider_import_adapter.rb (1)
update_balance(211-216)app/models/provider/simplefin.rb (1)
get_accounts(27-74)app/models/simplefin_item.rb (1)
upsert_simplefin_snapshot!(61-70)
test/models/simplefin_item_dedupe_test.rb (1)
app/models/simplefin_item.rb (1)
dedup_simplefin_accounts!(163-226)
app/models/account/provider_import_adapter.rb (3)
app/models/simplefin_entry/processor.rb (10)
external_id(55-59)amount(78-95)currency(97-99)date(105-120)name(61-76)merchant(169-182)notes(189-201)posted_date(122-139)transacted_date(141-158)account(47-49)app/models/plaid_entry/processor.rb (7)
external_id(33-35)amount(41-43)currency(45-47)date(49-51)name(37-39)merchant(62-70)account(29-31)app/models/concerns/enrichable.rb (1)
enrich_attribute(26-28)
test/controllers/simplefin_items_controller_test.rb (2)
test/models/account_provider_test.rb (1)
setup(3-132)test/models/simplefin_item_test.rb (1)
setup(3-148)
app/models/simplefin_entry/processor.rb (2)
app/models/account/provider_import_adapter.rb (2)
attr_reader(1-382)to_date(153-158)app/models/simplefin_account/transactions/processor.rb (2)
attr_reader(1-42)account(39-41)
test/models/simplefin_entry/processor_test.rb (1)
app/models/simplefin_entry/processor.rb (3)
process(10-25)name(61-76)currency(97-99)
app/models/simplefin_item/syncer.rb (4)
app/models/simplefin_item/importer.rb (1)
import_balances_only(35-68)app/models/simplefin_item.rb (1)
import_latest_simplefin_data(41-43)app/models/concerns/syncable.rb (1)
perform_post_sync(46-48)app/models/sync.rb (1)
perform_post_sync(144-151)
db/migrate/20251029015700_add_was_merged_to_transactions.rb (1)
db/migrate/20251029190000_add_extra_to_transactions.rb (2)
change(1-6)change(2-5)
…2). Address all 15 actionable CodeRabbit comments: Add UUID validations in rakes (e.g., simplefin_unlink), swap Ruby pattern matching/loops for efficient DB queries (e.g., where LOWER(name) LIKE ?), generate docstrings for low-coverage areas (31%), consolidate routes for simplefin_items, move view logic to helpers (e.g., format_transaction_extra), strengthen tests with exact assertions/fixtures for dedup/relink failures. Also, check for overlaps with merged we-promise#262 (merchants fix): Ensure merchant creation in simplefin_entry/processor.rb aligns with new payee-based flow and MD5 IDs; add tests for edge cases like empty payees or over-merging pendings. Prioritize security (PII redaction in logs, no hardcoded secrets).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
♻️ Duplicate comments (2)
lib/tasks/simplefin_fix_was_merged.rake (1)
35-36: Dry-run default has been corrected.The previous concern about
dry_rundefaulting to destructive behavior has been addressed. The current implementation correctly defaults totruewhen no argument is provided, ensuring safe preview behavior.lib/tasks/simplefin_unlink.rake (1)
9-21: The custom argument parsing has been flagged in a previous review.The existing review comment suggests simplifying lines 9-15 by using Rake's native argument handling. That recommendation remains valid.
🧹 Nitpick comments (7)
.junie/guidelines.md (3)
20-20: Wrap bare URLs in markdown syntax to comply with lint rules.The URLs on this line are reported as bare URLs (MD034). Wrap them in angle brackets or convert to markdown links for compliance.
- - cd sure - - cp .env.local.example .env.local - - bin/setup - - bin/dev (visit http://localhost:3000; demo: [email protected] / Password1!) + - cd sure + - cp .env.local.example .env.local + - bin/setup + - bin/dev (visit <http://localhost:3000>; demo: [email protected] / Password1!)
39-39: Capitalize "GitHub" per standard naming conventions.- - RuboCop is enforced by CI (`bin/rubocop -f github`) + - RuboCop is enforced by CI (`bin/rubocop -f GitHub`)
63-63: Use hyphen for compound adjective "request-scoped".- - Use `Current.user` and `Current.family` for request scoped context (never `current_user`/`current_family`) + - Use `Current.user` and `Current.family` for request-scoped context (never `current_user`/`current_family`)lib/tasks/simplefin_fix_was_merged.rake (1)
104-117: Consider extracting date parsing logic.The date parsing logic for
postedandtransacted_atis duplicated. Extracting this into a helper method would improve maintainability and reduce code duplication.Example helper method:
def parse_flexible_date(value) case value when String then Date.parse(value) rescue nil when Numeric then Time.zone.at(value).to_date rescue nil when Date then value when Time, DateTime then value.to_date else nil end endThen use:
posted_d = parse_flexible_date(t[:posted]) trans_d = parse_flexible_date(t[:transacted_at])app/controllers/simplefin_items_controller.rb (3)
323-449: Consider extracting apply_relink to a service object.The
apply_relinkmethod spans ~120 lines and handles multiple responsibilities: pair validation, transaction orchestration, data migration, cleanup, and response formatting. Extracting this logic to a dedicated service (e.g.,SimplefinItem::RelinkService) would improve testability and maintainability.Example structure:
# app/services/simplefin_item/relink_service.rb class SimplefinItem::RelinkService def initialize(simplefin_item, pairs, family) @simplefin_item = simplefin_item @pairs = pairs @family = family end def call # Extract lines 329-409 here end end # In controller: def apply_relink pairs = parse_relink_pairs results = SimplefinItem::RelinkService.new(@simplefin_item, pairs, Current.family).call # Response handling... end
455-562: Move helper methods to a concern or service.The private methods
compute_unlinked_count,normalize_name, andcompute_relink_candidates(lines 455-562) implement domain logic that could be reused or tested independently. Consider extracting them to a concern (e.g.,SimplefinItems::RelinkHelpers) or incorporating them into a service object.This would improve separation of concerns and make the controller more focused on request/response handling.
565-569: Simplify defensive Current.family checks.The
set_simplefin_itemmethod includes defensive checks forCurrent.family(lines 565-568), butCurrent.familyshould always be available after authentication viabefore_action. If this is for test compatibility, document why; otherwise, simplify to just line 566.def set_simplefin_item - if defined?(Current) && Current.respond_to?(:family) && Current.family.present? - @simplefin_item = Current.family.simplefin_items.find(params[:id]) - else - @simplefin_item = SimplefinItem.find(params[:id]) - end + @simplefin_item = Current.family.simplefin_items.find(params[:id]) end
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
.junie/guidelines.md(1 hunks)app/controllers/simplefin_items_controller.rb(6 hunks)app/helpers/simplefin_items_helper.rb(1 hunks)app/javascript/controllers/auto_relink_controller.js(1 hunks)app/views/accounts/index.html.erb(1 hunks)app/views/simplefin_items/_simplefin_item.html.erb(3 hunks)app/views/simplefin_items/relink.html.erb(1 hunks)config/routes.rb(1 hunks)lib/tasks/simplefin_backfill.rake(1 hunks)lib/tasks/simplefin_fix_was_merged.rake(1 hunks)lib/tasks/simplefin_unlink.rake(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- app/views/simplefin_items/relink.html.erb
- lib/tasks/simplefin_backfill.rake
- config/routes.rb
🧰 Additional context used
📓 Path-based instructions (21)
app/**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/
Files:
app/helpers/simplefin_items_helper.rbapp/controllers/simplefin_items_controller.rb
**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Files:
app/helpers/simplefin_items_helper.rbapp/controllers/simplefin_items_controller.rb
**/*.{rb,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.
Files:
app/helpers/simplefin_items_helper.rbapp/javascript/controllers/auto_relink_controller.jsapp/controllers/simplefin_items_controller.rb
app/{helpers,models}/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Format currencies, numbers, and dates on the server side (Ruby) before sending to the client
Files:
app/helpers/simplefin_items_helper.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/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)
Files:
app/helpers/simplefin_items_helper.rbapp/javascript/controllers/auto_relink_controller.jsapp/views/accounts/index.html.erbapp/views/simplefin_items/_simplefin_item.html.erb
{app/views/**,app/helpers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Always generate semantic HTML
Files:
app/helpers/simplefin_items_helper.rbapp/views/accounts/index.html.erbapp/views/simplefin_items/_simplefin_item.html.erb
app/javascript/**/*.{js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
app/javascript/**/*.{js,jsx}: Place JavaScript code under app/javascript/
JavaScript naming: lowerCamelCase for variables/functions; PascalCase for classes/components
Let Biome format and lint JavaScript code (npm run lint/format)
Files:
app/javascript/controllers/auto_relink_controller.js
app/javascript/controllers/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/javascript/controllers/**/*.js: Stimulus controllers should remain lightweight, have fewer than 7 targets, use private methods and expose a clear public API.
Pass data viadata-*-valueattributes, not with inline JavaScript.
Component-specific controllers should stay near components; global controllers go inapp/javascript/controllers/.
Files:
app/javascript/controllers/auto_relink_controller.js
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.
Files:
app/javascript/controllers/auto_relink_controller.jsapp/views/accounts/index.html.erbapp/views/simplefin_items/_simplefin_item.html.erb
app/javascript/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Use URL query params for state over localStorage/sessionStorage; only persist to DB if absolutely necessary
Files:
app/javascript/controllers/auto_relink_controller.js
app/javascript/controllers/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
app/javascript/controllers/**/*.{js,ts}: Stimulus controllers should display preformatted values; avoid implementing formatting logic client-side
Keep client-side code for interactions where it truly shines (e.g., bulk selection); avoid unnecessary client-only solutionsPlace global Stimulus controllers under app/javascript/controllers so they can be used across any view
Files:
app/javascript/controllers/auto_relink_controller.js
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions
Files:
app/javascript/controllers/auto_relink_controller.jsapp/views/accounts/index.html.erbapp/views/simplefin_items/_simplefin_item.html.erb
{app/javascript/controllers/**/*.{js,ts},app/components/**/*.{js,ts}}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
{app/javascript/controllers/**/*.{js,ts},app/components/**/*.{js,ts}}: Keep Stimulus controllers lightweight: aim for fewer than 7 targets; use private methods and expose a clear public API
Keep Stimulus controllers focused: no domain logic; favor single responsibility; leverage Stimulus callbacks, actions, targets, values, and classes
Files:
app/javascript/controllers/auto_relink_controller.js
{app/javascript/controllers,app/components}/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
Use Stimulus targets for DOM access instead of manual document.getElementById calls
Files:
app/javascript/controllers/auto_relink_controller.js
app/views/**/*.erb
📄 CodeRabbit inference engine (AGENTS.md)
app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.ymlAlways use the icon helper (icon) for icons; never call lucide_icon directly
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/_simplefin_item.html.erb
app/{models,controllers,views}/**/*.{rb,erb}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid N+1 queries
Files:
app/views/accounts/index.html.erbapp/controllers/simplefin_items_controller.rbapp/views/simplefin_items/_simplefin_item.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/_simplefin_item.html.erb
{app/views,app/components}/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript
Files:
app/views/accounts/index.html.erbapp/views/simplefin_items/_simplefin_item.html.erb
app/views/**/_*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)
Files:
app/views/simplefin_items/_simplefin_item.html.erb
🧠 Learnings (38)
📓 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: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Applied to files:
lib/tasks/simplefin_fix_was_merged.rakeapp/controllers/simplefin_items_controller.rbapp/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to **/*.{rb,js,jsx,ts,tsx} : Make changes atomic, testable, and explain their impact briefly in code suggestions.
Applied to files:
lib/tasks/simplefin_fix_was_merged.rakelib/tasks/simplefin_unlink.rake.junie/guidelines.md
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator
Applied to files:
lib/tasks/simplefin_fix_was_merged.rakeapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:22:07.608Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:07.608Z
Learning: Applies to {app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb} : Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions
Applied to files:
app/javascript/controllers/auto_relink_controller.jsapp/views/accounts/index.html.erbapp/controllers/simplefin_items_controller.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,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations
Applied to files:
app/javascript/controllers/auto_relink_controller.jsapp/views/accounts/index.html.erbapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:22:07.608Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:07.608Z
Learning: Applies to {app/javascript/controllers/**/*.{js,ts},app/components/**/*.{js,ts}} : Keep Stimulus controllers focused: no domain logic; favor single responsibility; leverage Stimulus callbacks, actions, targets, values, and classes
Applied to files:
app/javascript/controllers/auto_relink_controller.js
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Applied to files:
app/javascript/controllers/auto_relink_controller.jsapp/views/accounts/index.html.erbapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to app/javascript/controllers/**/*.{js,ts} : Place global Stimulus controllers under app/javascript/controllers so they can be used across any view
Applied to files:
app/javascript/controllers/auto_relink_controller.js
📚 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 **/*_controller.{js,ts} : In Stimulus controllers, do not imperatively bind DOM events (e.g., addEventListener in connect); expose methods and let views trigger them via actions
Applied to files:
app/javascript/controllers/auto_relink_controller.js
📚 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/javascript/controllers/**/*.js : Global Stimulus controllers under app/javascript/controllers/ can be used across any view
Applied to files:
app/javascript/controllers/auto_relink_controller.js
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to app/javascript/controllers/**/*.js : Stimulus controllers should remain lightweight, have fewer than 7 targets, use private methods and expose a clear public API.
Applied to files:
app/javascript/controllers/auto_relink_controller.js
📚 Learning: 2025-09-23T22:22:07.608Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:07.608Z
Learning: Applies to {app/javascript/controllers/**/*.{js,ts},app/components/**/*.{js,ts}} : Keep Stimulus controllers lightweight: aim for fewer than 7 targets; use private methods and expose a clear public API
Applied to files:
app/javascript/controllers/auto_relink_controller.js
📚 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 **/*_controller.{js,ts} : Keep Stimulus controllers focused on UI behavior; avoid domain/business logic and prefer single, closely related responsibilities; use Stimulus callbacks, actions, targets, values, and classes
Applied to files:
app/javascript/controllers/auto_relink_controller.js
📚 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/javascript/controllers/auto_relink_controller.jsapp/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript
Applied to files:
app/views/accounts/index.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/accounts/index.html.erb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to app/javascript/controllers/**/*.js : Pass data via `data-*-value` attributes, not with inline JavaScript.
Applied to files:
app/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Prefer native HTML elements (e.g., <dialog>, <details><summary>) over JS-based components
Applied to files:
app/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/javascript/controllers,app/components}/**/*.{js,ts} : Use Stimulus targets for DOM access instead of manual document.getElementById calls
Applied to files:
app/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Applied to files:
app/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated
Applied to files:
app/views/accounts/index.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/controllers/simplefin_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/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs
Applied to files:
app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries
Applied to files:
app/controllers/simplefin_items_controller.rb
📚 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/simplefin_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/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Applied to files:
app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Document breaking changes in the rules
Applied to files:
.junie/guidelines.md
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Modify existing rules when implementation details have changed
Applied to files:
.junie/guidelines.md
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Update rules after major refactors
Applied to files:
.junie/guidelines.md
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Add new rules when code reviews repeatedly mention the same feedback
Applied to files:
.junie/guidelines.md
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Document migration paths for deprecated or replaced rules
Applied to files:
.junie/guidelines.md
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Add new rules when common bugs could be prevented by a rule
Applied to files:
.junie/guidelines.md
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Applied to files:
.junie/guidelines.md
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Never propose system commands (e.g., rails server, rails credentials, migrations) in PR suggestions or code comments.
Applied to files:
.junie/guidelines.md
📚 Learning: 2025-09-23T22:23:17.441Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/ui-ux-design-guidelines.mdc:0-0
Timestamp: 2025-09-23T22:23:17.441Z
Learning: Applies to {app/views/**,app/helpers/**,app/javascript/controllers/**} : Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Applied to files:
.junie/guidelines.md
🧬 Code graph analysis (2)
app/helpers/simplefin_items_helper.rb (1)
app/models/simplefin_item/importer.rb (1)
stats(103-105)
app/controllers/simplefin_items_controller.rb (6)
app/controllers/accounts_controller.rb (2)
sync(30-46)family(78-80)app/models/simplefin_item/importer.rb (1)
import_balances_only(35-68)app/models/simplefin_item.rb (2)
dedup_simplefin_accounts!(163-226)merge_duplicate_provider_accounts!(230-274)app/services/simplefin_item/unlinker.rb (1)
unlink_all!(11-32)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)app/models/simplefin_account.rb (1)
current_account(16-18)
🪛 GitHub Check: ci / lint
app/helpers/simplefin_items_helper.rb
[failure] 30-30:
Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.
[failure] 30-30:
Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.
🪛 LanguageTool
.junie/guidelines.md
[uncategorized] ~39-~39: The official name of this software platform is spelled with a capital “H”.
Context: ...-enforced) - RuboCop is enforced by CI (bin/rubocop -f github) - Before committing/pushing: - Run ...
(GITHUB)
[grammar] ~63-~63: Use a hyphen to join words.
Context: ...t.userandCurrent.familyfor request scoped context (nevercurrent_user/cu...
(QB_NEW_EN_HYPHEN)
🪛 markdownlint-cli2 (0.18.1)
.junie/guidelines.md
20-20: Bare URL used
(MD034, no-bare-urls)
20-20: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci / test
🔇 Additional comments (5)
.junie/guidelines.md (1)
1-100: Well-structured guidelines document that aligns with PR #267 objectives.This document effectively consolidates operational rules, CI expectations, and conventions for the project. It includes important pragmatic rules such as prohibitions for assistants (no
rails server, no auto-run migrations) and SimpleFin/Plaid conventions directly relevant to the PR objectives (#110, #254). The guidance on skinny controllers, use ofCurrentcontext, and frontend token usage will help maintain consistency across the codebase as the SimpleFin enhancements are integrated. Clear, actionable, and well-organized.app/views/accounts/index.html.erb (1)
46-52: LGTM: Follows Stimulus best practices.The auto-relink flow now uses a declarative Stimulus controller with data attributes, correctly addressing the previous review feedback. The URL is safely passed via
data-auto-relink-url-value, and the controller handles the modal/navigation logic.app/javascript/controllers/auto_relink_controller.js (1)
1-30: LGTM: Well-structured Stimulus controller.The controller correctly uses the
connect()lifecycle to perform one-time modal initialization, includes robust error handling with fallback navigation, and remains lightweight and focused. The implementation follows Stimulus best practices.app/controllers/simplefin_items_controller.rb (2)
61-61: Replace bare rescue clauses with explicit exception classes.Multiple bare
rescueclauses throughout the controller catch all exceptions includingSystemExitandSignalException. SpecifyStandardErroror the specific exception types expected (e.g.,Provider::Simplefin::SimplefinError, ArgumentError, StandardError).Example for line 61:
- rescue Provider::Simplefin::SimplefinError, ArgumentError, StandardError => e + rescue Provider::Simplefin::SimplefinError, ArgumentError => eAnd for lines 73, 145 (currently bare rescue):
- rescue => e + rescue StandardError => eAlso applies to: 74-74, 131-131, 146-146
⛔ Skipped due to learnings
Learnt from: CR Repo: we-promise/sure PR: 0 File: .cursor/rules/project-design.mdc:0-0 Timestamp: 2025-09-16T13:15:56.406Z Learning: Applies to app/models/provider/**/*.rb : Concrete provider classes must be under the Provider:: namespace, inherit from Provider, wrap calls with with_provider_response, and raise on invalid/unavailable data
351-397: Test coverage for the FK constraint fix with holdings is incomplete.The test "apply_relink links pairs in one pass and avoids duplicates" creates and verifies the flow with duplicate accounts and the AccountProvider reassignment, but it does not create or assert holdings behavior. No holdings are created or tested in the
apply_relinktest suite.The schema confirms the FK constraint exists (
add_foreign_key "holdings", "account_providers"), and the controller code correctly updates holdings withaccount_provider_idbefore any deletion (line 378 before line 390). However, this critical path—holdings referencing the reused AccountProvider—is not verified by integration tests.To confirm the fix resolves the
PG::ForeignKeyViolation, you should either:
- Add integration test coverage creating holdings on the provider-linked account before relink, then assert they are successfully reassigned to the manual account with the correct
account_provider_id, or- Manually verify the relink flow works when holdings exist on the duplicate provider-linked account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
app/helpers/simplefin_items_helper.rb (1)
30-30: Fix RuboCop spacing violation.This issue was already flagged in a previous review. Please add spaces inside the array brackets.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/helpers/simplefin_items_helper.rb(1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
app/**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/
Files:
app/helpers/simplefin_items_helper.rb
**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Files:
app/helpers/simplefin_items_helper.rb
**/*.{rb,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.
Files:
app/helpers/simplefin_items_helper.rb
app/{helpers,models}/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Format currencies, numbers, and dates on the server side (Ruby) before sending to the client
Files:
app/helpers/simplefin_items_helper.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/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)
Files:
app/helpers/simplefin_items_helper.rb
{app/views/**,app/helpers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Always generate semantic HTML
Files:
app/helpers/simplefin_items_helper.rb
🧠 Learnings (2)
📓 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: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to **/*.rb : Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Applied to files:
app/helpers/simplefin_items_helper.rb
🧬 Code graph analysis (1)
app/helpers/simplefin_items_helper.rb (1)
app/models/simplefin_item/importer.rb (1)
stats(103-105)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: ci / test
🔇 Additional comments (3)
app/helpers/simplefin_items_helper.rb (3)
1-4: LGTM!Module declaration follows Rails and Ruby conventions correctly.
5-14: LGTM!Method documentation is clear and includes a helpful example structure.
15-23: LGTM!Input validation and sample-building logic are sound. The defensive programming with
Array()wrapper and dual key support (symbol/string) handles various input formats safely.
There was a problem hiding this 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 (4)
lib/tasks/simplefin_backfill.rake (3)
81-175: Don't callfind_eachon Arrays.When selecting by
account_id,sfasbecomes anArrayand.find_eachwill raiseNoMethodError, blocking the task. Branch on the capability before iterating.- sfas.find_each do |sfa| + iterable = sfas.respond_to?(:find_each) ? sfas : Array.wrap(sfas) + iterable.each do |sfa|
75-177: Per-account log output must use per-account counters.The per-SFA summary prints
{seen, matched, updated, skipped, errors}using the global totals, so every row repeats cumulative numbers and hides per-account performance. Track per-account counters alongside the totals before logging.
150-167: Guardexternal_idand reuse the processor.If
t[:id]is blank you generate"simplefin_", leading to bad lookups, and you instantiateSimplefinEntry::Processortwice. Guard the ID and reuse a single processor instance.- adapter = Account::ProviderImportAdapter.new(acct) - external_id = "simplefin_#{t[:id]}" + adapter = Account::ProviderImportAdapter.new(acct) + processor = SimplefinEntry::Processor.new(t, simplefin_account: sfa) + external_id = t[:id].present? ? "simplefin_#{t[:id]}" : nilThen reuse
processorforname/amount(dry-run) andprocess.app/models/account/provider_import_adapter.rb (1)
150-152: Remove the amount tolerance as agreed.The ±$0.01 tolerance contradicts the comment on lines 35-36 ("no tolerance, no window") and the agreement to remove it. Since composite_match already uses exact SQL amount matching, this tolerance check is redundant. It should use exact equality to prevent future confusion or misuse.
Apply this diff:
def within_amount_tolerance?(a, b) - (BigDecimal(a.to_s) - BigDecimal(b.to_s)).abs <= BigDecimal("0.01") + BigDecimal(a.to_s) == BigDecimal(b.to_s) end
🧹 Nitpick comments (4)
app/controllers/simplefin_items_controller.rb (2)
336-339: Potential N+1 queries in relink loop.Lines 337-338 execute
find_byqueries for each pair. If processing many relinks, this results in N+1 queries.Consider eager loading:
+ sfa_ids = pairs.map { |p| p[:sfa_id] }.compact + manual_ids = pairs.map { |p| p[:manual_id] }.compact + sfas_by_id = @simplefin_item.simplefin_accounts.where(id: sfa_ids).index_by(&:id) + manuals_by_id = Current.family.accounts.where(id: manual_ids).index_by(&:id) + SimplefinItem.transaction do pairs.each do |pair| - sfa = @simplefin_item.simplefin_accounts.find_by(id: pair[:sfa_id]) - manual = Current.family.accounts.find_by(id: pair[:manual_id]) + sfa = sfas_by_id[pair[:sfa_id]] + manual = manuals_by_id[pair[:manual_id]] next unless sfa && manual
474-568: Approve relink candidate heuristics with performance note.The
compute_relink_candidateslogic correctly implements conservative matching:
- Last4 matching with ambiguity guards (Lines 502-531)
- Balance proximity matching within ±$0.01 (Lines 534-547)
- Exact normalized name matching (Lines 550-556)
- Conflict detection prevents incorrect matches (Lines 518-525)
The O(n*m) complexity is acceptable for typical family account counts. For families with hundreds of accounts, consider adding a limit or pagination.
app/models/account/provider_import_adapter.rb (1)
165-173: Consider removing unused normalize_name helpers.The NAME_STRIP_REGEX constant and normalize_name method appear unused since composite_match was tightened to exact matching without name heuristics. Removing unused code improves maintainability.
app/models/simplefin_account/processor.rb (1)
30-36: Optional: Consider removing redundant guard.This check is redundant with the early return on line 14. However, keeping it as defensive programming is acceptable and the error message provides useful context if this path is ever reached.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
app/controllers/simplefin_items_controller.rb(6 hunks)app/models/account/provider_import_adapter.rb(2 hunks)app/models/simplefin_account/processor.rb(1 hunks)config/routes.rb(1 hunks)lib/tasks/simplefin_backfill.rake(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- config/routes.rb
🧰 Additional context used
📓 Path-based instructions (6)
app/models/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)
Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods
Use ActiveRecord validations for forms and complex domain constraints.
app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required
Files:
app/models/account/provider_import_adapter.rbapp/models/simplefin_account/processor.rb
app/**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/
Files:
app/models/account/provider_import_adapter.rbapp/models/simplefin_account/processor.rbapp/controllers/simplefin_items_controller.rb
**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Files:
app/models/account/provider_import_adapter.rbapp/models/simplefin_account/processor.rbapp/controllers/simplefin_items_controller.rb
**/*.{rb,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.
Files:
app/models/account/provider_import_adapter.rbapp/models/simplefin_account/processor.rbapp/controllers/simplefin_items_controller.rb
app/{helpers,models}/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Format currencies, numbers, and dates on the server side (Ruby) before sending to the client
Files:
app/models/account/provider_import_adapter.rbapp/models/simplefin_account/processor.rb
app/{models,controllers,views}/**/*.{rb,erb}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid N+1 queries
Files:
app/models/account/provider_import_adapter.rbapp/models/simplefin_account/processor.rbapp/controllers/simplefin_items_controller.rb
🧠 Learnings (15)
📓 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: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
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: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Applied to files:
lib/tasks/simplefin_backfill.rakeapp/models/account/provider_import_adapter.rbapp/models/simplefin_account/processor.rbapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:40:26.913Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.
Applied to files:
lib/tasks/simplefin_backfill.rake
📚 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:
lib/tasks/simplefin_backfill.rakeapp/models/account/provider_import_adapter.rbapp/models/simplefin_account/processor.rbapp/controllers/simplefin_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:
lib/tasks/simplefin_backfill.rakeapp/models/simplefin_account/processor.rbapp/controllers/simplefin_items_controller.rb
📚 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:
lib/tasks/simplefin_backfill.rakeapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Applied to files:
lib/tasks/simplefin_backfill.rake
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
Applied to files:
lib/tasks/simplefin_backfill.rake
📚 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:
lib/tasks/simplefin_backfill.rake
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{entry,valuation,transaction,trade}.rb : Entry amounts are signed: negative = inflow, positive = outflow (applies to Entry and its types: Valuation, Transaction, Trade)
Applied to files:
app/models/account/provider_import_adapter.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Applied to files:
app/models/simplefin_account/processor.rbapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs
Applied to files:
app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator
Applied to files:
app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries
Applied to files:
app/controllers/simplefin_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/simplefin_items_controller.rb
🧬 Code graph analysis (3)
app/models/account/provider_import_adapter.rb (4)
app/models/simplefin_entry/processor.rb (10)
external_id(55-59)amount(78-95)currency(97-99)date(105-120)name(61-76)merchant(169-182)notes(189-201)posted_date(122-139)transacted_date(141-158)account(47-49)app/models/plaid_entry/processor.rb (7)
external_id(33-35)amount(41-43)currency(45-47)date(49-51)name(37-39)merchant(62-70)account(29-31)app/models/plaid_account/investments/transactions_processor.rb (1)
account(26-28)app/models/concerns/enrichable.rb (1)
enrich_attribute(26-28)
app/models/simplefin_account/processor.rb (2)
app/models/simplefin_account.rb (2)
current_account(16-18)ensure_account_provider!(22-34)app/models/plaid_account/processor.rb (1)
process_account!(31-95)
app/controllers/simplefin_items_controller.rb (6)
app/controllers/accounts_controller.rb (2)
sync(30-46)family(78-80)app/models/simplefin_item/importer.rb (1)
import_balances_only(35-68)app/models/simplefin_item.rb (2)
dedup_simplefin_accounts!(163-226)merge_duplicate_provider_accounts!(230-274)app/services/simplefin_item/unlinker.rb (1)
unlink_all!(11-32)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)app/models/simplefin_account.rb (1)
current_account(16-18)
🔇 Additional comments (17)
app/controllers/simplefin_items_controller.rb (9)
1-1: LGTM!The
Setrequire is correctly placed and used incompute_relink_candidatesfor efficient membership tracking.
54-90: Approve enhanced update flow with balances-only sync and relink detection.The update flow correctly:
- Runs balances-only discovery to refresh SimpleFin accounts
- Deduplicates accounts to prevent UI issues
- Computes unlinked counts for setup CTA control
- Auto-detects relink candidates and redirects appropriately
- Handles errors gracefully with rescue blocks and logging
179-188: LGTM!The destroy action correctly unlinks all provider relationships before deletion, preventing foreign key violations. Error handling is appropriate.
213-229: LGTM!The setup flow correctly deduplicates accounts before presentation and uses eager loading to avoid N+1 queries. Logging provides good observability.
299-315: LGTM!The errors action correctly fetches sync stats and normalizes error data for the modal view. Authorization is enforced via
set_simplefin_itemusingCurrent.family.
317-327: LGTM!Both relink actions are straightforward and render modal-friendly views. The duplication is intentional for clarity of different flows.
354-404: Approve the apply_relink data migration logic.The relink logic correctly:
- Creates or updates the AccountProvider record before moving data (Lines 359-361)
- Moves entries with duplicate detection on (external_id, source) (Lines 365-374)
- Moves holdings with duplicate detection and repoints account_provider_id (Lines 377-387)
- Destroys the redundant provider-linked account after migration (Line 396)
- Handles the no-existing-account case (Lines 397-403)
This addresses the foreign key constraint issue from past reviews by reusing the provider row.
461-472: LGTM!Both
compute_unlinked_countandnormalize_nameare well-implemented helpers that reduce duplication and improve readability.
570-576: Approve defensive authorization check inset_simplefin_item.The method correctly scopes by
Current.familywhen available and falls back to direct lookup, ensuring proper authorization.app/models/account/provider_import_adapter.rb (6)
18-20: LGTM: Expanded signature supports richer transaction metadata.The new parameters (fitid, notes, posted_date, transacted_date, pending, extra) enable FITID fallback matching, pending→posted merge detection, and provider metadata persistence as described in the PR objectives.
25-48: LGTM: Three-stage matching flow is well-structured.The matching priority (exact external_id+source → FITID fallback → composite_match) prevents duplicates while supporting legacy FITID identifiers. Updating external_id on line 46 ensures future syncs use provider IDs, and the was_merged flag correctly captures pending→posted progressions.
76-79: LGTM: Notes enrichment follows the established pattern.Using enrich_attribute respects user overrides while allowing provider-sourced notes to populate empty fields.
80-86: LGTM: Extra metadata persistence is correctly implemented.The non-enriched, always-merged approach for extra metadata is appropriate for accumulating provider-specific data. Using deep_merge handles nested structures correctly.
87-94: LGTM: Single-record merge detection handles provider variations.This correctly handles providers that send a single transaction with both transacted_date and posted_date instead of separate pending/posted records, ensuring the "Merged" badge displays appropriately.
100-107: LGTM: Composite matching uses strict exact matching.The SQL query correctly implements exact date and exact amount matching as documented in the comment, preventing false positives from loose heuristics.
app/models/simplefin_account/processor.rb (2)
12-14: LGTM: Auto-linking correctly removed.The early return when current_account is missing prevents auto-linking and correctly implements the manual relink flow as agreed in previous reviews. The explicit comment reinforces the intent.
17-22: LGTM: Provider link validation is appropriately positioned.Calling ensure_account_provider! after account processing establishes the AccountProvider relationship for multi-provider tracking. The error handling logs failures without halting the sync, which is appropriate for this non-critical step.
…2). Address all 15 actionable CodeRabbit comments: Add UUID validations in rakes (e.g., simplefin_unlink), swap Ruby pattern matching/loops for efficient DB queries (e.g., where LOWER(name) LIKE ?), generate docstrings for low-coverage areas (31%), consolidate routes for simplefin_items, move view logic to helpers (e.g., format_transaction_extra), strengthen tests with exact assertions/fixtures for dedup/relink failures. Also, check for overlaps with merged we-promise#262 (merchants fix): Ensure merchant creation in simplefin_entry/processor.rb aligns with new payee-based flow and MD5 IDs; add tests for edge cases like empty payees or over-merging pendings. Prioritize security (PII redaction in logs, no hardcoded secrets).
1158e47 to
e707869
Compare
|
Rebased on main |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/models/family/auto_transfer_matchable.rb (1)
63-76: Post-rescue logic executes even when transfer creation was skipped.When
RecordNotUniqueis rescued (line 68), it means another concurrent process created the transfer. However, lines 72-76 still execute, causing:
- Redundant transaction updates that may have already been applied by the concurrent process
- Incorrect tracking in
used_transaction_idsfor transfers not created by this processConsider using
create_or_find_by(Rails 6+) or checking if the transfer is newly created:- begin - Transfer.find_or_create_by!( - inflow_transaction_id: match.inflow_transaction_id, - outflow_transaction_id: match.outflow_transaction_id, - ) - rescue ActiveRecord::RecordNotUnique - # Another concurrent job created the transfer; safe to ignore - end - - Transaction.find(match.inflow_transaction_id).update!(kind: "funds_movement") - Transaction.find(match.outflow_transaction_id).update!(kind: Transfer.kind_for_account(Transaction.find(match.outflow_transaction_id).entry.account)) - - used_transaction_ids << match.inflow_transaction_id - used_transaction_ids << match.outflow_transaction_id + transfer = Transfer.create_or_find_by( + inflow_transaction_id: match.inflow_transaction_id, + outflow_transaction_id: match.outflow_transaction_id + ) + + # Only update transactions if this process created the transfer + if transfer.previously_new_record? + Transaction.find(match.inflow_transaction_id).update!(kind: "funds_movement") + Transaction.find(match.outflow_transaction_id).update!(kind: Transfer.kind_for_account(Transaction.find(match.outflow_transaction_id).entry.account)) + + used_transaction_ids << match.inflow_transaction_id + used_transaction_ids << match.outflow_transaction_id + endapp/models/simplefin_entry/processor.rb (1)
10-25: Remove unsupported parameters fromimport_transactioncall.The
processmethod passesnotes,posted_date,transacted_date,pending, andextratoimport_transaction, but the method signature only acceptsexternal_id,amount,currency,date,name,source,category_id, andmerchant. This will raiseArgumentError: unknown keywordat runtime. Either remove these parameters from the call or add support for them inAccount::ProviderImportAdapter#import_transaction(app/models/account/provider_import_adapter.rb:19).
♻️ Duplicate comments (13)
lib/tasks/holdings_tools.rake (1)
26-31: Restore the dry-run default to prevent accidental writes.This issue was already flagged in a previous review. The current logic treats the absence of
dry_runasfalse, causing unintended writes when users expect preview-only behavior. The task description and examples indicate dry-run should be the default.app/models/simplefin_account.rb (1)
26-33: Fix provider reassignment to avoid unique violationsIf a SimpleFIN item is re-linked to a different Sure account, the existing
AccountProviderrow (scoped byprovider_type/provider_id) already exists with the old account. Because thefind_or_create_by!here scopes onaccount: acct, we fail to find it and attempt to insert a new row, tripping the unique index onprovider_type/provider_idand leaving the stale link behind. Please reuse the existing provider row and reassign itsaccountbefore saving, as previously suggested.- AccountProvider.find_or_create_by!( - account: acct, - provider_type: "SimplefinAccount", - provider_id: id - ) + AccountProvider + .find_or_initialize_by(provider_type: "SimplefinAccount", provider_id: id) + .tap do |provider| + provider.account = acct + provider.save! + enddb/migrate/20251030172500_add_cascade_on_account_deletes.rb (1)
3-3: Align migration version with Rails 7.2Please bump this to
ActiveRecord::Migration[7.2]to match the app’s current Rails level so new migrations pick up the right framework defaults.(guides.rubyonrails.org)lib/tasks/simplefin_debug.rake (1)
1-55: Fix timestamp coercion for sorting and pending inference.ISO 8601 strings like
"2025-10-31"still flow throughposted.to_i/trans.to_i, which collapses them to2025. That causes the sample list to sort almost randomly and marks transactions as “pending” when they aren’t. Please coerce to epoch seconds (handling numerics, digit strings, and ISO timestamps) before both sort and comparison; this was already flagged earlier and the bug is still present.+# frozen_string_literal: true + +require "json" +require "time" ... - txs = Array(sfa.raw_transactions_payload) - # Sort by best-known date: posted -> transacted_at -> as-is - txs = txs.map { |t| t.with_indifferent_access } - txs.sort_by! do |t| - posted = t[:posted] - trans = t[:transacted_at] - ts = if posted.is_a?(Numeric) - posted - elsif trans.is_a?(Numeric) - trans - else - 0 - end - -ts - end + txs = sfa.raw_transactions_payload + txs = JSON.parse(txs) if txs.is_a?(String) + txs = Array.wrap(txs).map { |t| t.with_indifferent_access } + + coerce_ts = ->(value) do + case value + when Numeric then value.to_i + when String + stripped = value.strip + return stripped.to_i if stripped.match?(/\A\d+\z/) + Time.parse(stripped).to_i + else + 0 + end + rescue ArgumentError + 0 + end + + txs.sort_by! do |t| + best = coerce_ts.call(t[:posted]) + best = coerce_ts.call(t[:transacted_at]) if best.zero? + -best + end ... - inferred_pending: (trans.present? && posted.present? && posted.to_i > trans.to_i) + inferred_pending: (coerce_ts.call(trans).positive? && coerce_ts.call(posted) > coerce_ts.call(trans))db/schema.rb (1)
28-30: Drop the duplicate SimpleFin provider unique index.
account_providersnow has bothindex_account_providers_on_account_and_provider_typeandindex_account_providers_on_account_id_and_provider_typecovering the same column set. That doubles uniqueness checks and wastes storage without increasing safety. Please update the migration to drop/rename the legacy index instead of layering a second copy, then regenerate the schema so only one remains.lib/tasks/was_merged.rake (1)
18-18: Fix dynamic constant assignmentRuby forbids defining constants inside method bodies. This issue was previously flagged and remains unresolved.
lib/tasks/simplefin_backfill.rake (4)
81-81: Blocker:find_eachwill crash whensfasis an Array.On line 69,
sfascan becomeArray.wrap(sfa), which is a plain Array. Calling.find_eachon an Array raisesNoMethodError. This issue was previously flagged and remains unresolved.Apply this fix:
- sfas.find_each do |sfa| + Array(sfas).each do |sfa|
75-80: Per-SFA summary uses cumulative totals instead of per-SFA counters.Line 174 prints
{ seen: total_seen, matched: total_matched, ... }for each SFA, but these are global counters that accumulate across all SFAs. This misleads operators reviewing per-account stats. This issue was previously flagged and remains unresolved.Initialize per-SFA counters inside the loop at line 81 (e.g.,
s_seen = s_matched = s_updated = s_skipped = s_errors = 0) and increment both the per-SFA and global counters throughout the processing, then use the per-SFA counters in the output at line 174.Also applies to: 174-174
152-152: Guardexternal_idwhen missing and avoid duplicate Processor instantiation.Line 152 builds
external_id = "simplefin_#{t[:id]}"without checking ift[:id]is present, resulting in"simplefin_"when nil. Line 157 instantiatesSimplefinEntry::Processortwice for the same transaction. Both issues were previously flagged and remain unresolved.Guard against missing
t[:id]and instantiate the processor once:adapter = Account::ProviderImportAdapter.new(acct) - external_id = "simplefin_#{t[:id]}" + external_id = t[:id].present? ? "simplefin_#{t[:id]}" : nil if dry_run # Simulate: check if we can composite-match; we won't persist - entry = acct.entries.find_by(external_id: external_id, source: "simplefin") - entry ||= adapter.composite_match(source: "simplefin", name: SimplefinEntry::Processor.new(t, simplefin_account: sfa).send(:name), amount: SimplefinEntry::Processor.new(t, simplefin_account: sfa).send(:amount), date: (posted_d || trans_d)) + entry = external_id && acct.entries.find_by(external_id: external_id, source: "simplefin") + processor = SimplefinEntry::Processor.new(t, simplefin_account: sfa) + entry ||= adapter.composite_match( + source: "simplefin", + name: processor.send(:name), + amount: processor.send(:amount), + date: (posted_d || trans_d) + )Also applies to: 157-157
193-193: Fix cleanup task default: it deletes by default.Line 193 computes
dry_runfrom a truthy check but does NOT default totruewhen blank. Runningsure:simplefin:cleanup_demo_entrieswithout args will delete entries immediately, contrary to operator expectations and the pattern inbackfill_extra. This issue was previously flagged and remains unresolved.Apply this diff to default to
truewhen blank:account_id = (kv["account_id"] || args[:account_id]).presence dry_raw = (kv["dry_run"] || args[:dry_run]).to_s.downcase pattern = (kv["pattern"] || args[:pattern]).presence || "simplefin_posted_demo_%|simplefin_posted_ui" - dry_run = %w[1 true yes y].include?(dry_raw) + dry_run = dry_raw.blank? ? true : %w[1 true yes y].include?(dry_raw)app/controllers/simplefin_items_controller.rb (3)
136-147: Refactor to use thecompute_unlinked_counthelper for consistency.Lines 136-139 duplicate the logic already extracted in
compute_unlinked_count(line 461). Theupdateaction uses the helper (line 67), butcreatedoesn't. This issue was previously flagged and remains unresolved.Apply this diff:
# Recompute unlinked count and clear pending flag when zero begin - unlinked = @simplefin_item.simplefin_accounts - .left_joins(:account, :account_provider) - .where(accounts: { id: nil }, account_providers: { id: nil }) - .count + unlinked = compute_unlinked_count(@simplefin_item) Rails.logger.info("SimpleFin create: unlinked_count=#{unlinked} (controls setup CTA) for item_id=#{@simplefin_item.id}")
202-209: Move synchronous SimpleFin API call to a background job.Line 204 calls
perform_syncsynchronously in the request thread. If the SimpleFin API is slow or unavailable, this blocks the request and degrades user experience. This issue was previously flagged and remains unresolved.Consider using an async pattern similar to the
syncaction:def balances + unless @simplefin_item.syncing? - sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) - SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) + sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) + SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id) + end respond_to do |format| format.html { redirect_back_or_to accounts_path } - format.json { render json: { ok: true, sync_id: sync.id } } + format.json { render json: { ok: true } } end end
428-439: Refactor to use thecompute_unlinked_counthelper.Lines 428-431 duplicate the same query logic found in the
createaction (lines 136-139) and extracted in thecompute_unlinked_counthelper (line 461). This issue was previously flagged and remains unresolved.Apply this diff:
# Recompute unlinked count and clear pending flag when zero begin - unlinked = @simplefin_item.simplefin_accounts - .left_joins(:account, :account_provider) - .where(accounts: { id: nil }, account_providers: { id: nil }) - .count + unlinked = compute_unlinked_count(@simplefin_item) Rails.logger.info("SimpleFin apply_relink: unlinked_count=#{unlinked} (controls setup CTA) for item_id=#{@simplefin_item.id}")
🧹 Nitpick comments (2)
app/models/family/auto_transfer_matchable.rb (1)
72-73: Consider optimizing N+1 queries.The
Transaction.findcalls execute individually for each match within the loop, potentially causing N+1 queries. Consider batch-loading transactions upfront or usingupdate_allwhere appropriate for better performance.Example optimization:
# Before the loop, collect all transaction IDs transaction_ids = candidates_scope.pluck(:inflow_transaction_id, :outflow_transaction_id).flatten.uniq transactions_by_id = Transaction.where(id: transaction_ids).includes(entry: :account).index_by(&:id) # Inside the loop, use the preloaded hash inflow_txn = transactions_by_id[match.inflow_transaction_id] outflow_txn = transactions_by_id[match.outflow_transaction_id] inflow_txn.update!(kind: "funds_movement") outflow_txn.update!(kind: Transfer.kind_for_account(outflow_txn.entry.account))app/views/transactions/show.html.erb (1)
102-145: Localize the new drawer label and strings.Everything else in this view pulls copy from
t(".…"), but the new “Additional details”, “Provider extras”, and field labels are hard-coded. Please move them into the locale file so the UI stays translatable and consistent.
There was a problem hiding this 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 (3)
app/views/simplefin_items/_simplefin_item.html.erb (3)
45-45: Replace bare rescue with explicit exception class.Bare
rescuecatches all exceptions includingSystemExitandSignalException. Userescue StandardErrorfor safer error handling.
198-198: Replace bare rescue with explicit exception class.Same issue as line 45: bare
rescueshould berescue StandardError.
214-217: Move business logic out of the view.Computing
unlinked_countdirectly in the ERB template violates the guideline to keep domain logic out of templates. This query logic should be computed in the controller or a helper method and passed as a local variable.
🧹 Nitpick comments (2)
app/models/account/provider_import_adapter.rb (2)
66-68: Remove inconsistentrespond_to?check.The
respond_to?(:enrich_attribute)check is inconsistent with the enrichment pattern used elsewhere in this method (lines 55, 59, 63). Ifentrydoesn't respond toenrich_attribute, those other calls would also fail. This defensive check adds unnecessary complexity without clear benefit.Apply this diff to align with the existing pattern:
-if notes.present? && entry.respond_to?(:enrich_attribute) +if notes.present? entry.enrich_attribute(:notes, notes, source: source) end
70-75: Verify the non-enriched deep merge strategy for extra metadata.The
extrafield usesdeep_mergeand explicitly bypasses the enrichment pattern (comment: "non-enriched; always merged"). This means:
- User modifications to
transaction.extrawill be overwritten on subsequent syncs- Partial updates to nested keys in
extrawill merge recursively, which might not match caller intent- Non-Hash values are silently converted to empty hashes (
extra.is_a?(Hash) ? extra.deep_stringify_keys : {})Confirm this design is intentional. If
extrashould respect user overrides like other fields, consider adopting the enrichment pattern. If not, document why this field behaves differently.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
app/models/account/provider_import_adapter.rb(2 hunks)app/models/simplefin_entry/processor.rb(3 hunks)app/views/simplefin_items/_simplefin_item.html.erb(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/models/simplefin_entry/processor.rb
🧰 Additional context used
📓 Path-based instructions (16)
app/models/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)
Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods
Use ActiveRecord validations for forms and complex domain constraints.
app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required
Files:
app/models/account/provider_import_adapter.rb
app/**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/
Files:
app/models/account/provider_import_adapter.rb
**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Files:
app/models/account/provider_import_adapter.rb
**/*.{rb,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.
Files:
app/models/account/provider_import_adapter.rb
app/{helpers,models}/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Format currencies, numbers, and dates on the server side (Ruby) before sending to the client
Files:
app/models/account/provider_import_adapter.rb
app/{models,controllers,views}/**/*.{rb,erb}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid N+1 queries
Files:
app/models/account/provider_import_adapter.rbapp/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.erb
📄 CodeRabbit inference engine (AGENTS.md)
app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.ymlAlways use the icon helper (icon) for icons; never call lucide_icon directly
Files:
app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.
Files:
app/views/simplefin_items/_simplefin_item.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.
Files:
app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible
Files:
app/views/simplefin_items/_simplefin_item.html.erb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions
Files:
app/views/simplefin_items/_simplefin_item.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views
Files:
app/views/simplefin_items/_simplefin_item.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)
Files:
app/views/simplefin_items/_simplefin_item.html.erb
{app/views/**,app/helpers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Always generate semantic HTML
Files:
app/views/simplefin_items/_simplefin_item.html.erb
{app/views,app/components}/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript
Files:
app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/_*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)
Files:
app/views/simplefin_items/_simplefin_item.html.erb
🧠 Learnings (8)
📓 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: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
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/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-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Applied to files:
app/models/account/provider_import_adapter.rbapp/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{entry,valuation,transaction,trade}.rb : Entry amounts are signed: negative = inflow, positive = outflow (applies to Entry and its types: Valuation, Transaction, Trade)
Applied to files:
app/models/account/provider_import_adapter.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/models/account/provider_import_adapter.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to app/views/**/*.erb : Keep heavy logic out of ERB views; prefer helpers/components instead
Applied to files:
app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Applied to files:
app/views/simplefin_items/_simplefin_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/simplefin_items/_simplefin_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/simplefin_items/_simplefin_item.html.erb
🧬 Code graph analysis (1)
app/models/account/provider_import_adapter.rb (2)
app/models/simplefin_entry/processor.rb (4)
date(103-118)notes(187-199)posted_date(120-137)transacted_date(139-156)app/models/concerns/enrichable.rb (1)
enrich_attribute(26-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci / test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (10)
app/views/simplefin_items/_simplefin_item.html.erb (4)
154-213: Move latest sync computation to controller to eliminate redundant queries.Line 155 queries
simplefin_item.syncs.ordered.firstinline, duplicating logic that already computeslatest_syncin the controller (line 16 ofaccounts_controller.rb). While theincludes(:syncs)prevents N+1, this duplication is unnecessary.Pass
@latest_syncand@sync_statsas locals or instance variables and reference them here:- <% if (latest_sync = simplefin_item.syncs.ordered.first).present? %> - <% stats = latest_sync.sync_stats || {} %> + <% latest_sync_for_item = @simplefin_latest_sync_map[simplefin_item.id] %> + <% if latest_sync_for_item.present? %> + <% stats = latest_sync_for_item.sync_stats || {} %>Update the controller to also populate
@simplefin_latest_sync_mapalongside the stats map.As per coding guidelines.
46-46: [STILL UNFIXED] Replace bare rescue with explicit exception class.Despite the past review comment, line 46 still uses a bare
rescuethat catches all exceptions includingSystemExitandSignalException. Inline rescues should explicitly catchStandardError.Apply this diff:
- <% ago = (ts.present? ? (time_ago_in_words(Time.parse(ts)) rescue nil) : nil) %> + <% ago = (ts.present? ? (time_ago_in_words(Time.parse(ts)) rescue StandardError nil) : nil) %>
200-200: [STILL UNFIXED] Replace bare rescue with explicit exception class.Line 200 has the same bare
rescueissue as line 46. Userescue StandardErrorto avoid catching system-level exceptions.Apply this diff:
- <% ago = (ts.present? ? (time_ago_in_words(Time.parse(ts)) rescue nil) : nil) %> + <% ago = (ts.present? ? (time_ago_in_words(Time.parse(ts)) rescue StandardError nil) : nil) %>
216-219: Move unlinked count computation to controller.Lines 216-219 execute a database query inline in the view, violating the guideline to keep domain logic out of ERB templates. This same query is already used to populate
@simplefin_has_unlinked_mapin the controller; extend that logic to also compute the count.Move this to the controller:
# In controller before rendering: @simplefin_unlinked_count_map = {} @simplefin_items.each do |item| @simplefin_unlinked_count_map[item.id] = item.simplefin_accounts .left_joins(:account, :account_provider) .where(accounts: { id: nil }, account_providers: { id: nil }) .count endThen reference
@simplefin_unlinked_count_map[simplefin_item.id]in the view.As per coding guidelines.
app/controllers/simplefin_items_controller.rb (3)
137-140: Use thecompute_unlinked_counthelper for consistency.Lines 137-140 duplicate the unlinked count query inline instead of using the
compute_unlinked_counthelper (fromSimplefinItems::RelinkHelpers, line 68 of this file shows it's already used inupdate).Apply this diff:
# Recompute unlinked count and clear pending flag when zero begin - unlinked = @simplefin_item.simplefin_accounts - .left_joins(:account, :account_provider) - .where(accounts: { id: nil }, account_providers: { id: nil }) - .count + unlinked = compute_unlinked_count(@simplefin_item) Rails.logger.info("SimpleFin create: unlinked_count=#{unlinked} (controls setup CTA) for item_id=#{@simplefin_item.id}")
203-211: Move synchronous sync to background job.Line 205 calls
perform_syncsynchronously in the request thread, blocking until the SimpleFin API completes. This is identical to the issue flagged in the past review for lines 203-210.Use an async pattern:
def balances - sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) - SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) + unless @simplefin_item.syncing? + sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) + SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id) + end respond_to do |format| format.html { redirect_back_or_to accounts_path } - format.json { render json: { ok: true, sync_id: sync.id } } + format.json { render json: { ok: true } } end end
122-133: Move synchronous balances-only import to background job.Lines 123-133 perform a synchronous
import_balances_onlycall during the HTTP request, similar to the issue in theupdateaction (lines 56-64). This blocks the user and consumes a request thread while waiting for the SimpleFin API.Apply the same async pattern suggested for the
updateaction, or document why synchronous execution is acceptable if this is an intentional lightweight operation.lib/tasks/holdings_tools.rake (1)
26-31: [STILL UNFIXED] Dry-run defaults to destructive writes.Despite the past review comment being marked as addressed, the current code still treats absent
dry_runarguments asfalse, causing the task to persist snapshots by default. When bothkv["dry_run"]andargs[:dry_run]arenil, line 29 produces"", and line 30 evaluates tofalse, enabling writes without explicit user consent.Apply this diff to restore safe preview-by-default behavior:
holding_id = (kv["holding_id"] || args[:holding_id]).presence change_pct = ((kv["change_pct"] || args[:change_pct] || 2).to_f) / 100.0 days_ago = (kv["days_ago"] || args[:days_ago] || 1).to_i dry_raw = (kv["dry_run"] || args[:dry_run]).to_s.downcase - dry_run = %w[1 true yes y].include?(dry_raw) + dry_run = dry_raw.blank? ? true : %w[1 true yes y].include?(dry_raw)lib/tasks/simplefin_fix_was_merged.rake (2)
72-124: [CRITICAL] Transaction boundary still excludes recompute phase.Despite the past review comment being marked as addressed, the clear phase (lines 73-85) and recompute phase (lines 101-120) remain in separate transactions. If the recompute phase fails after the clear transaction commits,
was_mergedflags are permanently lost without restoration, leaving the database in an inconsistent state.Wrap both phases in a single transaction when
recomputeis enabled:unless dry_run - ActiveRecord::Base.transaction do + ActiveRecord::Base.transaction do # Clear flags first entries_scope.includes(:entryable).find_each do |e| t = e.entryable next unless t.is_a?(Transaction) next unless t.respond_to?(:was_merged) if t.was_merged t.update!(was_merged: false) cleared += 1 end end - end - if recompute + if recompute # Try to reprocess SimpleFin raw payload for the linked SimplefinAccount sfa = begin # Prefer AccountProvider linkage ap = acct.account_providers.where(provider_type: "SimplefinAccount").first ap&.provider rescue nil end sfa ||= SimplefinAccount.find_by(account: acct) if sfa && sfa.raw_transactions_payload.present? txs = Array(sfa.raw_transactions_payload).map { |t| t.with_indifferent_access } - ActiveRecord::Base.transaction do txs.each do |t| begin posted_d = Simplefin::DateUtils.parse_provider_date(t[:posted]) trans_d = Simplefin::DateUtils.parse_provider_date(t[:transacted_at]) best = posted_d || trans_d next if best.nil? || best < window_start || best > window_end # Re-run processor (idempotent); current heuristics will set was_merged where appropriate SimplefinEntry::Processor.new(t, simplefin_account: sfa).process updated += 1 rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound => e errors += 1 puts({ warn: "recompute_error", message: e.message, tx_id: t[:id] }.to_json) rescue ArgumentError, TypeError => e errors += 1 puts({ warn: "recompute_parse_error", message: e.message, tx_id: t[:id] }.to_json) end end - end else puts({ info: "no_raw_transactions", message: "Unable to recompute without raw SimpleFin payload" }.to_json) end + end - end + end end
89-95: [STILL UNFIXED] Replace bare rescue with explicit exception class.Despite being marked as addressed in past commits, line 93 still uses a bare
rescuethat catches all exceptions, including system-level signals. While the intent is to fallback tonilwhen provider linkage fails, the rescue should explicitly catchStandardErrorto avoid suppressing critical system exceptions.Apply this diff:
sfa = begin # Prefer AccountProvider linkage ap = acct.account_providers.where(provider_type: "SimplefinAccount").first ap&.provider - rescue + rescue StandardError nil end
🧹 Nitpick comments (2)
app/controllers/accounts_controller.rb (1)
15-22: Optimize per-item unlinked query to reduce redundant database calls.Lines 18-21 execute the same
family.accounts.left_joins(:account_providers)...query once for eachsimplefin_item, even though all items share the same family. Since the family context is constant, computinghas_unlinked_accountsonce before the loop (or restructuring the query to compute per-item status in bulk) would eliminate redundant queries.Consider computing the unlinked status outside the loop:
@simplefin_sync_stats_map = {} @simplefin_has_unlinked_map = {} + + # Compute once: does this family have any unlinked accounts? + family_has_unlinked = family.accounts + .left_joins(:account_providers) + .where(account_providers: { id: nil }) + .exists? @simplefin_items.each do |item| latest_sync = item.syncs.ordered.first @simplefin_sync_stats_map[item.id] = (latest_sync&.sync_stats || {}) - @simplefin_has_unlinked_map[item.id] = item.family.accounts - .left_joins(:account_providers) - .where(account_providers: { id: nil }) - .exists? + @simplefin_has_unlinked_map[item.id] = family_has_unlinked endAlternatively, if per-item granularity is required (checking which items have unlinked accounts under their scope), clarify the intent with a comment.
lib/simplefin/date_utils.rb (1)
7-26: LGTM—robust date parsing utility.The implementation correctly handles multiple provider date formats (String, Numeric epoch, Time/DateTime, Date) with appropriate error handling. The rescue clause properly catches
ArgumentErrorandTypeError, returningnilfor unparseable inputs.However, note that
SimplefinEntry::Processor(lines 117-153) duplicates similar date-parsing logic inline. Consider refactoring the processor to use this utility for consistency:def posted_date Simplefin::DateUtils.parse_provider_date(data[:posted]) end def transacted_date Simplefin::DateUtils.parse_provider_date(data[:transacted_at]) end
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
app/controllers/accounts_controller.rb(1 hunks)app/controllers/simplefin_items_controller.rb(6 hunks)app/models/account/provider_import_adapter.rb(2 hunks)app/models/simplefin_entry/processor.rb(3 hunks)app/views/simplefin_items/_simplefin_item.html.erb(3 hunks)lib/simplefin/date_utils.rb(1 hunks)lib/tasks/holdings_tools.rake(1 hunks)lib/tasks/simplefin_fix_was_merged.rake(1 hunks)lib/tasks/simplefin_unlink.rake(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- lib/tasks/simplefin_unlink.rake
- app/models/account/provider_import_adapter.rb
🧰 Additional context used
📓 Path-based instructions (16)
app/views/**/*.erb
📄 CodeRabbit inference engine (AGENTS.md)
app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.ymlAlways use the icon helper (icon) for icons; never call lucide_icon directly
Files:
app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.
Files:
app/views/simplefin_items/_simplefin_item.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.
Files:
app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible
Files:
app/views/simplefin_items/_simplefin_item.html.erb
app/{models,controllers,views}/**/*.{rb,erb}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid N+1 queries
Files:
app/views/simplefin_items/_simplefin_item.html.erbapp/models/simplefin_entry/processor.rbapp/controllers/simplefin_items_controller.rbapp/controllers/accounts_controller.rb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions
Files:
app/views/simplefin_items/_simplefin_item.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views
Files:
app/views/simplefin_items/_simplefin_item.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)
Files:
app/views/simplefin_items/_simplefin_item.html.erb
{app/views/**,app/helpers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Always generate semantic HTML
Files:
app/views/simplefin_items/_simplefin_item.html.erb
{app/views,app/components}/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript
Files:
app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/_*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)
Files:
app/views/simplefin_items/_simplefin_item.html.erb
app/models/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)
Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods
Use ActiveRecord validations for forms and complex domain constraints.
app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required
Files:
app/models/simplefin_entry/processor.rb
app/**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/
Files:
app/models/simplefin_entry/processor.rbapp/controllers/simplefin_items_controller.rbapp/controllers/accounts_controller.rb
**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Files:
app/models/simplefin_entry/processor.rbapp/controllers/simplefin_items_controller.rblib/simplefin/date_utils.rbapp/controllers/accounts_controller.rb
**/*.{rb,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.
Files:
app/models/simplefin_entry/processor.rbapp/controllers/simplefin_items_controller.rblib/simplefin/date_utils.rbapp/controllers/accounts_controller.rb
app/{helpers,models}/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Format currencies, numbers, and dates on the server side (Ruby) before sending to the client
Files:
app/models/simplefin_entry/processor.rb
🧠 Learnings (20)
📓 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: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
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/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.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator
Applied to files:
lib/tasks/holdings_tools.rake
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Applied to files:
lib/tasks/simplefin_fix_was_merged.rakeapp/views/simplefin_items/_simplefin_item.html.erbapp/models/simplefin_entry/processor.rbapp/controllers/simplefin_items_controller.rbapp/controllers/accounts_controller.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to **/*.{rb,js,jsx,ts,tsx} : Make changes atomic, testable, and explain their impact briefly in code suggestions.
Applied to files:
lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Applied to files:
lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator
Applied to files:
lib/tasks/simplefin_fix_was_merged.rakeapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to app/views/**/*.erb : Keep heavy logic out of ERB views; prefer helpers/components instead
Applied to files:
app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Applied to files:
app/views/simplefin_items/_simplefin_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/simplefin_items/_simplefin_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/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries
Applied to files:
app/views/simplefin_items/_simplefin_item.html.erbapp/controllers/simplefin_items_controller.rbapp/controllers/accounts_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/**/*.erb : Be mindful of performance in global layouts (e.g., avoid loading large data payloads)
Applied to files:
app/views/simplefin_items/_simplefin_item.html.erbapp/controllers/accounts_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs
Applied to files:
app/views/simplefin_items/_simplefin_item.html.erbapp/controllers/simplefin_items_controller.rbapp/controllers/accounts_controller.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to app/components/**/* : Logic belongs in component files, not in component template (*.html.erb, *.html.slim) files.
Applied to files:
app/views/simplefin_items/_simplefin_item.html.erb
📚 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/views/simplefin_items/_simplefin_item.html.erbapp/controllers/simplefin_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/models/simplefin_entry/processor.rbapp/controllers/simplefin_items_controller.rb
📚 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/simplefin_items_controller.rbapp/controllers/accounts_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/simplefin_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/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Applied to files:
app/controllers/simplefin_items_controller.rb
🧬 Code graph analysis (3)
app/models/simplefin_entry/processor.rb (1)
app/models/simplefin_account/transactions/processor.rb (1)
account(39-41)
app/controllers/simplefin_items_controller.rb (7)
app/models/simplefin_item.rb (3)
include(1-301)dedup_simplefin_accounts!(163-226)merge_duplicate_provider_accounts!(230-274)app/models/simplefin_item/relink_service.rb (2)
new(3-122)apply!(14-121)app/models/simplefin_item/provided.rb (1)
simplefin_provider(4-6)app/models/simplefin_item/importer.rb (1)
import_balances_only(35-68)app/controllers/concerns/simplefin_items/relink_helpers.rb (2)
compute_unlinked_count(10-15)compute_relink_candidates(23-114)app/services/simplefin_item/unlinker.rb (1)
unlink_all!(11-32)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)
app/controllers/accounts_controller.rb (1)
app/models/concerns/syncable.rb (1)
latest_sync(67-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci / test
🔇 Additional comments (10)
app/views/simplefin_items/_simplefin_item.html.erb (2)
30-68: Good use of precomputed stats to avoid N+1 queries.Lines 31-32 correctly reference
@simplefin_sync_stats_map[simplefin_item.id]precomputed in the controller (seeapp/controllers/accounts_controller.rblines 12-17), eliminating the N+1 query concern from the past review. The fallback todefined?(sync_stats)preserves flexibility for different rendering contexts.
123-133: Good use of precomputed unlinked status.Line 124 correctly references
@simplefin_has_unlinked_map[simplefin_item.id]precomputed in the controller (seeapp/controllers/accounts_controller.rblines 13, 18-21), addressing the past concern about executing database queries in the view.app/models/simplefin_entry/processor.rb (4)
27-36: Well-structured metadata collection.The
extra_metadatamethod cleanly preserves raw provider data under a namespaced"simplefin"key, preventing data loss and supporting future extensibility. The method correctly checks for key presence and type guardsextraas a Hash.
96-115: Date selection logic is sound.The account-type-specific date preference (lines 102-112) appropriately prioritizes transaction dates for credit cards/loans (matching statement logic) while preferring posted dates for other accounts. Raising
ArgumentErrorwhen both dates are missing (line 114) ensures data integrity.
155-162: Pending inference logic is appropriate.The method correctly prioritizes an explicit
pendingflag (line 158) while providing a reasonable fallback heuristic (posted date after transacted date suggests a pending transaction, line 161). The nil-safe checks prevent errors.
184-196: Well-composed notes field.The
notesmethod provides useful context by including memo and conditionally adding payee information when it differs from description (lines 192-194). The separator and nil-handling (line 195) keep the output clean and concise.app/controllers/simplefin_items_controller.rb (4)
213-230: Well-structured setup_accounts filtering with observability.Lines 215-219 safely attempt deduplication with error recovery, and lines 221-224 correctly use
includesto eager-load associations before filtering for unlinked accounts, avoiding N+1 queries. The logging (lines 227-229) provides useful observability for the setup flow.
300-316: Clean error modal implementation.The
errorsaction appropriately fetches the latest sync stats (line 302), normalizes error data for display (lines 306-313), and renders without layout for modal presentation (line 315). The symbol/string key handling (lines 308-311) ensures compatibility with different serialization contexts.
330-352: Well-structured relink application with service delegation.The
apply_relinkaction correctly delegates business logic toSimplefinItem::RelinkService(lines 335-339), handles multiple response formats (Turbo Stream, HTML, JSON), and provides detailed result data (line 350). The Turbo Stream response properly removes the modal and updates the item card (lines 344-347).
359-362: Appropriate family scoping for security.Lines 360-361 correctly scope the lookup to
Current.familywhen available, ensuring users can only access SimpleFin items within their family context, while providing a fallback for admin or system contexts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
app/controllers/simplefin_items_controller.rb (2)
119-122: Use thecompute_unlinked_counthelper for consistency.This query duplicates the logic already extracted in the
compute_unlinked_counthelper (defined inSimplefinItems::RelinkHelpers). Theupdateaction uses the helper correctly at Line 60.Apply this diff:
- unlinked = @simplefin_item.simplefin_accounts - .left_joins(:account, :account_provider) - .where(accounts: { id: nil }, account_providers: { id: nil }) - .count + unlinked = compute_unlinked_count(@simplefin_item)
186-187: Move synchronous SimpleFin sync to background job.Line 187 calls
perform_syncsynchronously in the request thread. If the SimpleFin API is slow or unavailable, this blocks the request and degrades user experience. This is a reliability anti-pattern and was flagged in a previous review.Consider using an async pattern consistent with the
syncaction:def balances - sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) - SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) + unless @simplefin_item.syncing? + sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) + SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id) + end respond_to do |format| format.html { redirect_back_or_to accounts_path } - format.json { render json: { ok: true, sync_id: sync.id } } + format.json { render json: { ok: true } } end endAlternatively, leverage the existing
sync_laterinfrastructure if it can accept a balances-only flag.
🧹 Nitpick comments (1)
app/jobs/simplefin_item/balances_only_job.rb (1)
20-20: Optional: Simplify redundant exception classes.
ArgumentErroris redundant since it's already covered byStandardError.Apply this diff to simplify:
- rescue Provider::Simplefin::SimplefinError, ArgumentError, StandardError => e + rescue Provider::Simplefin::SimplefinError, StandardError => e
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/controllers/simplefin_items_controller.rb(6 hunks)app/jobs/simplefin_item/balances_only_job.rb(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
app/**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/
Files:
app/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rb
**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Files:
app/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rb
**/*.{rb,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.
Files:
app/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rb
app/{models,controllers,views}/**/*.{rb,erb}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid N+1 queries
Files:
app/controllers/simplefin_items_controller.rb
🧠 Learnings (12)
📓 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: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
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/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.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Applied to files:
app/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator
Applied to files:
app/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_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/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator
Applied to files:
app/jobs/simplefin_item/balances_only_job.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/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rb
📚 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/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs
Applied to files:
app/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_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/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries
Applied to files:
app/controllers/simplefin_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/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Applied to files:
app/controllers/simplefin_items_controller.rb
🧬 Code graph analysis (2)
app/jobs/simplefin_item/balances_only_job.rb (3)
app/models/simplefin_item/provided.rb (1)
simplefin_provider(4-6)app/models/simplefin_item/importer.rb (1)
import_balances_only(35-68)app/models/simplefin_item.rb (2)
dedup_simplefin_accounts!(163-226)merge_duplicate_provider_accounts!(230-274)
app/controllers/simplefin_items_controller.rb (5)
app/models/simplefin_item.rb (2)
include(1-301)dedup_simplefin_accounts!(163-226)app/controllers/concerns/simplefin_items/relink_helpers.rb (2)
compute_unlinked_count(10-15)compute_relink_candidates(23-114)app/models/simplefin_item/relink_service.rb (2)
new(3-122)apply!(14-121)app/services/simplefin_item/unlinker.rb (1)
unlink_all!(11-32)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)
🔇 Additional comments (11)
app/controllers/simplefin_items_controller.rb (8)
1-4: LGTM!The additions are appropriate:
Setsupports the relink candidate tracking,RelinkHelperscentralizes relink logic, and all new actions are correctly listed in thebefore_action.
55-82: LGTM! Async job pattern and relink flow improve UX.The switch to
SimplefinItem::BalancesOnlyJob.perform_lateraddresses the past concern about blocking requests. The relink candidate detection and auto-modal flow provide smooth onboarding when matches exist.
162-170: LGTM! Proper unlinking before deletion.The addition of
SimplefinItem::Unlinkerensures provider-linked accounts are safely moved to "Other accounts" before the item is destroyed. Error handling is appropriate.
197-211: LGTM! Deduplication and improved filtering.The deduplication safeguard prevents duplicate UI rows, and the
includes(:account, :account_provider)prevents N+1 queries. The logging provides good observability for setup account filtering.
282-298: LGTM! Clean error listing action.The action safely extracts per-account errors from the latest sync stats with defensive programming (
|| {},Array()), and the key normalization handles both symbol and string keys gracefully.
300-310: LGTM! Clean relink modal actions.Both actions appropriately delegate to
compute_relink_candidatesand render modal-friendly views. The explicitmanual_relinkendpoint adds clarity to the user flow without significant duplication.
312-334: LGTM! Well-structured apply_relink action.The action properly filters checked pairs, delegates to
SimplefinItem::RelinkService, and returns appropriate responses for multiple formats. The Turbo Stream response correctly removes the modal and updates the SimpleFin card.
341-344: LGTM! Proper scoping with fallback.The
set_simplefin_itemmethod correctly respects the current family context when available and safely falls back to the global scope.app/jobs/simplefin_item/balances_only_job.rb (3)
1-4: LGTM - Standard ActiveJob setup.The job class follows Rails conventions with proper frozen string literal pragma and explicit queue assignment.
6-14: LGTM - Defensive item lookup.The documentation clearly describes the job's operations, and the use of
find_bywith an early return guard is appropriate for gracefully handling missing records without raising exceptions.
24-41: Code at lines 24-41 is correct—best-effort cleanup is appropriately handled.The verification confirms that
Account#sync_datadoes not exist in the codebase, and SimpleFin's balances-only job is intentionally designed as a lightweight operation. The best-effort error handling (barerescuewith logging) is the correct approach for non-critical cleanup operations that should not fail the job. The job's documented scope—import_balances_only, dedup, merge, and updatelast_synced_at—is properly implemented without requiring additional orchestration that doesn't exist in the codebase.
|
How do you guys feel about the remaining bot-comments @luckyPipewrench and @sokie? |
I am waiting for @luckyPipewrench to finish all the changes and once ready I'll do a proper reveiw |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
app/controllers/simplefin_items_controller.rb (1)
126-129: Use the compute_unlinked_count helper for consistency.Lines 126-129 duplicate the query logic that's already extracted in the
compute_unlinked_counthelper (from SimplefinItems::RelinkHelpers). Theupdateaction uses the helper at line 60, butcreatedoesn't.Apply this diff:
- unlinked = @simplefin_item.simplefin_accounts - .left_joins(:account, :account_provider) - .where(accounts: { id: nil }, account_providers: { id: nil }) - .count + unlinked = compute_unlinked_count(@simplefin_item)
🧹 Nitpick comments (6)
app/controllers/concerns/simplefin_items/relink_helpers.rb (2)
25-25: Consider logging dedup failures instead of silently swallowing them.The
rescue nilsuppresses all errors fromdedup_simplefin_accounts!, which could hide legitimate issues like database connectivity problems or data integrity violations.Replace with:
- @simplefin_item.dedup_simplefin_accounts! rescue nil + begin + @simplefin_item.dedup_simplefin_accounts! + rescue => e + Rails.logger.warn("SimpleFin compute_relink_candidates: dedup failed: #{e.class} - #{e.message}") + end
66-72: Consider narrowing the rescue scope to specific exceptions.The bare
rescueon line 71 catches all exceptions including system errors. Since this is handling BigDecimal conversion and arithmetic, consider rescuingArgumentErrororTypeErrorspecifically.Apply this diff:
- rescue + rescue ArgumentError, TypeErrorThe same applies to the rescue block at lines 86-88.
app/jobs/simplefin_item/balances_only_job.rb (2)
16-22: Clarify why ArgumentError is rescued alongside SimplefinError.Line 20 rescues
ArgumentErroralongside provider-specific errors. If this is expected (e.g., from invalid access URL parsing), document it with a comment; otherwise, consider removing it to avoid masking unrelated argument errors.
44-53: Consider extracting the Turbo broadcast logic to a helper method.The inline ERB template construction (lines 46-49) is fragile and mixes concerns. Since this pattern may be reused, extract it to a helper method in the controller or a service object.
For example, in the controller or a concern:
def broadcast_relink_modal_refresh(item) url = relink_simplefin_item_path(item) html = ApplicationController.render( inline: "<turbo-frame id='modal' src='#{ERB::Util.html_escape(url)}'></turbo-frame>", formats: [:html] ) Turbo::StreamsChannel.broadcast_replace_to(item.family, target: "modal", html: html) endtest/controllers/simplefin_items_controller_test.rb (1)
67-70: Consider extracting the URI parsing pattern to a test helper.The pattern of parsing
response.redirect_urland asserting onuri.pathis repeated at lines 67-70, 158-161, and 230-232. Extract this to a helper method to reduce duplication.For example:
def assert_redirected_to_path(expected_path) assert_response :redirect uri = URI(response.redirect_url) assert_equal expected_path, uri.path endThen use:
assert_redirected_to_path("/accounts")app/controllers/simplefin_items_controller.rb (1)
1-1: Remove unused require or document its purpose.Line 1 adds
require "set"but Set is not used anywhere in this controller. Either remove it or add a comment explaining why it's needed for future use.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
app/controllers/concerns/simplefin_items/relink_helpers.rb(1 hunks)app/controllers/simplefin_items_controller.rb(7 hunks)app/jobs/simplefin_item/balances_only_job.rb(1 hunks)app/views/simplefin_items/relink.html.erb(1 hunks)test/controllers/simplefin_items_controller_test.rb(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/views/simplefin_items/relink.html.erb
🧰 Additional context used
📓 Path-based instructions (5)
test/**/*_test.rb
📄 CodeRabbit inference engine (AGENTS.md)
Name Minitest files with *_test.rb and mirror the app/ structure under test/
test/**/*_test.rb: ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Use Mocha for mocking in tests when necessary.
Use VCR for external API tests.
test/**/*_test.rb: Always use Minitest for tests; do not use RSpec
Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Only write tests for critical and important code paths
Avoid tests that merely verify framework/ActiveRecord behavior
Test boundaries correctly: for queries, assert returned values; for commands, assert collaborators are called with correct params
Never test the implementation details of one class in another class’s test suite
Use the mocha gem for stubs and mocks
Prefer OpenStruct for mock instances; use a mock class for complex cases
Only mock what’s necessary; don’t mock return values unless they are under test
Files:
test/controllers/simplefin_items_controller_test.rb
**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Files:
test/controllers/simplefin_items_controller_test.rbapp/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rbapp/controllers/concerns/simplefin_items/relink_helpers.rb
**/*.{rb,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.
Files:
test/controllers/simplefin_items_controller_test.rbapp/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rbapp/controllers/concerns/simplefin_items/relink_helpers.rb
app/**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/
Files:
app/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rbapp/controllers/concerns/simplefin_items/relink_helpers.rb
app/{models,controllers,views}/**/*.{rb,erb}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid N+1 queries
Files:
app/controllers/simplefin_items_controller.rbapp/controllers/concerns/simplefin_items/relink_helpers.rb
🧠 Learnings (23)
📓 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: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
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/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:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/**/*_test.rb : ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Avoid tests that merely verify framework/ActiveRecord behavior
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases; create edge cases inline in tests
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Use fixtures for test data rather than factories
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases.
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.turbo_stream.erb : Use Turbo Streams to enhance functionality but avoid hard dependencies on them
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Applied to files:
test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Applied to files:
app/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator
Applied to files:
app/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_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/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator
Applied to files:
app/jobs/simplefin_item/balances_only_job.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/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rb
📚 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/jobs/simplefin_item/balances_only_job.rbapp/controllers/simplefin_items_controller.rbapp/controllers/concerns/simplefin_items/relink_helpers.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs
Applied to files:
app/controllers/simplefin_items_controller.rbapp/controllers/concerns/simplefin_items/relink_helpers.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/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries
Applied to files:
app/controllers/simplefin_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/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Applied to files:
app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/models/concerns/**/*.rb : Use Rails concerns organized around model traits (not as generic code dumps)
Applied to files:
app/controllers/concerns/simplefin_items/relink_helpers.rb
🧬 Code graph analysis (3)
app/jobs/simplefin_item/balances_only_job.rb (3)
app/models/simplefin_item/provided.rb (1)
simplefin_provider(4-6)app/models/simplefin_item/importer.rb (1)
import_balances_only(35-68)app/models/simplefin_item.rb (2)
dedup_simplefin_accounts!(163-226)merge_duplicate_provider_accounts!(230-274)
app/controllers/simplefin_items_controller.rb (5)
app/models/simplefin_item.rb (3)
include(1-301)destroy_later(36-39)dedup_simplefin_accounts!(163-226)app/controllers/concerns/simplefin_items/relink_helpers.rb (2)
compute_unlinked_count(10-15)compute_relink_candidates(23-114)app/models/simplefin_item/relink_service.rb (2)
new(3-122)apply!(14-121)app/services/simplefin_item/unlinker.rb (1)
unlink_all!(11-32)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)
app/controllers/concerns/simplefin_items/relink_helpers.rb (1)
app/models/simplefin_item.rb (1)
dedup_simplefin_accounts!(163-226)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci / test
🔇 Additional comments (13)
app/controllers/concerns/simplefin_items/relink_helpers.rb (3)
10-15: LGTM!The unlinked count query correctly identifies SimpleFin accounts with no Account link (legacy FK) and no AccountProvider link (new polymorphic).
17-21: LGTM!The name normalization logic is appropriate for fuzzy matching and handles nil/empty inputs safely.
39-109: Well-designed matching heuristics with ambiguity guards.The three-tier matching strategy (last4 → balance proximity → normalized name) with ambiguity guards (skipping when multiple candidates match) effectively balances automation with safety. The used_manual_ids tracking prevents duplicate assignments.
app/jobs/simplefin_item/balances_only_job.rb (1)
6-41: Defensive error handling pattern prevents cascading failures.Each operation (import, dedup, merge, timestamp update) is isolated in its own rescue block, ensuring that failures in one step don't prevent subsequent cleanup or UI updates. This is appropriate for a best-effort background job.
test/controllers/simplefin_items_controller_test.rb (4)
244-256: Good coverage of destroy unlinking behavior.The test verifies that destroying a SimplefinItem immediately unlinks provider links and legacy FK references, even though the actual deletion is scheduled. This confirms the fix for the unlink-before-delete requirement.
258-280: Good coverage of apply_relink deduplication.The test validates that
apply_relinkmoves the provider link to the manual account and removes the duplicate provider-backed account, confirming the relink service cleanup behavior.
282-307: Good coverage of selective account creation during setup.The test confirms that
complete_account_setuponly creates accounts for truly unlinked SimpleFin accounts while preserving existing links, preventing duplicate account creation.
324-346: Good coverage of Turbo Stream response format.The test validates that
apply_relinkresponds with proper Turbo Stream content including remove and replace actions, confirming the multi-format response support.app/controllers/simplefin_items_controller.rb (5)
55-88: Well-structured post-update flow with auto-relink support.The update action now schedules a balances-only job for async data refresh, computes relink candidates, and conditionally opens the relink modal when matches are found. The multi-format response support (HTML/Turbo/JSON) enables flexible frontend integration.
121-153: Well-structured post-create flow with relink discovery.The create action schedules a balances-only job and computes relink candidates, presenting the relink modal immediately when matches exist. This improves the onboarding UX by surfacing relinking opportunities early.
171-179: Good defensive unlinking before deletion.The destroy action now uses SimplefinItem::Unlinker to remove provider links before scheduling deletion, ensuring accounts move to "Other accounts" section immediately. The rescue block prevents deletion failures from blocking the user flow.
193-202: Balances-only sync action provides lightweight refresh capability.The new
balancesaction creates a sync with balances_only stats and performs it synchronously. While this runs in-request, the earlier past review comments indicate this was addressed by making it async elsewhere (BalancesOnlyJob), though here it's still synchronous.Note: Earlier feedback flagged synchronous SimpleFin API calls as a reliability risk. Verify whether this action is intentionally kept synchronous (e.g., for immediate user feedback) or should dispatch BalancesOnlyJob instead.
291-357: New actions provide comprehensive error reporting and relinking UX.The
errors,relink,manual_relink, andapply_relinkactions support modal-based workflows for viewing per-account errors, auto-suggesting relinks, manually selecting relinks, and applying them with multi-format responses (HTML/Turbo/JSON).
@sokie Sorry, my discord has limited access for a couple days,,,, But thanks for the context. Sorry I missed your DM before pinging you for review! #268 is awesome. De-dup should be strict enough now, linking is manual-only, user selects via check boxes in modal, not auto. Will tighten if needed. On generalization, the modal is simplefin-specific for now, planning to make it provider-agnostic in a follow up later unless you think I should change it before it merges. |
Sounds good! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
♻️ Duplicate comments (5)
app/views/simplefin_items/_simplefin_item.html.erb (4)
46-46: Address the bare rescue from past review.A past review comment flagged this bare
rescuewhich catches all exceptions includingSystemExitandSignalException. Replace withrescue StandardError.
211-211: Address bare rescue from past reviews.These lines still use bare
rescuewhich was flagged in past review comments. Replace withrescue StandardErrorto avoid catching system exceptions.Also applies to: 272-272
227-227: Move sync query to controller.After removing the duplicate section, this
simplefin_item.syncs.ordered.firstquery should be moved to the controller to avoid N+1 queries and keep domain logic out of views, as previously suggested in past review comments.As per coding guidelines.
288-291: Move unlinked account query to controller.This database query computing
unlinked_countwas flagged in past review comments and should be moved to the controller to keep domain logic out of views.As per coding guidelines.
db/schema.rb (1)
28-30: Drop the duplicate unique index onaccount_providers.We still emit two unique indexes on
(account_id, provider_type)(index_account_providers_on_account_and_provider_typeandindex_account_providers_on_account_id_and_provider_type). That doubles constraint checks/storage without adding coverage. Please update the migration to drop/rename the legacy index instead of creating a second copy and regenerate the schema.
🧹 Nitpick comments (6)
app/models/provider/openai.rb (1)
57-61: Good fail-fast guard with clear logging.The guard prevents wasteful API calls when categories are unavailable and includes defensive family_id logging. The placement before
effective_modelcomputation is optimal.Consider enhancing the user-facing error message with actionable guidance:
- raise Error, "No categories available for auto-categorization" + raise Error, "No categories available for auto-categorization. Please create categories before using this feature."app/models/family/auto_categorizer.rb (1)
19-24: Optional: Validation duplicates provider-level check.The empty categories guard duplicates validation already in
Provider::OpenAI#auto_categorize, which raises an error whenuser_categories.blank?. While defensive checks can be beneficial, this duplication means the validation logic lives in two places.Consider removing this guard to keep validation centralized, or document why the early return is preferred over the provider's exception.
app/models/family/sync_complete_event.rb (1)
32-37: Consider moving pattern identification to a background job.Running
RecurringTransaction.identify_patterns_for(family)synchronously after every sync could impact performance, especially for families with large transaction histories. This blocks thebroadcastmethod and delays sync completion.Consider enqueuing pattern identification as a background job to avoid blocking the sync completion flow:
- # Identify recurring transaction patterns after sync - begin - RecurringTransaction.identify_patterns_for(family) - rescue => e - Rails.logger.error("Family::SyncCompleteEvent recurring transaction identification failed: #{e.message}\n#{e.backtrace&.join("\n")}") - end + # Identify recurring transaction patterns after sync in background + IdentifyRecurringPatternsJob.perform_later(family.id)This would allow the sync to complete immediately while pattern identification runs asynchronously.
config/locales/views/recurring_transactions/en.yml (1)
1-38: LGTM with minor formatting suggestion.The translation file is comprehensive and well-structured. All interpolation variables follow Rails conventions, and the content is clear and user-friendly.
Consider fixing minor spacing inconsistencies in the trigger descriptions:
triggers: - - CSV imports complete (transactions, trades, accounts, etc. ) - - Any provider sync completes ( Plaid, SimpleFIN, etc. ) + - CSV imports complete (transactions, trades, accounts, etc.) + - Any provider sync completes (Plaid, SimpleFIN, etc.)app/controllers/transactions_controller.rb (1)
26-32: Consider reordering the WHERE clause for readability.The logic is correct but the condition order reads unnaturally. The query searches for dates between today and one month from now, but the SQL conditions are reversed.
Apply this diff for improved clarity:
- @projected_recurring = Current.family.recurring_transactions - .active - .where("next_expected_date <= ? AND next_expected_date >= ?", - 1.month.from_now.to_date, - Date.current) - .includes(:merchant) + @projected_recurring = Current.family.recurring_transactions + .active + .where("next_expected_date >= ? AND next_expected_date <= ?", + Date.current, + 1.month.from_now.to_date) + .includes(:merchant)Or use BETWEEN for even better readability:
- @projected_recurring = Current.family.recurring_transactions - .active - .where("next_expected_date <= ? AND next_expected_date >= ?", - 1.month.from_now.to_date, - Date.current) - .includes(:merchant) + @projected_recurring = Current.family.recurring_transactions + .active + .where("next_expected_date BETWEEN ? AND ?", + Date.current, + 1.month.from_now.to_date) + .includes(:merchant)test/models/recurring_transaction_test.rb (1)
63-77: Consider adding a leap year test case.The end-of-month handling test is solid. For comprehensive coverage, consider adding a test for leap years (e.g., Jan 31 → Feb 29 in 2024) to ensure the date calculation handles that edge case correctly.
Example test:
test "calculate_next_expected_date handles leap year correctly" do recurring = @family.recurring_transactions.create!( merchant: @merchant, amount: 29.99, currency: "USD", expected_day_of_month: 31, last_occurrence_date: Date.new(2024, 1, 31), next_expected_date: Date.new(2024, 2, 29), status: "active" ) next_date = recurring.calculate_next_expected_date(Date.new(2024, 1, 31)) assert_equal Date.new(2024, 2, 29), next_date end
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (42)
app/controllers/recurring_transactions_controller.rb(1 hunks)app/controllers/transactions_controller.rb(1 hunks)app/models/balance_sheet/classification_group.rb(1 hunks)app/models/family.rb(1 hunks)app/models/family/auto_categorizer.rb(2 hunks)app/models/family/sync_complete_event.rb(1 hunks)app/models/merchant.rb(1 hunks)app/models/provider/openai.rb(1 hunks)app/models/recurring_transaction.rb(1 hunks)app/models/recurring_transaction/cleaner.rb(1 hunks)app/models/recurring_transaction/identifier.rb(1 hunks)app/models/simplefin_account/processor.rb(1 hunks)app/models/simplefin_entry/processor.rb(3 hunks)app/views/accounts/_account_sidebar_tabs.html.erb(3 hunks)app/views/accounts/_accountable_group.html.erb(1 hunks)app/views/family_exports/_list.html.erb(2 hunks)app/views/imports/_import.html.erb(1 hunks)app/views/imports/index.html.erb(2 hunks)app/views/layouts/_dark_mode_check.html.erb(1 hunks)app/views/pages/dashboard/_outflows_donut.html.erb(3 hunks)app/views/recurring_transactions/_projected_transaction.html.erb(1 hunks)app/views/recurring_transactions/index.html.erb(1 hunks)app/views/settings/_settings_nav.html.erb(1 hunks)app/views/settings/ai_prompts/show.html.erb(4 hunks)app/views/settings/bank_sync/_provider_link.html.erb(1 hunks)app/views/settings/bank_sync/show.html.erb(0 hunks)app/views/simplefin_items/_simplefin_item.html.erb(3 hunks)app/views/simplefin_items/relink.html.erb(1 hunks)app/views/trades/_form.html.erb(1 hunks)app/views/transactions/index.html.erb(2 hunks)config/locales/views/accounts/ca.yml(1 hunks)config/locales/views/accounts/en.yml(1 hunks)config/locales/views/recurring_transactions/en.yml(1 hunks)config/locales/views/settings/en.yml(1 hunks)config/routes.rb(3 hunks)db/migrate/20251031132654_create_recurring_transactions.rb(1 hunks)db/schema.rb(8 hunks)test/fixtures/recurring_transactions.yml(1 hunks)test/models/balance_sheet_test.rb(2 hunks)test/models/recurring_transaction/identifier_test.rb(1 hunks)test/models/recurring_transaction_test.rb(1 hunks)test/system/settings/ai_prompts_test.rb(1 hunks)
💤 Files with no reviewable changes (1)
- app/views/settings/bank_sync/show.html.erb
✅ Files skipped from review due to trivial changes (5)
- app/views/imports/_import.html.erb
- app/views/settings/bank_sync/_provider_link.html.erb
- app/views/family_exports/_list.html.erb
- app/views/pages/dashboard/_outflows_donut.html.erb
- app/views/layouts/_dark_mode_check.html.erb
🚧 Files skipped from review as they are similar to previous changes (2)
- app/views/simplefin_items/relink.html.erb
- config/routes.rb
🧰 Additional context used
📓 Path-based instructions (25)
app/models/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)
Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods
Use ActiveRecord validations for forms and complex domain constraints.
app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required
Files:
app/models/family/sync_complete_event.rbapp/models/family.rbapp/models/recurring_transaction/cleaner.rbapp/models/merchant.rbapp/models/recurring_transaction/identifier.rbapp/models/simplefin_account/processor.rbapp/models/provider/openai.rbapp/models/simplefin_entry/processor.rbapp/models/recurring_transaction.rbapp/models/family/auto_categorizer.rbapp/models/balance_sheet/classification_group.rb
app/**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/
Files:
app/models/family/sync_complete_event.rbapp/models/family.rbapp/models/recurring_transaction/cleaner.rbapp/models/merchant.rbapp/controllers/transactions_controller.rbapp/models/recurring_transaction/identifier.rbapp/models/simplefin_account/processor.rbapp/models/provider/openai.rbapp/models/simplefin_entry/processor.rbapp/models/recurring_transaction.rbapp/models/family/auto_categorizer.rbapp/controllers/recurring_transactions_controller.rbapp/models/balance_sheet/classification_group.rb
**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Files:
app/models/family/sync_complete_event.rbtest/system/settings/ai_prompts_test.rbapp/models/family.rbapp/models/recurring_transaction/cleaner.rbapp/models/merchant.rbapp/controllers/transactions_controller.rbapp/models/recurring_transaction/identifier.rbapp/models/simplefin_account/processor.rbdb/migrate/20251031132654_create_recurring_transactions.rbapp/models/provider/openai.rbapp/models/simplefin_entry/processor.rbapp/models/recurring_transaction.rbapp/models/family/auto_categorizer.rbapp/controllers/recurring_transactions_controller.rbtest/models/recurring_transaction_test.rbapp/models/balance_sheet/classification_group.rbtest/models/recurring_transaction/identifier_test.rbtest/models/balance_sheet_test.rbdb/schema.rb
**/*.{rb,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.
Files:
app/models/family/sync_complete_event.rbtest/system/settings/ai_prompts_test.rbapp/models/family.rbapp/models/recurring_transaction/cleaner.rbapp/models/merchant.rbapp/controllers/transactions_controller.rbapp/models/recurring_transaction/identifier.rbapp/models/simplefin_account/processor.rbdb/migrate/20251031132654_create_recurring_transactions.rbapp/models/provider/openai.rbapp/models/simplefin_entry/processor.rbapp/models/recurring_transaction.rbapp/models/family/auto_categorizer.rbapp/controllers/recurring_transactions_controller.rbtest/models/recurring_transaction_test.rbapp/models/balance_sheet/classification_group.rbtest/models/recurring_transaction/identifier_test.rbtest/models/balance_sheet_test.rbdb/schema.rb
app/{helpers,models}/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Format currencies, numbers, and dates on the server side (Ruby) before sending to the client
Files:
app/models/family/sync_complete_event.rbapp/models/family.rbapp/models/recurring_transaction/cleaner.rbapp/models/merchant.rbapp/models/recurring_transaction/identifier.rbapp/models/simplefin_account/processor.rbapp/models/provider/openai.rbapp/models/simplefin_entry/processor.rbapp/models/recurring_transaction.rbapp/models/family/auto_categorizer.rbapp/models/balance_sheet/classification_group.rb
app/{models,controllers,views}/**/*.{rb,erb}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid N+1 queries
Files:
app/models/family/sync_complete_event.rbapp/views/accounts/_accountable_group.html.erbapp/models/family.rbapp/models/recurring_transaction/cleaner.rbapp/models/merchant.rbapp/views/trades/_form.html.erbapp/views/transactions/index.html.erbapp/controllers/transactions_controller.rbapp/models/recurring_transaction/identifier.rbapp/models/simplefin_account/processor.rbapp/views/recurring_transactions/_projected_transaction.html.erbapp/models/provider/openai.rbapp/models/simplefin_entry/processor.rbapp/models/recurring_transaction.rbapp/models/family/auto_categorizer.rbapp/views/simplefin_items/_simplefin_item.html.erbapp/controllers/recurring_transactions_controller.rbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/recurring_transactions/index.html.erbapp/models/balance_sheet/classification_group.rbapp/views/imports/index.html.erbapp/views/settings/_settings_nav.html.erbapp/views/settings/ai_prompts/show.html.erb
app/views/**/*.erb
📄 CodeRabbit inference engine (AGENTS.md)
app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.ymlAlways use the icon helper (icon) for icons; never call lucide_icon directly
Files:
app/views/accounts/_accountable_group.html.erbapp/views/trades/_form.html.erbapp/views/transactions/index.html.erbapp/views/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/recurring_transactions/index.html.erbapp/views/imports/index.html.erbapp/views/settings/_settings_nav.html.erbapp/views/settings/ai_prompts/show.html.erb
app/views/**/*.html.*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.
Files:
app/views/accounts/_accountable_group.html.erbapp/views/trades/_form.html.erbapp/views/transactions/index.html.erbapp/views/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/recurring_transactions/index.html.erbapp/views/imports/index.html.erbapp/views/settings/_settings_nav.html.erbapp/views/settings/ai_prompts/show.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.
Files:
app/views/accounts/_accountable_group.html.erbapp/views/trades/_form.html.erbapp/views/transactions/index.html.erbapp/views/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/recurring_transactions/index.html.erbapp/views/imports/index.html.erbapp/views/settings/_settings_nav.html.erbapp/views/settings/ai_prompts/show.html.erb
app/views/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible
Files:
app/views/accounts/_accountable_group.html.erbapp/views/trades/_form.html.erbapp/views/transactions/index.html.erbapp/views/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/recurring_transactions/index.html.erbapp/views/imports/index.html.erbapp/views/settings/_settings_nav.html.erbapp/views/settings/ai_prompts/show.html.erb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions
Files:
app/views/accounts/_accountable_group.html.erbapp/views/trades/_form.html.erbapp/views/transactions/index.html.erbapp/views/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/recurring_transactions/index.html.erbapp/views/imports/index.html.erbapp/views/settings/_settings_nav.html.erbapp/views/settings/ai_prompts/show.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views
Files:
app/views/accounts/_accountable_group.html.erbapp/views/trades/_form.html.erbapp/views/transactions/index.html.erbapp/views/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/recurring_transactions/index.html.erbapp/views/imports/index.html.erbapp/views/settings/_settings_nav.html.erbapp/views/settings/ai_prompts/show.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)
Files:
app/views/accounts/_accountable_group.html.erbapp/views/trades/_form.html.erbapp/views/transactions/index.html.erbapp/views/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/recurring_transactions/index.html.erbapp/views/imports/index.html.erbapp/views/settings/_settings_nav.html.erbapp/views/settings/ai_prompts/show.html.erb
{app/views/**,app/helpers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Always generate semantic HTML
Files:
app/views/accounts/_accountable_group.html.erbapp/views/trades/_form.html.erbapp/views/transactions/index.html.erbapp/views/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/recurring_transactions/index.html.erbapp/views/imports/index.html.erbapp/views/settings/_settings_nav.html.erbapp/views/settings/ai_prompts/show.html.erb
{app/views,app/components}/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript
Files:
app/views/accounts/_accountable_group.html.erbapp/views/trades/_form.html.erbapp/views/transactions/index.html.erbapp/views/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/recurring_transactions/index.html.erbapp/views/imports/index.html.erbapp/views/settings/_settings_nav.html.erbapp/views/settings/ai_prompts/show.html.erb
app/views/**/_*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)
Files:
app/views/accounts/_accountable_group.html.erbapp/views/trades/_form.html.erbapp/views/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erbapp/views/accounts/_account_sidebar_tabs.html.erbapp/views/settings/_settings_nav.html.erb
test/**/*_test.rb
📄 CodeRabbit inference engine (AGENTS.md)
Name Minitest files with *_test.rb and mirror the app/ structure under test/
test/**/*_test.rb: ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Use Mocha for mocking in tests when necessary.
Use VCR for external API tests.
test/**/*_test.rb: Always use Minitest for tests; do not use RSpec
Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Only write tests for critical and important code paths
Avoid tests that merely verify framework/ActiveRecord behavior
Test boundaries correctly: for queries, assert returned values; for commands, assert collaborators are called with correct params
Never test the implementation details of one class in another class’s test suite
Use the mocha gem for stubs and mocks
Prefer OpenStruct for mock instances; use a mock class for complex cases
Only mock what’s necessary; don’t mock return values unless they are under test
Files:
test/system/settings/ai_prompts_test.rbtest/models/recurring_transaction_test.rbtest/models/recurring_transaction/identifier_test.rbtest/models/balance_sheet_test.rb
test/system/**/*_test.rb
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
Use system tests sparingly to keep the suite fast
Files:
test/system/settings/ai_prompts_test.rb
config/**
📄 CodeRabbit inference engine (AGENTS.md)
Store application and environment configuration under config/
Files:
config/locales/views/settings/en.ymlconfig/locales/views/accounts/ca.ymlconfig/locales/views/accounts/en.ymlconfig/locales/views/recurring_transactions/en.yml
db/migrate/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place database migrations under db/migrate/
Simple validations like null checks and unique constraints should be handled by database constraints in migration files.
Files:
db/migrate/20251031132654_create_recurring_transactions.rb
db/migrate/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Enforce simple validations in the database (NULL constraints, unique indexes, etc.)
Files:
db/migrate/20251031132654_create_recurring_transactions.rb
test/fixtures/**/*
📄 CodeRabbit inference engine (AGENTS.md)
Use test/fixtures for fixtures (and existing VCR cassettes if present)
Files:
test/fixtures/recurring_transactions.yml
test/fixtures/**/*.yml
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Keep fixtures minimal: 2–3 per model for base cases.
test/fixtures/**/*.yml: Use fixtures for test data rather than factories
Keep fixtures minimal: 2–3 per model for base cases; create edge cases inline in tests
Files:
test/fixtures/recurring_transactions.yml
app/models/provider/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)
Concrete provider classes must be under the Provider:: namespace, inherit from Provider, wrap calls with with_provider_response, and raise on invalid/unavailable data
Files:
app/models/provider/openai.rb
db/schema.rb
📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)
Treat db/schema.rb as the authoritative source of truth for model relationships
Files:
db/schema.rb
🧠 Learnings (36)
📓 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: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
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/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-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs
Applied to files:
app/models/family/sync_complete_event.rbapp/models/recurring_transaction/cleaner.rbapp/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Applied to files:
app/models/family/sync_complete_event.rbapp/controllers/transactions_controller.rbapp/models/simplefin_account/processor.rbapp/models/simplefin_entry/processor.rbapp/models/recurring_transaction.rbapp/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/models/**/*.rb : Implement complex validations and business logic with ActiveRecord validations
Applied to files:
app/models/family.rbapp/models/merchant.rb
📚 Learning: 2025-08-23T05:26:38.919Z
Learnt from: jjmata
Repo: we-promise/sure PR: 89
File: app/views/trades/_form.html.erb:53-53
Timestamp: 2025-08-23T05:26:38.919Z
Learning: In the we-promise/sure Rails application, there is a deliberate distinction between storage precision and display precision for Trade.price: the database stores up to 10 decimal places to avoid rounding errors in calculations, but UI display should use default currency precision (typically 2-4 decimals) for readability. Input forms can allow high precision entry, but display views should not show excessive decimal places.
Applied to files:
app/views/trades/_form.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{helpers,models}/**/*.rb : Format currencies, numbers, and dates on the server side (Ruby) before sending to the client
Applied to files:
app/views/trades/_form.html.erbconfig/locales/views/recurring_transactions/en.ymlapp/views/accounts/_account_sidebar_tabs.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/models/simplefin_account/processor.rbapp/views/simplefin_items/_simplefin_item.html.erbtest/models/balance_sheet_test.rbdb/schema.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/simplefin_account/processor.rbapp/models/simplefin_entry/processor.rbtest/models/balance_sheet_test.rbdb/schema.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Applied to files:
app/models/simplefin_account/processor.rb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to app/views/**/_*.html.erb : Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)
Applied to files:
app/views/recurring_transactions/_projected_transaction.html.erbapp/views/accounts/_account_sidebar_tabs.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/recurring_transactions/_projected_transaction.html.erbapp/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to db/migrate/**/*.rb : Enforce simple validations in the database (NULL constraints, unique indexes, etc.)
Applied to files:
db/migrate/20251031132654_create_recurring_transactions.rbdb/schema.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Use fixtures for test data rather than factories
Applied to files:
test/fixtures/recurring_transactions.yml
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases; create edge cases inline in tests
Applied to files:
test/fixtures/recurring_transactions.yml
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases.
Applied to files:
test/fixtures/recurring_transactions.yml
📚 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:
config/locales/views/recurring_transactions/en.ymlapp/views/accounts/_account_sidebar_tabs.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:
config/locales/views/recurring_transactions/en.ymlapp/views/accounts/_account_sidebar_tabs.html.erb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to app/views/**/*.erb : Keep heavy logic out of ERB views; prefer helpers/components instead
Applied to files:
app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Applied to files:
app/views/simplefin_items/_simplefin_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/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries
Applied to files:
app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/**/*.erb : Be mindful of performance in global layouts (e.g., avoid loading large data payloads)
Applied to files:
app/views/simplefin_items/_simplefin_item.html.erb
📚 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/views/simplefin_items/_simplefin_item.html.erbdb/schema.rb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Place context-specific partials within the relevant controller view directory (e.g., accounts/_account_sidebar_tabs.html.erb)
Applied to files:
app/views/accounts/_account_sidebar_tabs.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Prefer native HTML elements (e.g., <dialog>, <details><summary>) over JS-based components
Applied to files:
app/views/accounts/_account_sidebar_tabs.html.erb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Avoid tests that merely verify framework/ActiveRecord behavior
Applied to files:
test/models/recurring_transaction_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Applied to files:
test/models/recurring_transaction_test.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator
Applied to files:
test/models/balance_sheet_test.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated
Applied to files:
app/views/imports/index.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Applied to files:
app/views/imports/index.html.erb
📚 Learning: 2025-08-22T18:30:26.758Z
Learnt from: jjmata
Repo: we-promise/sure PR: 116
File: app/views/family_exports/_list.html.erb:16-28
Timestamp: 2025-08-22T18:30:26.758Z
Learning: In the family exports UI (app/views/family_exports/_list.html.erb), raw color utilities like bg-green-500/5, text-green-500, bg-red-500/5, text-red-500 are preferred over design system tokens for status indicators to maintain UI legibility and consistency with the existing import UI patterns. The user has indicated that design token replacements would make the UI illegible in this context.
Applied to files:
app/views/imports/index.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to db/schema.rb : Treat db/schema.rb as the authoritative source of truth for model relationships
Applied to files:
db/schema.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to db/migrate/*.rb : Simple validations like null checks and unique constraints should be handled by database constraints in migration files.
Applied to files:
db/schema.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to db/seeds.rb : Keep seed data definitions in db/seeds.rb
Applied to files:
db/schema.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:
db/schema.rb
📚 Learning: 2025-08-22T23:27:46.033Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: db/migrate/20250813144520_add_institution_fields_to_simplefin_items.rb:6-6
Timestamp: 2025-08-22T23:27:46.033Z
Learning: Institution URLs in financial contexts are typically short (under 100 characters) and don't require text columns - varchar(255) via :string is sufficient for SimpleFin institution URLs.
Applied to files:
db/schema.rb
🧬 Code graph analysis (15)
app/models/family/sync_complete_event.rb (2)
app/models/recurring_transaction.rb (1)
identify_patterns_for(19-21)app/models/sync.rb (1)
family(192-198)
app/models/family.rb (1)
app/models/category.rb (1)
has_many(1-149)
app/models/recurring_transaction/cleaner.rb (1)
app/models/recurring_transaction.rb (2)
matching_transactions(28-42)mark_inactive!(51-53)
app/controllers/transactions_controller.rb (1)
app/controllers/accounts_controller.rb (1)
family(91-93)
app/models/recurring_transaction/identifier.rb (3)
app/models/family/sync_complete_event.rb (2)
attr_reader(1-39)initialize(4-6)app/models/recurring_transaction/cleaner.rb (3)
attr_reader(1-41)attr_reader(2-40)initialize(5-7)app/models/recurring_transaction.rb (1)
calculate_next_expected_date(70-81)
app/models/simplefin_account/processor.rb (2)
app/models/simplefin_account.rb (2)
current_account(16-18)ensure_account_provider!(22-34)app/models/plaid_account/processor.rb (2)
process_account!(31-95)family(22-24)
app/models/provider/openai.rb (1)
app/models/assistant/function.rb (1)
family(78-80)
app/models/simplefin_entry/processor.rb (2)
app/models/account/provider_import_adapter.rb (1)
attr_reader(1-314)app/models/simplefin_account/transactions/processor.rb (2)
attr_reader(1-42)account(39-41)
app/models/recurring_transaction.rb (5)
app/controllers/transactions_controller.rb (2)
include(1-196)new(6-10)app/models/family.rb (1)
include(1-127)app/models/concerns/monetizable.rb (1)
monetize(5-15)app/models/recurring_transaction/identifier.rb (2)
identify_recurring_patterns(10-73)calculate_next_expected_date(148-157)app/models/recurring_transaction/cleaner.rb (1)
cleanup_stale_transactions(10-29)
app/models/family/auto_categorizer.rb (2)
app/models/family/auto_merchant_detector.rb (2)
llm_provider(65-67)transactions_input(82-92)app/models/provider/openai.rb (1)
auto_categorize(54-84)
app/controllers/recurring_transactions_controller.rb (2)
app/controllers/transactions_controller.rb (1)
index(12-33)app/models/recurring_transaction.rb (4)
identify_patterns_for(19-21)cleanup_stale_for(23-25)mark_inactive!(51-53)mark_active!(56-58)
test/models/recurring_transaction_test.rb (3)
test/models/recurring_transaction/identifier_test.rb (2)
setup(3-248)setup(4-8)app/models/recurring_transaction.rb (5)
identify_patterns_for(19-21)calculate_next_expected_date(70-81)should_be_inactive?(45-48)cleanup_stale_for(23-25)record_occurrence!(61-67)app/models/recurring_transaction/identifier.rb (1)
calculate_next_expected_date(148-157)
test/models/recurring_transaction/identifier_test.rb (2)
test/models/recurring_transaction_test.rb (2)
setup(3-172)setup(4-9)app/models/recurring_transaction/identifier.rb (1)
identify_recurring_patterns(10-73)
test/models/balance_sheet_test.rb (2)
app/models/balance_sheet/classification_group.rb (2)
name(15-17)total(23-25)app/models/balance_sheet/account_group.rb (1)
total(36-38)
db/schema.rb (2)
app/controllers/recurring_transactions_controller.rb (1)
index(4-8)app/controllers/transactions_controller.rb (1)
index(12-33)
🪛 ast-grep (0.39.6)
app/controllers/transactions_controller.rb
[warning] 26-30: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: Current.family.recurring_transactions
.active
.where("next_expected_date <= ? AND next_expected_date >= ?",
1.month.from_now.to_date,
Date.current)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
app/models/recurring_transaction.rb
[warning] 15-15: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: active.where("next_expected_date <= ?", 1.month.from_now)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 28-34: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: family.entries
.where(entryable_type: "Transaction")
.where(currency: currency)
.where("entries.amount = ?", amount)
.where("EXTRACT(DAY FROM entries.date) BETWEEN ? AND ?",
[ expected_day_of_month - 2, 1 ].max,
[ expected_day_of_month + 2, 31 ].min)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci / test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
app/views/simplefin_items/relink.html.erb (2)
12-12: Use functional design tokens instead of raw color classes.The
divide-gray-200andborder-gray-200classes are raw Tailwind colors. Per the coding guidelines, prefer functional tokens from the design system such asborder-primaryordivide-primary.As per coding guidelines.
Apply this diff:
- <div class="divide-y divide-gray-200 rounded-md border border-gray-200"> + <div class="divide-y divide-primary rounded-md border border-primary">
37-37: Use functional design tokens instead of raw color classes.Same as line 12: prefer functional tokens like
border-primaryordivide-primaryover rawborder-gray-200anddivide-gray-200.As per coding guidelines.
Apply this diff:
- <div class="mt-2 divide-y divide-gray-200 rounded-md border border-gray-200"> + <div class="mt-2 divide-y divide-primary rounded-md border border-primary">
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/views/simplefin_items/relink.html.erb(1 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
app/views/**/*.erb
📄 CodeRabbit inference engine (AGENTS.md)
app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.ymlAlways use the icon helper (icon) for icons; never call lucide_icon directly
Files:
app/views/simplefin_items/relink.html.erb
app/views/**/*.html.*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.
Files:
app/views/simplefin_items/relink.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.
Files:
app/views/simplefin_items/relink.html.erb
app/views/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible
Files:
app/views/simplefin_items/relink.html.erb
app/{models,controllers,views}/**/*.{rb,erb}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid N+1 queries
Files:
app/views/simplefin_items/relink.html.erb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions
Files:
app/views/simplefin_items/relink.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views
Files:
app/views/simplefin_items/relink.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)
Files:
app/views/simplefin_items/relink.html.erb
{app/views/**,app/helpers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Always generate semantic HTML
Files:
app/views/simplefin_items/relink.html.erb
{app/views,app/components}/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript
Files:
app/views/simplefin_items/relink.html.erb
🧠 Learnings (5)
📓 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: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
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.
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Applied to files:
app/views/simplefin_items/relink.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 : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations
Applied to files:
app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Applied to files:
app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated
Applied to files:
app/views/simplefin_items/relink.html.erb
🔇 Additional comments (2)
app/views/simplefin_items/relink.html.erb (2)
1-7: LGTM! Modal structure follows Hotwire conventions.The Turbo Frame wrapping and declarative Stimulus action for closing the modal align with the project's Hotwire integration patterns.
13-13: The original review comment incorrectly flags N+1 query concerns that are not present in the code:
@candidatesis pre-computed as an array of plain hashes (with keys:sfa_id,:sfa_name,:manual_id,:manual_name) bycompute_relink_candidates. The view accesses only hash keys—no AR objects or associations involved.
@unlinked_sfasusesleft_joins(:account, :account_provider)with filtering and the view accesses only direct attributes (.id,.name), which are not associations.
@manual_accountsusesleft_joins(:account_providers)with filtering and the view accesses only direct attributes (.id,.name), which are not associations.The controller properly applies preloading via explicit joins before filtering. No N+1 queries will occur.
Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (3)
lib/tasks/simplefin_backfill.rake (3)
81-81: Critical blocker still present:find_eachwill crash whensfasis an Array.When
account_idis provided (line 62-69),sfasbecomesArray.wrap(sfa)at line 69, so callingsfas.find_eachat line 81 raisesNoMethodErrorbecause Arrays don't have afind_eachmethod.Apply the fix from the previous review:
- sfas.find_each do |sfa| + Array(sfas).each do |sfa|Or retain batch behavior for Relations:
+ sfas = sfas.is_a?(ActiveRecord::Relation) ? sfas : SimplefinAccount.where(id: Array.wrap(sfas).compact.map(&:id)) sfas.find_each do |sfa|
75-79: Major issue still present: Per-SFA summary uses cumulative totals.The counters
total_seen,total_matched,total_updated,total_skipped, andtotal_errorsare initialized once (lines 75-79) and incremented throughout the entire loop. At line 174, the per-SFA output prints these cumulative values, misleading operators into thinking they're per-SFA counts.Introduce per-SFA counters as suggested in the previous review to provide accurate per-account reporting while maintaining global totals.
Also applies to: 174-174
150-167: Minor issues still present: Guardexternal_idand avoid duplicate Processor instantiation.
- Line 152: If
t[:id]is nil,external_idbecomes"simplefin_"which is invalid.- Line 157:
SimplefinEntry::Processoris instantiated twice in dry_run mode.- Line 157: The
composite_matchcall is missing thewindow_daysparameter.Apply the fix from the previous review to guard nil IDs, instantiate Processor once, and include window_days.
🧹 Nitpick comments (5)
db/migrate/20251102143510_remove_duplicate_account_providers_index.rb (1)
2-10: Consider adding a safety check to verify the newer index exists.The migration correctly removes the duplicate legacy index with proper guards. However, for extra safety, consider verifying that the newer index (
index_account_providers_on_account_and_provider_type) exists before removing the legacy one. This ensures the table retains the uniqueness constraint even if migrations run out of order.Apply this diff to add the safety check:
def up + # Verify the newer index exists before removing the legacy one + unless index_exists?(:account_providers, [ :account_id, :provider_type ], name: "index_account_providers_on_account_and_provider_type") + raise "Cannot remove legacy index: newer index 'index_account_providers_on_account_and_provider_type' does not exist. Run FixAccountProvidersIndexes migration first." + end + # We currently have two unique indexes on the same column set (account_id, provider_type): # - index_account_providers_on_account_and_provider_type (added in FixAccountProvidersIndexes) # - index_account_providers_on_account_id_and_provider_type (legacy auto-generated name) # Drop the legacy duplicate to avoid redundant constraint checks and storage. if index_exists?(:account_providers, [ :account_id, :provider_type ], name: "index_account_providers_on_account_id_and_provider_type") remove_index :account_providers, name: "index_account_providers_on_account_id_and_provider_type" end endapp/models/recurring_transaction.rb (1)
28-42: Optimize to avoid N+1 queries.The method loads entries from the database, then filters them in Ruby by accessing
entry.entryable.merchant_id, which triggers a separate query for each entry. This violates the "Avoid N+1 queries" guideline.Consider one of these approaches:
Option 1: Eager load the entryable association
def matching_transactions entries = family.entries + .includes(:entryable) .where(entryable_type: "Transaction")Option 2 (preferred): Filter merchant at the database level
def matching_transactions - entries = family.entries + entries = family.entries + .joins("INNER JOIN transactions ON transactions.id = entries.entryable_id") .where(entryable_type: "Transaction") + .where(transactions: { merchant_id: merchant_id }) .where(currency: currency) .where("entries.amount = ?", amount) .where("EXTRACT(DAY FROM entries.date) BETWEEN ? AND ?", [ expected_day_of_month - 2, 1 ].max, [ expected_day_of_month + 2, 31 ].min) .order(date: :desc) - - # Filter by merchant through the entryable (Transaction) - entries.select do |entry| - entry.entryable.is_a?(Transaction) && entry.entryable.merchant_id == merchant_id - end endAs per coding guidelines.
lib/tasks/simplefin_backfill.rake (2)
154-171: Consider per-entry transaction wrapping in live mode.In non-dry-run mode (line 161), if
SimplefinEntry::Processor#processpartially updates data before failing, the error rescue (lines 168-171) logs the issue but doesn't roll back partial changes. For cleaner error recovery, wrap each entry's processing in anActiveRecord::Base.transactionblock.Apply this diff to add transaction safety:
if dry_run # ... existing dry_run logic ... else - processed = SimplefinEntry::Processor.new(t, simplefin_account: sfa).process - if processed&.transaction&.extra.present? - total_updated += 1 - else - total_skipped += 1 + ActiveRecord::Base.transaction do + processed = SimplefinEntry::Processor.new(t, simplefin_account: sfa).process + if processed&.transaction&.extra.present? + total_updated += 1 + else + total_skipped += 1 + end end end
100-125: Consider extracting date parsing logic to a helper method.The date parsing logic for
posted(lines 100-112) andtransacted_at(lines 113-125) is identical. Extracting it to a private method would reduce duplication and improve maintainability.Example refactor:
def parse_date_field(value) case value when String Date.parse(value) rescue nil when Numeric Time.zone.at(value).to_date rescue nil when Date value when Time, DateTime value.to_date else nil end endThen use:
posted_d = parse_date_field(t[:posted]) trans_d = parse_date_field(t[:transacted_at])app/controllers/simplefin_items_controller.rb (1)
1-1: Remove unused require.The
Setclass is not referenced anywhere in this controller. Removing the unused require keeps dependencies clean.Apply this diff:
-require "set" - class SimplefinItemsController < ApplicationController
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
app/controllers/accounts_controller.rb(1 hunks)app/controllers/simplefin_items_controller.rb(8 hunks)app/helpers/simplefin_items_helper.rb(1 hunks)app/models/recurring_transaction.rb(1 hunks)app/models/simplefin_account.rb(1 hunks)app/models/simplefin_account/processor.rb(1 hunks)app/views/simplefin_items/_simplefin_item.html.erb(3 hunks)app/views/simplefin_items/relink.html.erb(1 hunks)db/migrate/20251102143510_remove_duplicate_account_providers_index.rb(1 hunks)db/schema.rb(5 hunks)lib/tasks/simplefin_backfill.rake(1 hunks)lib/tasks/simplefin_fix_was_merged.rake(1 hunks)lib/tasks/was_merged.rake(1 hunks)test/views/transactions/merged_badge_view_test.rb(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- app/controllers/accounts_controller.rb
- app/views/simplefin_items/_simplefin_item.html.erb
- db/schema.rb
🧰 Additional context used
📓 Path-based instructions (18)
db/migrate/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place database migrations under db/migrate/
Simple validations like null checks and unique constraints should be handled by database constraints in migration files.
Files:
db/migrate/20251102143510_remove_duplicate_account_providers_index.rb
**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Files:
db/migrate/20251102143510_remove_duplicate_account_providers_index.rbapp/models/simplefin_account.rbtest/views/transactions/merged_badge_view_test.rbapp/models/recurring_transaction.rbapp/controllers/simplefin_items_controller.rbapp/helpers/simplefin_items_helper.rbapp/models/simplefin_account/processor.rb
**/*.{rb,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.
Files:
db/migrate/20251102143510_remove_duplicate_account_providers_index.rbapp/models/simplefin_account.rbtest/views/transactions/merged_badge_view_test.rbapp/models/recurring_transaction.rbapp/controllers/simplefin_items_controller.rbapp/helpers/simplefin_items_helper.rbapp/models/simplefin_account/processor.rb
db/migrate/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Enforce simple validations in the database (NULL constraints, unique indexes, etc.)
Files:
db/migrate/20251102143510_remove_duplicate_account_providers_index.rb
app/models/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)
Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods
Use ActiveRecord validations for forms and complex domain constraints.
app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required
Files:
app/models/simplefin_account.rbapp/models/recurring_transaction.rbapp/models/simplefin_account/processor.rb
app/**/*.rb
📄 CodeRabbit inference engine (AGENTS.md)
Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/
Files:
app/models/simplefin_account.rbapp/models/recurring_transaction.rbapp/controllers/simplefin_items_controller.rbapp/helpers/simplefin_items_helper.rbapp/models/simplefin_account/processor.rb
app/{helpers,models}/**/*.rb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Format currencies, numbers, and dates on the server side (Ruby) before sending to the client
Files:
app/models/simplefin_account.rbapp/models/recurring_transaction.rbapp/helpers/simplefin_items_helper.rbapp/models/simplefin_account/processor.rb
app/{models,controllers,views}/**/*.{rb,erb}
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
Avoid N+1 queries
Files:
app/models/simplefin_account.rbapp/models/recurring_transaction.rbapp/controllers/simplefin_items_controller.rbapp/views/simplefin_items/relink.html.erbapp/models/simplefin_account/processor.rb
test/**/*_test.rb
📄 CodeRabbit inference engine (AGENTS.md)
Name Minitest files with *_test.rb and mirror the app/ structure under test/
test/**/*_test.rb: ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Use Mocha for mocking in tests when necessary.
Use VCR for external API tests.
test/**/*_test.rb: Always use Minitest for tests; do not use RSpec
Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Only write tests for critical and important code paths
Avoid tests that merely verify framework/ActiveRecord behavior
Test boundaries correctly: for queries, assert returned values; for commands, assert collaborators are called with correct params
Never test the implementation details of one class in another class’s test suite
Use the mocha gem for stubs and mocks
Prefer OpenStruct for mock instances; use a mock class for complex cases
Only mock what’s necessary; don’t mock return values unless they are under test
Files:
test/views/transactions/merged_badge_view_test.rb
app/views/**/*.erb
📄 CodeRabbit inference engine (AGENTS.md)
app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.ymlAlways use the icon helper (icon) for icons; never call lucide_icon directly
Files:
app/views/simplefin_items/relink.html.erb
app/views/**/*.html.*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.
Files:
app/views/simplefin_items/relink.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.
Files:
app/views/simplefin_items/relink.html.erb
app/views/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)
app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible
Files:
app/views/simplefin_items/relink.html.erb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions
Files:
app/views/simplefin_items/relink.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}
📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)
Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views
Files:
app/views/simplefin_items/relink.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)
Files:
app/views/simplefin_items/relink.html.erbapp/helpers/simplefin_items_helper.rb
{app/views/**,app/helpers/**}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Always generate semantic HTML
Files:
app/views/simplefin_items/relink.html.erbapp/helpers/simplefin_items_helper.rb
{app/views,app/components}/**/*.html.erb
📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)
{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript
Files:
app/views/simplefin_items/relink.html.erb
🧠 Learnings (32)
📓 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: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
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/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:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to db/migrate/**/*.rb : Enforce simple validations in the database (NULL constraints, unique indexes, etc.)
Applied to files:
db/migrate/20251102143510_remove_duplicate_account_providers_index.rbapp/models/simplefin_account.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to db/migrate/*.rb : Simple validations like null checks and unique constraints should be handled by database constraints in migration files.
Applied to files:
db/migrate/20251102143510_remove_duplicate_account_providers_index.rb
📚 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/models/simplefin_account.rblib/tasks/simplefin_backfill.rakeapp/controllers/simplefin_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/models/simplefin_account.rbapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Applied to files:
app/models/simplefin_account.rblib/tasks/simplefin_backfill.rakelib/tasks/was_merged.rakeapp/controllers/simplefin_items_controller.rbapp/models/simplefin_account/processor.rblib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
Applied to files:
app/models/simplefin_account.rblib/tasks/simplefin_backfill.rake
📚 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/simplefin_account.rblib/tasks/simplefin_backfill.rakeapp/controllers/simplefin_items_controller.rbapp/models/simplefin_account/processor.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Applied to files:
app/models/simplefin_account.rblib/tasks/simplefin_backfill.rake
📚 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/models/simplefin_account.rblib/tasks/simplefin_backfill.rakeapp/controllers/simplefin_items_controller.rbapp/models/simplefin_account/processor.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Avoid tests that merely verify framework/ActiveRecord behavior
Applied to files:
test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to **/*.{rb,js,jsx,ts,tsx} : Respect existing tests and add tests when changing critical logic.
Applied to files:
test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
Applied to files:
test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases; create edge cases inline in tests
Applied to files:
test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/**/*_test.rb : ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Applied to files:
test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Applied to files:
test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases.
Applied to files:
test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Use fixtures for test data rather than factories
Applied to files:
test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to test/fixtures/**/* : Use test/fixtures for fixtures (and existing VCR cassettes if present)
Applied to files:
test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-13T11:40:26.913Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.
Applied to files:
lib/tasks/simplefin_backfill.rake
📚 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:
lib/tasks/simplefin_backfill.rakeapp/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator
Applied to files:
lib/tasks/was_merged.rakeapp/controllers/simplefin_items_controller.rbapp/models/simplefin_account/processor.rblib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs
Applied to files:
app/controllers/simplefin_items_controller.rblib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries
Applied to files:
app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Applied to files:
app/controllers/simplefin_items_controller.rbapp/models/simplefin_account/processor.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Applied to files:
app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to app/views/**/*.html.* : Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Applied to files:
app/views/simplefin_items/relink.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 : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations
Applied to files:
app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated
Applied to files:
app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to **/*.rb : Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules
Applied to files:
app/helpers/simplefin_items_helper.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to **/*.{rb,js,jsx,ts,tsx} : Make changes atomic, testable, and explain their impact briefly in code suggestions.
Applied to files:
app/helpers/simplefin_items_helper.rblib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Applied to files:
app/helpers/simplefin_items_helper.rblib/tasks/simplefin_fix_was_merged.rake
🧬 Code graph analysis (5)
db/migrate/20251102143510_remove_duplicate_account_providers_index.rb (1)
db/migrate/20251028104241_fix_account_providers_indexes.rb (1)
change(1-18)
app/models/simplefin_account.rb (1)
app/models/account/linkable.rb (1)
provider(27-31)
app/controllers/simplefin_items_controller.rb (6)
app/models/simplefin_item.rb (2)
include(1-301)dedup_simplefin_accounts!(163-226)app/controllers/accounts_controller.rb (3)
index(5-35)family(101-103)sync(53-69)app/controllers/concerns/simplefin_items/relink_helpers.rb (2)
compute_unlinked_count(10-15)compute_relink_candidates(23-114)app/models/simplefin_item/relink_service.rb (2)
new(3-122)apply!(14-121)app/services/simplefin_item/unlinker.rb (1)
unlink_all!(11-32)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)
app/helpers/simplefin_items_helper.rb (1)
app/models/simplefin_item/importer.rb (1)
stats(103-105)
app/models/simplefin_account/processor.rb (2)
app/models/simplefin_account.rb (2)
current_account(16-18)ensure_account_provider!(22-35)app/models/plaid_account/processor.rb (1)
process_account!(31-95)
🔇 Additional comments (15)
db/migrate/20251102143510_remove_duplicate_account_providers_index.rb (1)
12-17: LGTM!The
downmethod correctly handles reversibility by recreating the legacy index with proper existence guards. While it recreates a duplicate, this is the correct behavior for rollback—restoring the database to its previous state.app/models/recurring_transaction.rb (1)
47-47: LGTM! Date comparison fix is correct.The
.to_dateconversion ensures Date-to-Date comparison and prevents the ArgumentError that was flagged in the previous review.test/views/transactions/merged_badge_view_test.rb (1)
9-17: LGTM!Inline creation of the Transaction and Entry with
was_merged: trueis appropriate here, as this is an edge case being tested. The Entry has all required fields properly set.lib/tasks/simplefin_backfill.rake (4)
23-56: LGTM: Argument parsing and validation are solid.The flexible argument parsing (positional and key=value), UUID validation, and safe defaults (dry_run=true by default) are well-implemented.
100-132: Date parsing is robust with intentional silent failure handling.The multi-type date parsing (String, Numeric, Date, Time, DateTime) with
rescue nilat lines 103 and 116 ensures the task doesn't crash on malformed data. Transactions with unparseable dates are safely skipped at lines 129-132.
136-148: LGTM: Extra payload construction is correct.Using
t.key?(:payee)(lines 138-141) correctly distinguishes between absent keys and nil values, and the force flag (line 145) appropriately controls re-processing behavior.
180-224: LGTM: Cleanup task is correctly implemented.The
dry_rundefault (line 193) correctly defaults to true when blank, addressing the previous review concern. The parameterized LIKE queries (lines 205-207) prevent SQL injection, and the transaction wrapping (lines 214-219) ensures atomic deletions.app/helpers/simplefin_items_helper.rb (1)
15-36: LGTM! Well-structured tooltip helper.The implementation is clean and correct. The early nil return for zero errors, guard against non-hash input, and compact formatting with sample errors and bucket counts all work well together. The past review concerns have been properly addressed.
app/controllers/simplefin_items_controller.rb (7)
23-108: LGTM! Update action is well-structured.The action properly handles token updates with async balances refresh (line 60), safe account transfer in a transaction (lines 39-54), relink candidate detection, and comprehensive error handling with appropriate flash messages. The past review concerns about synchronous API calls and flash messaging have been addressed.
114-169: LGTM! Create action follows best practices.The action uses async job dispatch (line 126), consistent helper usage for unlinked count computation (lines 130-138), and intelligent relink candidate detection (lines 140-154). Error handling is comprehensive with clear user messaging.
194-203: Verify that synchronous SimpleFin API call is intentional.Line 197 calls
perform_syncsynchronously in the request thread, which blocks until the SimpleFin API responds. In contrast, thecreateandupdateactions useSimplefinItem::BalancesOnlyJob.perform_laterfor async execution (lines 60, 126). A past review flagged this as a reliability concern.If balances-only syncs are guaranteed to be fast and lightweight, document this decision with a comment explaining why synchronous execution is safe. Otherwise, align with the async pattern used elsewhere.
Consider this async alternative:
def balances - sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) - SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) + unless @simplefin_item.syncing? + sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) + SimplefinItem::BalancesOnlyJob.perform_later(@simplefin_item.id) + end respond_to do |format| format.html { redirect_back_or_to accounts_path } - format.json { render json: { ok: true, sync_id: sync.id } } + format.json { render json: { ok: true } } end end
205-256: LGTM! Setup accounts action is well-designed.The action safely dedups (lines 207-211), properly fetches unlinked accounts with explicit joins to avoid N+1 (lines 213-217), and includes helpful logging for observability (lines 219-221). The account type and subtype options are clearly structured.
292-308: LGTM! Errors action handles data safely.The action defensively extracts sync stats (line 295), handles both symbol and string keys in error data (lines 298-304), and properly renders without layout for modal display (line 307).
310-361: LGTM! Relink actions are well-implemented.The
relinkaction (lines 311-328) safely computes candidates and prepares fallback lists with proper joins. Theapply_relinkaction (lines 337-361) correctly filters checked pairs (line 338), delegates to the service layer with family scoping (lines 341-345), and rebuilds maps before rendering (line 348) to avoid N+1 queries. The multi-format response with Turbo Streams (lines 351-356) enables seamless live updates. Past FK violation concerns have been addressed.
389-425: LGTM! Excellent N+1 query prevention.This helper is well-designed to precompute data for the view. The
syncs.loaded?check (lines 400-404) intelligently avoids redundant queries when syncs are eager-loaded viaincludes(:syncs). The three maps (sync_stats, has_unlinked, unlinked_count) are populated with proper left joins and defensive fallbacks (lines 421-424).Based on learnings.
…nify \"manual\" scope, and correct unlinked counts - Preserve #manual-accounts wrapper: switch non-empty updates to turbo_stream.update and background broadcast_update_to; keep empty-path replace to render <div id=\"manual-accounts\"></div> - Unify definition of manual accounts via Account.visible_manual (visible + legacy-nil + no AccountProvider); reuse in controllers, jobs, and helper - Correct setup/unlinked counts: SimplefinItem::Syncer#finalize_setup_counts and maps now consider AccountProvider links (legacy account AND provider must be absent) Deleted: - app/models/simplefin_item/relink_service.rb - app/controllers/concerns/simplefin_items/relink_helpers.rb - app/javascript/controllers/auto_relink_controller.js - app/views/simplefin_items/_relink_modal.html.erb - app/views/simplefin_items/manual_relink.html.erb - app/views/simplefin_items/relink.html.erb - test/services/simplefin_item/relink_service_test.rb Refs: PR we-promise#318 unified link/unlink; PR we-promise#267 SimpleFIN; follow-up to fix wrapper ID loss and counting drift."
2b7b1f9 to
297670a
Compare
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/models/simplefin_item/importer.rb (1)
171-174: Usechunk_size_daysfor the guard, not 365.Your chunks target 60 days; enforcing 365 here is inconsistent and risks oversize requests if math drifts.
- if actual_days > 365 - Rails.logger.error "SimpleFin: Chunk exceeds 365 days (#{actual_days} days). This should not happen." - chunk_start_date = chunk_end_date - 365.days + if actual_days > chunk_size_days + Rails.logger.error "SimpleFin: Chunk exceeds #{chunk_size_days} days (#{actual_days} days). Adjusting." + chunk_start_date = chunk_end_date - chunk_size_days.daysapp/models/simplefin_item/syncer.rb (1)
27-48: Wrap full-sync in rescue and callmark_failedon exceptions.Balances-only path handles failures; full path doesn’t, so job/request can crash without status updates.
- # Full sync path - sync.update!(status_text: "Importing accounts from SimpleFin...") if sync.respond_to?(:status_text) - simplefin_item.import_latest_simplefin_data(sync: sync) - - finalize_setup_counts(sync) + # Full sync path + sync.update!(status_text: "Importing accounts from SimpleFin...") if sync.respond_to?(:status_text) + begin + simplefin_item.import_latest_simplefin_data(sync: sync) + finalize_setup_counts(sync) + rescue => e + mark_failed(sync, e) + return + end
♻️ Duplicate comments (8)
lib/tasks/holdings_tools.rake (1)
68-74: Fix error message to match parameter name.The task parameter is
:snapshot_id(line 69) and the description says "Args: snapshot_id" (line 68), but the error message on line 72 says"Provide id". This inconsistency could confuse users.Apply this diff:
- puts({ ok: false, error: "usage", message: "Provide id" }.to_json) + puts({ ok: false, error: "usage", message: "Provide snapshot_id" }.to_json)db/migrate/20251030172500_add_cascade_on_account_deletes.rb (1)
3-3: Migration version fixed.The migration now correctly inherits from
ActiveRecord::Migration[7.2], consistent with project guidelines.app/controllers/accounts_controller.rb (1)
13-51: Eliminate N+1 queries and code duplication by delegating to the shared helper.This map-building logic causes N+1 queries:
- Lines 20-22: queries
item.family.accounts.visible_manual.exists?per item- Lines 28-31: queries unlinked count per item
- Lines 43-44: may query
simplefin_accounts.exists?per item if not loadedAdditionally, this duplicates the batched logic already implemented in
SimplefinItems::MapsHelper.build_simplefin_maps_for(app/controllers/concerns/simplefin_items/maps_helper.rb).Line 47 also uses a bare
rescuewhich should berescue StandardError.Apply this refactor to eliminate both issues:
class AccountsController < ApplicationController + include SimplefinItems::MapsHelper before_action :set_account, only: %i[sync sparkline toggle_active show destroy unlink confirm_unlink select_provider] include Periodable def index @manual_accounts = family.accounts .visible_manual .order(:name) @plaid_items = family.plaid_items.ordered @simplefin_items = family.simplefin_items.ordered.includes(:syncs) @lunchflow_items = family.lunchflow_items.ordered # Precompute per-item maps to avoid queries in the view - @simplefin_sync_stats_map = {} - @simplefin_has_unlinked_map = {} - - @simplefin_items.each do |item| - latest_sync = item.syncs.ordered.first - @simplefin_sync_stats_map[item.id] = (latest_sync&.sync_stats || {}) - @simplefin_has_unlinked_map[item.id] = item.family.accounts - .visible_manual - .exists? - end - - # Count of SimpleFin accounts that are not linked (no legacy account and no AccountProvider) - @simplefin_unlinked_count_map = {} - @simplefin_items.each do |item| - count = item.simplefin_accounts - .left_joins(:account, :account_provider) - .where(accounts: { id: nil }, account_providers: { id: nil }) - .count - @simplefin_unlinked_count_map[item.id] = count - end - - # Compute CTA visibility map used by the simplefin_item partial - @simplefin_show_relink_map = {} - @simplefin_items.each do |item| - begin - unlinked_count = @simplefin_unlinked_count_map[item.id] || 0 - manuals_exist = @simplefin_has_unlinked_map[item.id] - sfa_any = if item.simplefin_accounts.loaded? - item.simplefin_accounts.any? - else - item.simplefin_accounts.exists? - end - @simplefin_show_relink_map[item.id] = (unlinked_count.to_i == 0 && manuals_exist && sfa_any) - rescue => e - Rails.logger.warn("SimpleFin card: CTA computation failed for item #{item.id}: #{e.class} - #{e.message}") - @simplefin_show_relink_map[item.id] = false - end - end + build_simplefin_maps_for(@simplefin_items) # Prevent Turbo Drive from caching this page to ensure fresh account lists expires_nowapp/controllers/settings/bank_sync_controller.rb (1)
23-23: Use production SimpleFin endpoint, not beta.The URL
https://beta-bridge.simplefin.orgpoints to a beta/demo environment. According to previous review findings and SimpleFin documentation, the production endpoint ishttps://bridge.simplefin.org/simplefin. Please update to use the production URL.lib/tasks/simplefin_holdings_backfill.rake (1)
99-102: Critical: Undefined variable reference.Line 99 references
errors.empty?beforeerrorsis initialized on line 106. This will raise aNameErrorwhen this branch executes (i.e., when no selector argument is provided).Apply this diff to fix:
- success = errors.empty? puts({ ok: false, error: "usage", message: "Provide one of item_id, account_id, or account_name" }.to_json) exit 1app/models/simplefin_account/investments/holdings_processor.rb (1)
50-52: Fix holding date lookup to includecreated_at/updated. Missing keys re-date holdings to today.Not checking underscored keys can corrupt historical valuations on import. Add both variants.
- # Use best-known date: created -> updated_at -> as_of -> date -> today - holding_date = parse_holding_date(any_of(simplefin_holding, %w[created updated_at as_of date])) || Date.current + # Use best-known date: created/created_at -> updated/updated_at -> as_of -> date -> today + holding_date = parse_holding_date( + any_of(simplefin_holding, %w[created created_at updated updated_at as_of date]) + ) || Date.currentBased on learnings
lib/tasks/simplefin_backfill.rake (1)
170-181: Guard against undefinedcomposite_matchmethod to prevent crash in dry-run.The
adapter.composite_match(...)call at line 175 will raiseNoMethodErrorbecause this method is not defined inAccount::ProviderImportAdapter. Add a guard or fallback:- entry ||= adapter.composite_match( - source: "simplefin", - name: processor.send(:name), - amount: processor.send(:amount), - date: (posted_d || trans_d), - window_days: window_days - ) + if adapter.respond_to?(:composite_match) + entry ||= adapter.composite_match( + source: "simplefin", + name: processor.send(:name), + amount: processor.send(:amount), + date: (posted_d || trans_d), + window_days: window_days + ) + else + # Fallback: match by external_id or local query + entry ||= acct.entries.where( + source: "simplefin", + name: processor.send(:name), + amount: processor.send(:amount), + date: (posted_d || trans_d) + ).first + endapp/controllers/simplefin_items_controller.rb (1)
143-152: Remove synchronous sync call; queue async job instead.The current code calls
perform_syncsynchronously (line 147), blocking the request. However, the review's suggested job name is incorrect—SimplefinItemSyncJobdoesn't exist.Use one of two correct patterns:
- Simpler: Queue
SimplefinItem::BalancesOnlyJob.perform_later(@simplefin_item.id)directly (purpose-built for this use case)- Alternative: Create the sync and queue
SyncJob.perform_later(sync)instead of callingperform_syncdirectlyThe existing sync action (line 132–136) already demonstrates the correct async pattern with
sync_later.
🧹 Nitpick comments (12)
app/views/layouts/settings.html.erb (1)
7-7: Clarify if element-level cache control is redundant.Since
settings.html.erbrenders_htmldoc.html.erb(line 1), which now includes a document-levelturbo-cache-control: no-cachemeta tag, this element-leveldata-turbo-cache="false"attribute may be redundant. The document-level meta tag already disables caching for the entire page.If the document-level meta tag remains, consider removing this attribute to avoid duplication. Alternatively, if the document-level tag is moved elsewhere (see previous comment), this element-level control becomes the appropriate solution for settings pages.
db/migrate/20251030172500_add_cascade_on_account_deletes.rb (1)
30-35: Consider clarifying the FK replacement logic.The pattern of checking existence, removing, then adding with an
unless foreign_key_exists?guard works correctly but may be unclear to future maintainers. The guard is defensive (prevents duplicate FK errors if migration runs multiple times), but after the removal on line 33, the FK won't exist, making the guard mostly redundant.Consider adding a comment or simplifying to:
# Entries -> Accounts (account_id) with CASCADE if foreign_key_exists?(:entries, :accounts) remove_foreign_key :entries, :accounts end # Add with CASCADE (guard prevents duplicate if FK somehow still exists) add_foreign_key :entries, :accounts, column: :account_id, on_delete: :cascade unless foreign_key_exists?(:entries, :accounts)Or simplify by removing the guard if Rails migration tracking is trusted:
# Entries -> Accounts (account_id) with CASCADE remove_foreign_key :entries, :accounts if foreign_key_exists?(:entries, :accounts) add_foreign_key :entries, :accounts, column: :account_id, on_delete: :cascadeapp/jobs/simplefin_item/balances_only_job.rb (3)
22-27: Replace barerescuewithrescue StandardError.The bare
rescueat line 25 catches all exceptions, including system-level ones likeSignalExceptionandSystemExit, which can interfere with graceful shutdowns.Apply this diff:
begin item.update!(last_synced_at: Time.current) if item.has_attribute?(:last_synced_at) - rescue => e + rescue StandardError => e Rails.logger.warn("SimpleFin BalancesOnlyJob last_synced_at update failed: #{e.class} - #{e.message}") end
39-57: Replace barerescuewithrescue StandardError.The bare
rescueat line 55 catches system-level exceptions. UseStandardErrorinstead.Apply this diff:
Turbo::StreamsChannel.broadcast_replace_to(item.family, target: "manual-accounts", html: manual_html) end - rescue => inner + rescue StandardError => inner Rails.logger.warn("SimpleFin BalancesOnlyJob manual-accounts broadcast failed: #{inner.class} - #{inner.message}") end
29-60: Replace barerescuewithrescue StandardError.The bare
rescueat line 58 catches system-level exceptions. UseStandardErrorinstead.Apply this diff:
Rails.logger.warn("SimpleFin BalancesOnlyJob manual-accounts broadcast failed: #{inner.class} - #{inner.message}") end - rescue => e + rescue StandardError => e Rails.logger.warn("SimpleFin BalancesOnlyJob broadcast failed: #{e.class} - #{e.message}") endlib/tasks/simplefin_debug.rake (1)
19-26: Consider database-level filtering to avoid loading all accounts.The current approach loads all
simplefin_accountsinto memory and filters in Ruby. For items with many accounts, this is inefficient. Consider using a database query similar to the pattern at lines 74-77.Apply this diff if you want to optimize:
- sfa = item.simplefin_accounts.order(updated_at: :desc).find do |acc| - acc.name.to_s.downcase.include?(args[:account_name].to_s.downcase) - end + sfa = item.simplefin_accounts + .where("LOWER(name) LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(args[:account_name].to_s.downcase)}%") + .order(updated_at: :desc) + .firstNote: Since this is a debug task rather than a production code path, the current implementation is acceptable.
app/controllers/concerns/simplefin_items/maps_helper.rb (1)
80-92: Replace barerescuewithrescue StandardError.Line 90 uses a bare
rescuewhich catches system-level exceptions. This is inconsistent with the fix at line 66 and can mask critical errors.Apply this diff:
errs.present? && errs.all? { |m| m.to_s.downcase.include?("duplicate upstream account detected") } - rescue + rescue StandardError false endtest/models/simplefin_item/importer_duplicate_test.rb (2)
17-19: Consider more targeted stubbing to avoid test pollution.Using
SimplefinAccount.any_instance.stubs(:save!)affects all SimplefinAccount instances globally, which can cause test pollution and make the test brittle. The.then.returns(true)chain suggests the test expects multiplesave!calls, but it's unclear if this matches the actual behavior.Consider stubbing at a more specific scope or using expectations that are clearer about call counts.
35-36: Same global stubbing concern applies here.Similar to the first test, the global
any_instance.stubs(:save!)pattern can affect other parts of the test suite if tests run in parallel or if there's any shared state.app/views/simplefin_items/_simplefin_item.html.erb (2)
45-53: Extract “time-ago” parsing to a small helper to DRY the view.You compute
ago = time_ago_in_words(Time.parse(ts))twice with identical rescue. Move into a helper likesimplefin_time_ago(ts)for clarity and testability.Also applies to: 195-199
212-226: Consider removing the local fallback query when maps are present.The fallback
left_joinsquery is correct but can reintroduce N+1 under broadcast-only renders. If controller maps are reliably provided, prefer the map and log when missing; skip the DB fallback.app/models/simplefin_account/investments/holdings_processor.rb (1)
114-121: Makeparse_holding_dateacceptTimeandDate.Payloads can already be Time/Date. Handle them directly.
case created_timestamp when Integer Time.at(created_timestamp).to_date when String Date.parse(created_timestamp) + when Time, Date + created_timestamp.to_date else nil end
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (70)
app/controllers/accounts_controller.rb(1 hunks)app/controllers/concerns/simplefin_items/maps_helper.rb(1 hunks)app/controllers/settings/bank_sync_controller.rb(1 hunks)app/controllers/settings/providers_controller.rb(3 hunks)app/controllers/simplefin_items_controller.rb(6 hunks)app/helpers/settings_helper.rb(1 hunks)app/helpers/simplefin_items_helper.rb(1 hunks)app/helpers/transactions_helper.rb(1 hunks)app/jobs/simplefin_item/balances_only_job.rb(1 hunks)app/models/account.rb(2 hunks)app/models/account/provider_import_adapter.rb(2 hunks)app/models/family/auto_transfer_matchable.rb(1 hunks)app/models/holding.rb(1 hunks)app/models/recurring_transaction/identifier.rb(1 hunks)app/models/simplefin_account.rb(1 hunks)app/models/simplefin_account/investments/holdings_processor.rb(4 hunks)app/models/simplefin_account/processor.rb(1 hunks)app/models/simplefin_entry/processor.rb(3 hunks)app/models/simplefin_item.rb(4 hunks)app/models/simplefin_item/importer.rb(9 hunks)app/models/simplefin_item/syncer.rb(2 hunks)app/models/simplefin_item/unlinking.rb(1 hunks)app/services/simplefin_item/unlinker.rb(1 hunks)app/views/accounts/_account.html.erb(1 hunks)app/views/accounts/index.html.erb(1 hunks)app/views/accounts/index/_account_groups.erb(1 hunks)app/views/holdings/_holding.html.erb(1 hunks)app/views/layouts/settings.html.erb(1 hunks)app/views/layouts/shared/_htmldoc.html.erb(1 hunks)app/views/settings/_settings_nav.html.erb(1 hunks)app/views/settings/providers/_provider_form.html.erb(1 hunks)app/views/settings/providers/_simplefin_panel.html.erb(1 hunks)app/views/settings/providers/show.html.erb(1 hunks)app/views/simplefin_items/_simplefin_item.html.erb(4 hunks)app/views/simplefin_items/edit.html.erb(2 hunks)app/views/simplefin_items/errors.html.erb(1 hunks)app/views/simplefin_items/index.html.erb(0 hunks)app/views/simplefin_items/new.html.erb(1 hunks)app/views/simplefin_items/select_existing_account.html.erb(1 hunks)app/views/simplefin_items/setup_accounts.html.erb(1 hunks)app/views/simplefin_items/show.html.erb(0 hunks)app/views/transactions/show.html.erb(1 hunks)config/locales/views/simplefin_items/update.en.yml(1 hunks)config/routes.rb(1 hunks)db/migrate/20251029190000_add_extra_to_transactions.rb(1 hunks)db/migrate/20251030172500_add_cascade_on_account_deletes.rb(1 hunks)db/migrate/20251102143510_remove_duplicate_account_providers_index.rb(1 hunks)db/migrate/20251103185320_drop_was_merged_from_transactions.rb(1 hunks)db/migrate/20251104000100_add_unique_index_on_simplefin_accounts.rb(1 hunks)db/migrate/20251115194500_allow_null_merchant_id_on_recurring_transactions.rb(1 hunks)db/schema.rb(5 hunks)lib/simplefin/date_utils.rb(1 hunks)lib/tasks/holdings_tools.rake(1 hunks)lib/tasks/simplefin.rake(1 hunks)lib/tasks/simplefin_backfill.rake(1 hunks)lib/tasks/simplefin_debug.rake(1 hunks)lib/tasks/simplefin_fix_was_merged.rake(1 hunks)lib/tasks/simplefin_holdings_backfill.rake(1 hunks)lib/tasks/simplefin_unlink.rake(1 hunks)test/controllers/accounts_controller_test.rb(1 hunks)test/controllers/settings/hostings_controller_test.rb(1 hunks)test/controllers/settings/providers_controller_test.rb(1 hunks)test/controllers/simplefin_items_controller_test.rb(6 hunks)test/models/account/provider_import_adapter_test.rb(5 hunks)test/models/recurring_transaction_test.rb(2 hunks)test/models/simplefin_entry/processor_test.rb(1 hunks)test/models/simplefin_item/importer_duplicate_test.rb(1 hunks)test/services/simplefin_item/unlinker_test.rb(1 hunks)test/system/settings/ai_prompts_test.rb(0 hunks)test/views/transactions/merged_badge_view_test.rb(1 hunks)
💤 Files with no reviewable changes (3)
- test/system/settings/ai_prompts_test.rb
- app/views/simplefin_items/index.html.erb
- app/views/simplefin_items/show.html.erb
✅ Files skipped from review due to trivial changes (2)
- app/views/settings/providers/_provider_form.html.erb
- lib/tasks/simplefin_fix_was_merged.rake
🚧 Files skipped from review as they are similar to previous changes (22)
- app/models/holding.rb
- app/views/simplefin_items/edit.html.erb
- app/views/accounts/index/_account_groups.erb
- app/models/family/auto_transfer_matchable.rb
- config/locales/views/simplefin_items/update.en.yml
- app/views/accounts/index.html.erb
- db/migrate/20251103185320_drop_was_merged_from_transactions.rb
- test/controllers/accounts_controller_test.rb
- test/services/simplefin_item/unlinker_test.rb
- test/controllers/settings/hostings_controller_test.rb
- app/helpers/settings_helper.rb
- app/models/simplefin_item/unlinking.rb
- app/views/holdings/_holding.html.erb
- app/views/settings/_settings_nav.html.erb
- app/models/simplefin_entry/processor.rb
- db/migrate/20251029190000_add_extra_to_transactions.rb
- app/views/simplefin_items/setup_accounts.html.erb
- app/controllers/settings/providers_controller.rb
- app/views/simplefin_items/errors.html.erb
- test/views/transactions/merged_badge_view_test.rb
- app/helpers/simplefin_items_helper.rb
- config/routes.rb
🧰 Additional context used
🧠 Learnings (19)
📓 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: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
📚 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/layouts/settings.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:
test/models/recurring_transaction_test.rblib/tasks/simplefin_backfill.raketest/models/account/provider_import_adapter_test.rbapp/views/simplefin_items/_simplefin_item.html.erbapp/models/simplefin_account/processor.rbapp/models/account/provider_import_adapter.rbapp/jobs/simplefin_item/balances_only_job.rbapp/models/simplefin_item/syncer.rbapp/models/simplefin_account/investments/holdings_processor.rbtest/models/simplefin_entry/processor_test.rbapp/controllers/simplefin_items_controller.rbapp/models/simplefin_account.rbdb/schema.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,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations
Applied to files:
app/views/settings/providers/_simplefin_panel.html.erbapp/controllers/simplefin_items_controller.rbapp/views/simplefin_items/new.html.erb
📚 Learning: 2025-08-22T18:30:26.758Z
Learnt from: jjmata
Repo: we-promise/sure PR: 116
File: app/views/family_exports/_list.html.erb:16-28
Timestamp: 2025-08-22T18:30:26.758Z
Learning: In the family exports UI (app/views/family_exports/_list.html.erb), raw color utilities like bg-green-500/5, text-green-500, bg-red-500/5, text-red-500 are preferred over design system tokens for status indicators to maintain UI legibility and consistency with the existing import UI patterns. The user has indicated that design token replacements would make the UI illegible in this context.
Applied to files:
app/views/settings/providers/_simplefin_panel.html.erb
📚 Learning: 2025-10-24T12:37:31.878Z
Learnt from: jjmata
Repo: we-promise/sure PR: 77
File: app/assets/tailwind/google-sign-in.css:1-106
Timestamp: 2025-10-24T12:37:31.878Z
Learning: In app/assets/tailwind/google-sign-in.css: Google OAuth button styles (gsi-material-button classes) should be kept with their original hardcoded colors and fonts as provided by Google, even though they don't use design system tokens. This is a brand compliance exception.
Applied to files:
app/views/settings/providers/_simplefin_panel.html.erb
📚 Learning: 2025-09-13T11:40:26.913Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.
Applied to files:
lib/tasks/simplefin_backfill.rake
📚 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:
lib/tasks/simplefin_backfill.raketest/models/account/provider_import_adapter_test.rbapp/models/simplefin_account/processor.rbapp/jobs/simplefin_item/balances_only_job.rbapp/models/simplefin_item/syncer.rbapp/models/simplefin_account/investments/holdings_processor.rbtest/models/simplefin_entry/processor_test.rbapp/controllers/simplefin_items_controller.rbapp/models/simplefin_account.rbdb/schema.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Applied to files:
lib/tasks/simplefin_backfill.rakeapp/models/simplefin_item/importer.rbapp/models/simplefin_account.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
Applied to files:
lib/tasks/simplefin_backfill.rakeapp/models/simplefin_account.rb
📚 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:
lib/tasks/simplefin_backfill.raketest/controllers/simplefin_items_controller_test.rbapp/views/simplefin_items/_simplefin_item.html.erbtest/models/simplefin_item/importer_duplicate_test.rbapp/models/simplefin_item/importer.rbapp/controllers/concerns/simplefin_items/maps_helper.rbdb/migrate/20251104000100_add_unique_index_on_simplefin_accounts.rbapp/controllers/simplefin_items_controller.rbapp/models/simplefin_account.rbapp/models/simplefin_item.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:
lib/tasks/simplefin_backfill.rakeapp/views/simplefin_items/_simplefin_item.html.erbapp/models/account/provider_import_adapter.rbapp/models/simplefin_item/importer.rbapp/controllers/accounts_controller.rbapp/controllers/concerns/simplefin_items/maps_helper.rbapp/controllers/simplefin_items_controller.rbdb/schema.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,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/simplefin_items/_simplefin_item.html.erbapp/views/transactions/show.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/simplefin_items/_simplefin_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 ViewComponents when elements have complex logic/styling, are reused across contexts, need variants/slots/accessibility, or require Stimulus interactivity
Applied to files:
app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Applied to files:
app/models/simplefin_account/processor.rbapp/models/simplefin_item/importer.rbapp/models/simplefin_item/syncer.rb
📚 Learning: 2025-11-14T14:22:37.038Z
Learnt from: jjmata
Repo: we-promise/sure PR: 326
File: app/models/category_import.rb:10-10
Timestamp: 2025-11-14T14:22:37.038Z
Learning: In the CategoryImport model (app/models/category_import.rb), the import process implements a "last write wins" strategy where importing a category.csv file completely overrides existing category structures, including parent relationships. Setting category.parent = nil in the first pass is intentional behavior to ensure the CSV becomes the source of truth.
Applied to files:
app/models/simplefin_item/importer.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/simplefin_items_controller.rbapp/models/simplefin_account.rbdb/schema.rb
📚 Learning: 2025-08-22T23:27:46.033Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: db/migrate/20250813144520_add_institution_fields_to_simplefin_items.rb:6-6
Timestamp: 2025-08-22T23:27:46.033Z
Learning: Institution URLs in financial contexts are typically short (under 100 characters) and don't require text columns - varchar(255) via :string is sufficient for SimpleFin institution URLs.
Applied to files:
db/schema.rb
🧬 Code graph analysis (14)
test/controllers/simplefin_items_controller_test.rb (2)
test/controllers/accounts_controller_test.rb (1)
fixtures(148-196)app/models/simplefin_item.rb (1)
accounts(38-45)
test/models/simplefin_item/importer_duplicate_test.rb (1)
app/models/simplefin_item/importer.rb (2)
import_balances_only(35-68)stats(116-118)
app/models/simplefin_account/processor.rb (1)
app/models/simplefin_account.rb (2)
current_account(16-18)ensure_account_provider!(22-35)
app/models/account/provider_import_adapter.rb (3)
app/models/simplefin_entry/processor.rb (3)
name(59-74)merchant(135-148)notes(155-167)app/models/plaid_entry/processor.rb (2)
name(37-39)merchant(62-70)app/models/concerns/enrichable.rb (1)
enrich_attribute(26-28)
app/models/simplefin_item/importer.rb (5)
app/models/simplefin_item/syncer.rb (2)
attr_reader(1-205)initialize(4-6)app/models/provider/simplefin.rb (4)
attr_reader(87-94)initialize(7-8)initialize(90-93)get_accounts(27-74)app/models/simplefin_item.rb (1)
upsert_simplefin_snapshot!(72-81)app/models/simplefin_account.rb (1)
upsert_simplefin_snapshot!(37-55)app/models/account/provider_import_adapter.rb (1)
update_balance(110-115)
app/controllers/accounts_controller.rb (2)
app/models/simplefin_item.rb (1)
accounts(38-45)app/models/concerns/syncable.rb (1)
latest_sync(67-69)
app/jobs/simplefin_item/balances_only_job.rb (3)
app/models/simplefin_item/provided.rb (1)
simplefin_provider(4-6)app/models/simplefin_item/importer.rb (1)
import_balances_only(35-68)app/models/simplefin_item.rb (1)
accounts(38-45)
app/models/simplefin_item/syncer.rb (3)
app/models/simplefin_item/importer.rb (2)
import_balances_only(35-68)stats(116-118)app/models/simplefin_item.rb (2)
import_latest_simplefin_data(52-54)accounts(38-45)app/models/sync.rb (1)
perform_post_sync(144-151)
app/models/simplefin_account/investments/holdings_processor.rb (2)
app/models/simplefin_entry/processor.rb (7)
account(45-47)import_adapter(41-43)amount(76-93)currency(95-97)date(107-123)external_id(53-57)name(59-74)app/models/account/provider_import_adapter.rb (1)
import_holding(131-193)
app/controllers/concerns/simplefin_items/maps_helper.rb (2)
app/models/concerns/syncable.rb (1)
latest_sync(67-69)app/models/simplefin_item/importer.rb (1)
stats(116-118)
test/models/simplefin_entry/processor_test.rb (1)
app/models/simplefin_entry/processor.rb (3)
process(11-23)name(59-74)currency(95-97)
app/services/simplefin_item/unlinker.rb (1)
app/models/simplefin_item/unlinking.rb (1)
unlink_all!(13-56)
app/controllers/simplefin_items_controller.rb (5)
app/models/account.rb (1)
include(1-227)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)app/controllers/concerns/simplefin_items/maps_helper.rb (1)
build_simplefin_maps_for(9-78)app/controllers/concerns/notifiable.rb (1)
flash_notification_stream_items(18-34)app/models/simplefin_account.rb (1)
current_account(16-18)
app/models/simplefin_item.rb (3)
app/controllers/simplefin_items_controller.rb (3)
include(1-497)sync(132-141)new(80-82)app/models/simplefin_item/provided.rb (1)
simplefin_provider(4-6)app/models/simplefin_item/importer.rb (1)
import(11-32)
🪛 ast-grep (0.39.9)
app/controllers/simplefin_items_controller.rb
[warning] 240-240: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 346-346: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 358-358: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci / test
🔇 Additional comments (46)
lib/tasks/holdings_tools.rake (4)
1-14: LGTM: Clear documentation and helpful examples.The file header provides clear documentation with practical usage examples for both tasks, making the rake tasks easy to use.
17-28: LGTM: Flexible argument parsing with sensible defaults.The key=value parsing pattern allows both positional and named arguments, and the default values (2% change, 1 day ago) are appropriate for demonstration purposes.
29-46: LGTM: Robust dry-run handling with safe defaults.The dry-run logic now properly defaults to
true(preview mode) when no value is provided, with strict validation of allowed values. This addresses the safety concern from previous reviews and prevents accidental data writes.
48-66: LGTM: Clean snapshot logic with proper error handling.The snapshot creation logic is straightforward, and the error handling correctly captures and reports actual exception details. The dry-run preview output is also appropriate.
app/views/layouts/shared/_htmldoc.html.erb (1)
13-13: Verify intentional scope of app-wide cache disable.The
turbo-cache-control: no-cachemeta tag in the shared_htmldoc.html.erblayout affects all pages using 7 layouts (settings, wizard, onboardings, blank, imports, auth, application). This disables Turbo Drive caching globally, causing a documented performance regression of 500–800ms per navigation.Confirm whether this broad scope is intentional. If the goal is to ensure fresh data only for specific pages (e.g., settings or SimpleFin), consider:
- Moving the meta tag to specific layout files (
settings.html.erb, etc.)- Using per-element
data-turbo="false"ordata-turbo-temporaryattributes on dynamic sections- Using per-page
<meta name="turbo-cache-control">in targeted layouts insteaddb/migrate/20251030172500_add_cascade_on_account_deletes.rb (3)
37-51: Consistent FK replacement pattern.The FK replacement logic for
holdingsandaccount_providersfollows the same pattern asentries, maintaining consistency across the migration. Thetable_exists?guards appropriately handle optional schema elements.
54-70: Down method correctly reverts cascade behavior.The rollback logic appropriately removes the
ON DELETE CASCADEconstraint and restores standard foreign keys. The conditional checks ensure only existing foreign keys are modified, preventing errors during rollback.
6-28: Verify destructive orphan cleanup is tested.The migration code is well-structured with proper guards and correct SQL logic. However, no test file was found for this migration in the codebase. Confirm that the cleanup is covered by automated tests (migration specs, integration tests, or CI/CD) with production-like data volumes to ensure:
- Orphan cleanup completes without error in test environment
- No unintended deletions occur (entries contain transaction data)
- Cascades work correctly after cleanup on all three tables:
entries,holdings,account_providersapp/views/settings/providers/show.html.erb (1)
11-21: LGTM! Clean separation of SimpleFin UI.The guard clause properly excludes SimpleFin from the generic provider loop, and the dedicated turbo-frame section enables dynamic UI updates specific to SimpleFin's enhanced workflows.
app/models/account.rb (2)
31-33: LGTM! Clean scope composition.The new
visible_manualscope provides a convenient way to query visible manual accounts by composing existing scopes.
170-178: LGTM! Formatting improvement.The reformatted
current_holdingsmethod improves readability without changing behavior.test/controllers/settings/providers_controller_test.rb (1)
11-18: LGTM! Clean refactor to configuration-based approach.The test now uses Rails configuration stubs instead of environment variable overrides, aligning with the app-wide shift to configuration-based app mode detection.
lib/simplefin/date_utils.rb (1)
1-28: LGTM! Robust date parsing utility.The implementation correctly handles multiple input types and uses UTC for epoch timestamps to ensure consistent date conversion across different timezone configurations. The error handling properly returns nil for unparseable inputs.
db/migrate/20251102143510_remove_duplicate_account_providers_index.rb (1)
1-18: LGTM! Safe and reversible index cleanup.The migration properly removes the duplicate index with existence guards for idempotency and implements full reversibility. The inline documentation clearly explains the rationale.
app/models/account/provider_import_adapter.rb (2)
18-21: LGTM! Well-documented parameter additions.The new
notesandextraparameters are properly documented and extend the adapter to support richer transaction metadata from providers.
69-78: LGTM! Proper enrichment and metadata handling.The implementation correctly:
- Uses the enrichment pattern for
notesto respect user overrides- Deep merges
extrametadata with string keys for consistency- Includes appropriate guards and type checks
test/models/account/provider_import_adapter_test.rb (1)
313-313: LGTM! Improved test robustness with relative dates.The date adjustments prevent conflicts with existing fixtures by using relative offsets instead of fixed dates. This makes the tests more reliable.
db/migrate/20251115194500_allow_null_merchant_id_on_recurring_transactions.rb (1)
1-5: LGTM!The migration correctly allows NULL values on the
merchant_idcolumn. The use ofchangeis appropriate sincechange_column_nullis reversible.app/views/settings/providers/_simplefin_panel.html.erb (1)
1-43: LGTM!The SimpleFin panel structure is clean and follows the design system guidelines. The previous concern about using raw color utilities has been addressed (line 37 now uses
bg-success). The auto-submit pattern with Stimulus is appropriate for this use case.db/migrate/20251104000100_add_unique_index_on_simplefin_accounts.rb (1)
1-19: LGTM!The migration correctly implements a partial unique index to enforce at-most-one non-null
account_idper(simplefin_item_id, account_id)pair. The idempotency checks in bothupanddownmethods ensure safe re-runs.lib/tasks/simplefin.rake (1)
34-117: LGTM!The encryption task is well-structured with proper safeguards:
- Defaults to dry-run mode for safety (addressed from past review)
- Validates encryption configuration before proceeding
- Implements batch processing with configurable limits
- Includes comprehensive error handling and reporting
lib/tasks/simplefin_debug.rake (2)
28-65: LGTM!The transaction debugging logic correctly handles the raw payload with appropriate sorting and formatting. Previous concerns about timestamp handling have been addressed.
67-105: LGTM!The entries debugging task correctly uses database-level filtering with SQL injection protection (line 75) and eager-loads the
entryableassociation (line 87) to avoid N+1 queries. Previous concerns have been addressed.app/controllers/concerns/simplefin_items/maps_helper.rb (1)
9-78: LGTM!The batch query approach correctly eliminates N+1 queries by precomputing family-level and item-level data. The per-item loop uses the precomputed results efficiently, and the error handling at line 66 is appropriate.
app/views/accounts/_account.html.erb (1)
36-36: LGTM—Investment accounts can now link providers.This change logically extends provider linking to Investment accounts alongside Depository and CreditCard types, aligning with the SimpleFin enhancements that support investment account syncing.
test/models/recurring_transaction_test.rb (1)
243-246: LGTM—Skip guards handle schema migration transition.The conditional skip guards appropriately handle environments where the
merchant_idcolumn is still NOT NULL. This allows the tests to coexist with the migration that makesmerchant_idnullable for name-based recurring transactions.Also applies to: 287-290
app/models/simplefin_account/processor.rb (2)
12-16: LGTM—Manual relink guard prevents auto-linking.The early return when
current_accountis missing ensures relinking is manual and user-confirmed via the modal, addressing past concerns about error-prone auto-linking.
19-24: LGTM—Provider link maintenance with graceful error handling.The call to
ensure_account_provider!correctly maintains the AccountProvider linkage after processing, and the rescue block appropriately logs failures without interrupting the broader processing flow.app/views/transactions/show.html.erb (1)
100-146: LGTM—Clean rendering with logic delegated to helper.The "Additional details" section appropriately delegates business logic to the
build_transaction_extra_detailshelper and focuses solely on rendering the structured data. The conditional rendering for SimpleFin vs. raw data is clear and maintainable.app/models/simplefin_item.rb (2)
14-21: LGTM—Encryption readiness check supports multiple configurations.The
encryption_ready?method correctly checks for Active Record Encryption configuration in both Rails credentials and environment variables, supporting different deployment scenarios.
52-53: LGTM—Backward-compatible sync parameter addition.The optional
syncparameter allows the importer to track sync context while maintaining backward compatibility with existing calls that don't provide it.app/views/simplefin_items/new.html.erb (1)
1-24: LGTM—Streamlined connection flow with accessible form.The conversion from modal to standalone page simplifies the UI flow. The form now correctly uses
f.labelfor proper accessibility and label-to-input association, addressing previous concerns. The inline error display and direct form structure are clear improvements.app/helpers/transactions_helper.rb (1)
22-77: LGTM—Well-structured helper with proper error handling.The
build_transaction_extra_detailshelper cleanly extracts presentation logic from the view and returns a well-structured hash. The use ofrescue StandardError(line 67) appropriately catches only expected errors, addressing the previous concern about bare rescue. The method handles SimpleFin-specific data, provider extras, and raw fallback gracefully.app/services/simplefin_item/unlinker.rb (1)
1-17: Clean deprecation wrapper for backward compatibility.The deprecation notice is clear, and the delegation to
item.unlink_all!is straightforward. This approach maintains backward compatibility while encouraging migration to the new model-based API.test/models/simplefin_entry/processor_test.rb (1)
1-56: Well-structured test with comprehensive assertions.The test setup is clear, and the assertions thoroughly validate the extra metadata persistence, including the nested structure. The test correctly validates that payee, memo, description, and extra are all captured in the "simplefin" sub-hash.
lib/tasks/simplefin_unlink.rake (1)
1-64: Solid task implementation with proper safeguards.The task correctly:
- Defaults to non-destructive dry_run mode
- Validates UUID format before processing
- Uses recursive redaction to protect sensitive data
- Provides clear JSON output for automation
app/models/simplefin_account.rb (2)
12-12: Good uniqueness validation to prevent duplicate upstream accounts.The validation correctly scopes uniqueness to
simplefin_item_idwithallow_nil: true, which prevents duplicate SimpleFin account IDs within the same item while allowing temporary nil values during creation.
20-35: Correctly implements provider linking with proper scope.The method correctly addresses the previous review concern by looking up the AccountProvider by
provider_typeandprovider_idonly, then assigning the account. This ensures that when a SimpleFin account is relinked to a different Sure account, the existing provider row is updated rather than attempting to create a duplicate.The rescue block appropriately logs errors and returns nil for graceful degradation.
app/views/simplefin_items/select_existing_account.html.erb (1)
1-40: Clean modal implementation with helpful empty state.The conditional rendering provides clear guidance when no accounts are available for linking, and the form structure uses DS components appropriately. The balance calculation pattern is consistent with other integrations in the codebase.
test/controllers/simplefin_items_controller_test.rb (3)
227-233: Clear test for empty-state rendering.The test correctly validates that the modal shows helpful guidance when no SimpleFIN accounts are available for linking, matching the view implementation.
234-246: Good coverage of unlinking behavior.This test validates that provider links and legacy foreign keys are properly cleaned up during deletion, ensuring data consistency.
249-274: Validates setup flow correctly handles linked vs unlinked accounts.The test ensures that
complete_account_setuponly creates accounts for truly unlinked SimpleFIN accounts and preserves existing links, which is the correct behavior.lib/tasks/simplefin_holdings_backfill.rake (1)
1-141: Comprehensive backfill task with good safeguards.The task provides multiple selection strategies, defaults to safe dry-run mode, includes rate limiting via sleep_ms, and produces structured JSON output for automation. The recursive processing handles errors gracefully per-account without aborting the entire run.
lib/tasks/simplefin_backfill.rake (1)
84-88: Nice: coerces Arrays into a Relation beforefind_each.Prevents
NoMethodErrorand retains batching.db/schema.rb (1)
931-935: LGTM:transactions.extra(jsonb) with GIN index.Fits queryability for metadata and aligns with PR goals.
app/models/simplefin_item/importer.rb (1)
120-124: No action required—sync_statsserialization is properly configured.The
sync_statsattribute in the Sync model is already declared with JSON serialization (serialize :sync_stats, coder: JSONatapp/models/sync.rb:24), so Rails will correctly handle Hash serialization/deserialization. The code is safe as written.Likely an incorrect or invalid review comment.
…prove holdings task error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
lib/tasks/holdings_tools.rake (1)
70-74: Fix parameter name inconsistency in error message.The error message on line 72 says
"Provide id"but the task parameter is:snapshot_id. This inconsistency remains from the previous review and could confuse users about which argument to supply.Apply this diff:
unless id - puts({ ok: false, error: "usage", message: "Provide id" }.to_json) + puts({ ok: false, error: "usage", message: "Provide snapshot_id" }.to_json) exit 1 endapp/controllers/simplefin_items_controller.rb (1)
143-152: Synchronous SimpleFin API call blocks request thread.Line 146 performs a synchronous
perform_synccall during the HTTP request. If the SimpleFin API is slow or unavailable, this blocks the request thread and degrades UX—a reliability anti-pattern. According to past review comments, this was supposedly addressed in commit 7848530, but the synchronous call remains in the current code.Use an async pattern similar to the
syncaction (lines 132-141):def balances + unless @simplefin_item.syncing? sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) - SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) + SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id) + end respond_to do |format| format.html { redirect_back_or_to accounts_path } - format.json { render json: { ok: true, sync_id: sync.id } } + format.json { render json: { ok: true } } end end
🧹 Nitpick comments (4)
app/controllers/simplefin_items_controller.rb (4)
317-319: Clarify intent of rescuingdisable!error.Line 318 swallows exceptions from
previous_account.disable!without logging. If disabling the orphaned account fails (e.g., due to validation errors or callbacks), this silently continues. While this might be intentional to avoid blocking the relink operation, it could hide legitimate issues.Consider logging the error to aid debugging:
if previous_account && previous_account.id != @account.id && previous_account.family_id == @account.family_id - previous_account.disable! rescue nil + begin + previous_account.disable! + rescue => e + Rails.logger.warn("Failed to disable orphaned account #{previous_account.id}: #{e.message}") + end end
362-362: Remove unnecessary cache-busting query parameter.Line 362 appends
cache_bust: SecureRandom.hex(6)to the redirect URL for non-Turbo requests. This is unusual and likely unnecessary—Rails routing will ignore unrecognized params, and browser caching is typically handled via HTTP headers. If this is intentional to force a page refresh, document why; otherwise, remove it.- redirect_to accounts_path(cache_bust: SecureRandom.hex(6)), notice: "Account successfully linked to SimpleFIN", status: :see_other + redirect_to accounts_path, notice: "Account successfully linked to SimpleFIN", status: :see_other
405-405: Specify exception type in rescue clause.Line 405 uses a bare
rescuewhich catches all exceptions, includingSystemExitandSignalException. This could hide serious errors during balance parsing.- sfa_balance = (sfa.current_balance || sfa.available_balance).to_d rescue 0.to_d + begin + sfa_balance = (sfa.current_balance || sfa.available_balance).to_d + rescue TypeError, ArgumentError + sfa_balance = 0.to_d + end
428-429: Extract magic number as named constant.Lines 428-429 use
BigDecimal("1.00")as a threshold for detecting balance conflicts during last4 matching. This magic number makes the heuristic less transparent.+ BALANCE_CONFLICT_THRESHOLD = BigDecimal("1.00") + def compute_relink_candidates # ... begin ab = (cand.balance || cand.cash_balance || 0).to_d - if sfa_balance.nonzero? && ab.nonzero? && (ab - sfa_balance).abs > BigDecimal("1.00") + if sfa_balance.nonzero? && ab.nonzero? && (ab - sfa_balance).abs > BALANCE_CONFLICT_THRESHOLD cand = nil endSimilarly, extract
BigDecimal("0.01")at line 447 asBALANCE_PROXIMITY_THRESHOLD.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
app/controllers/simplefin_items_controller.rb(6 hunks)app/models/recurring_transaction/identifier.rb(0 hunks)lib/tasks/holdings_tools.rake(1 hunks)
💤 Files with no reviewable changes (1)
- app/models/recurring_transaction/identifier.rb
🧰 Additional context used
🧠 Learnings (7)
📓 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: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
📚 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/controllers/simplefin_items_controller.rb
📚 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/simplefin_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/simplefin_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/simplefin_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/simplefin_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/simplefin_items_controller.rb
🧬 Code graph analysis (1)
app/controllers/simplefin_items_controller.rb (6)
app/models/account.rb (2)
include(1-227)destroy_later(154-157)app/models/simplefin_item.rb (3)
include(1-198)destroy_later(47-50)accounts(38-45)app/controllers/plaid_items_controller.rb (3)
sync(40-49)new(4-14)link_existing_account(68-91)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)app/controllers/concerns/simplefin_items/maps_helper.rb (1)
build_simplefin_maps_for(9-78)app/models/simplefin_account.rb (1)
current_account(16-18)
🪛 ast-grep (0.39.9)
app/controllers/simplefin_items_controller.rb
[warning] 240-240: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 346-346: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 358-358: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci / test
🔇 Additional comments (1)
lib/tasks/holdings_tools.rake (1)
17-66: Well-structured task with proper defaults and validation.The
seed_prev_snapshottask implementation is solid:
- Correctly defaults
dry_runtotruefor safety (lines 32-33)- Strict validation of
dry_runvalues prevents accidental writes (lines 34-41)- Clear comment explaining the factor semantics (line 51)
- Proper exception handling with detailed error reporting (lines 63-66)
…ink actions; removed dead helper
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/controllers/simplefin_items_controller.rb (1)
18-78: Refactor to use async Sync framework; synchronous API call during HTTP request blocks user interaction.Line 31 calls
import_latest_simplefin_datasynchronously during the HTTP request, which blocks the request thread until SimpleFin responds. When the user receives the Turbo response (lines 54–63), they see success, but no data has been fetched yet if the API is slow.This is architecturally inconsistent with the codebase. The
Syncmodel andSimplefinItem::Syncerare designed for background jobs (seeSimplefinItem::BalancesOnlyJob), but the update action bypasses this framework entirely.Recommendation:
- Create a
SimplefinItem::UpdateJobthat validates the token asynchronously and transfers accounts- Return immediate Turbo response with optimistic feedback (e.g., "Validating token...")
- Broadcast completion/error status to the UI once the job completes
- This matches the pattern already established for balances-only syncs and avoids blocking the request thread
Alternatively, if token validation is non-negotiable before marking update complete, add a code comment explaining why synchronous execution is safe and necessary here.
♻️ Duplicate comments (1)
app/controllers/simplefin_items_controller.rb (1)
143-152: Move synchronous SimpleFin API call to background job.Line 146 calls
perform_syncsynchronously in the request thread. If the SimpleFin API is slow or unavailable, this blocks the request thread and degrades user experience. The PR objectives explicitly mention "backgrounded balances import for faster UI", suggesting this should be async.Apply this diff to make balances-only sync asynchronous:
def balances - sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) - SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) + unless @simplefin_item.syncing? + sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) + SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id) + end respond_to do |format| format.html { redirect_back_or_to accounts_path } - format.json { render json: { ok: true, sync_id: sync.id } } + format.json { render json: { ok: true } } end endAlternatively, create a dedicated
SimplefinItemBalancesSyncJobif balances-only syncs need different handling than full syncs.
🧹 Nitpick comments (1)
app/controllers/simplefin_items_controller.rb (1)
407-501: Consider defensive logging and refactoring for testability.The
compute_relink_candidatesmethod implements sophisticated heuristic matching (last4 → balance proximity → name) with appropriate ambiguity and conflict guards. The logic appears sound overall.However, a few improvements would help maintainability:
Line 409:
rescue nilsilently swallows all dedup errors, which could hide issues. Consider logging exceptions:begin @simplefin_item.dedup_simplefin_accounts! rescue => e Rails.logger.warn("SimpleFin dedup failed before relink: #{e.class} - #{e.message}") endLine 453: The
$1.00threshold for balance conflict detection is somewhat arbitrary. Consider extracting as a constant:BALANCE_CONFLICT_THRESHOLD = BigDecimal("1.00")Complexity: The method has high cyclomatic complexity. Consider extracting the three matching strategies into separate private methods:
def match_by_last4(sfa, manuals, used_ids, sfa_balance) # lines 435-463 end def match_by_balance(sfa, manuals, used_ids, sfa_balance) # lines 467-480 end def match_by_name(sfa, manuals, used_ids) # lines 483-489 endThis would improve testability and readability without changing behavior.
The used_manual_ids tracking and 1:1 matching constraints are well-designed.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/controllers/simplefin_items_controller.rb(6 hunks)app/views/simplefin_items/errors.html.erb(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/views/simplefin_items/errors.html.erb
🧰 Additional context used
🧠 Learnings (7)
📓 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.
📚 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/controllers/simplefin_items_controller.rb
📚 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/simplefin_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/simplefin_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/simplefin_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/simplefin_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/simplefin_items_controller.rb
🧬 Code graph analysis (1)
app/controllers/simplefin_items_controller.rb (4)
app/models/account.rb (2)
include(1-227)destroy_later(154-157)app/models/simplefin_item.rb (3)
include(1-198)destroy_later(47-50)accounts(38-45)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)app/controllers/concerns/simplefin_items/maps_helper.rb (1)
build_simplefin_maps_for(9-78)
🪛 ast-grep (0.39.9)
app/controllers/simplefin_items_controller.rb
[warning] 240-240: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 346-346: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 358-358: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci / test
🔇 Additional comments (4)
app/controllers/simplefin_items_controller.rb (4)
54-63: LGTM: Turbo Frame handling is well-implemented.The Turbo Stream responses throughout the controller (update, create, complete_account_setup, link_existing_account) properly:
- Check
turbo_frame_request?before rendering streams- Replace/update appropriate DOM targets
- Include flash notification streams
- Rebuild data models (manual accounts, SimpleFin maps) to ensure UI consistency
- Use proper fallback redirects for non-Turbo requests
The pattern is consistent and follows Turbo conventions correctly.
Also applies to: 95-104, 222-254, 322-363
274-320: LGTM: Solid guard and relink logic.The guard at lines 279-286 correctly ensures only manual accounts (no existing provider links or legacy IDs) can be linked, preventing data corruption from double-linking.
The relink behavior (lines 300-320) is well-designed:
- Uses transaction with explicit lock to prevent race conditions
- Clears legacy association before upsetting provider link
- Tracks previous account and disables it if orphaned (clever cleanup)
- Swallows errors from orphan disable (acceptable since it's cleanup)
The deterministic upsert pattern via
find_or_initialize_by(provider: simplefin_account)ensures no duplicate AccountProvider records.
366-394: LGTM: Clean error aggregation for modal display.The
errorsaction properly:
- Finds the latest sync (with optimization for loaded associations)
- Extracts and normalizes errors from sync_stats (handling both Hash and String formats)
- Falls back to item-level
sync_errorif present- Deduplicates and filters empty messages
- Renders as a modal (layout: false)
This is a straightforward, defensive implementation for surfacing sync errors to users.
257-272: LGTM: Clean modal preparation with intentional design.The
select_existing_accountaction properly:
- Filters out the no-op case (SFA already linked to this account)
- Intentionally keeps SFAs linked to other accounts (to allow reassignment in unified flow, per comment)
- Orders newest-first for better UX
- Always renders a modal (either with choices or empty-state)
The filtering logic at line 266 correctly uses
account_provider&.account_idto safely check the current link.
sokie
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested it also and works good and is stable.
with the account creation and unlinking it also looks really good!
sokie
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Connection and stability is really good, tested it and it works great.
Unfortunately I found a bug with the linking feature.
It seems I can link accounts from "Other" to SimpleFIN accounts that are already linked and synced.
This means I end up getting multiple acocunts linked to the same remote SimpleFIn account ( screenshot 2 ).
In the Lunch Flow link feature, I used to check for accounts that are already linked, and not show those, that functionality seems to also be broken now. Also when I create accounts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
app/controllers/simplefin_items_controller.rb (1)
143-152: Critical: Synchronous API call blocks request thread.Line 146 calls
perform_syncsynchronously during the HTTP request. Even though theSyncerhas a "fast path" for balances-only syncs, this blocks the request thread if the SimpleFin API is slow, rate-limited, or unavailable, degrading UX and tying up application resources.The
syncaction (lines 132-141) demonstrates the correct async pattern usingsync_later. Apply a similar approach here:def balances - sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) - SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) + unless @simplefin_item.syncing? + sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) + SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id) + end respond_to do |format| format.html { redirect_back_or_to accounts_path } - format.json { render json: { ok: true, sync_id: sync.id } } + format.json { render json: { ok: true } } end endAlternatively, if you intentionally keep it synchronous because balances-only is guaranteed lightweight, add a clear comment explaining why inline execution is safe.
🧹 Nitpick comments (2)
app/controllers/simplefin_items_controller.rb (2)
321-339: Remove unnecessary reload.Line 339 reloads
@account, but the transaction (lines 299-319) doesn't modify the account record itself—only its associations. The reload is unnecessary and adds a redundant database query.flash[:notice] = "Account successfully linked to SimpleFIN" - @account.reload manual_accounts_stream = if @manual_accounts.any?
448-470: Extract magic numbers to named constants.Lines 452 and 470 use hardcoded thresholds (
1.00for balance conflict detection and0.01for balance proximity matching) that would be clearer as named constants.+ # Relink heuristic thresholds + BALANCE_CONFLICT_THRESHOLD = BigDecimal("1.00") + BALANCE_PROXIMITY_THRESHOLD = BigDecimal("0.01") + def compute_relink_candidates # ... # Conflict guard: if both have balances and differ wildly, skip begin ab = (cand.balance || cand.cash_balance || 0).to_d - if sfa_balance.nonzero? && ab.nonzero? && (ab - sfa_balance).abs > BigDecimal("1.00") + if sfa_balance.nonzero? && ab.nonzero? && (ab - sfa_balance).abs > BALANCE_CONFLICT_THRESHOLD cand = nil end rescue # ... end # ... balance_matches = manuals.reject { |a| used_manual_ids.include?(a.id) }.select do |a| begin ab = (a.balance || a.cash_balance || 0).to_d - (ab - sfa_balance).abs <= BigDecimal("0.01") + (ab - sfa_balance).abs <= BALANCE_PROXIMITY_THRESHOLD rescue false end end
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/controllers/simplefin_items_controller.rb(6 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 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/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/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/controllers/simplefin_items_controller.rb
📚 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/simplefin_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/simplefin_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/simplefin_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/simplefin_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/simplefin_items_controller.rb
🧬 Code graph analysis (1)
app/controllers/simplefin_items_controller.rb (4)
app/models/account.rb (2)
include(1-227)destroy_later(154-157)app/models/simplefin_item.rb (3)
include(1-198)destroy_later(47-50)accounts(38-45)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)app/controllers/concerns/simplefin_items/maps_helper.rb (1)
build_simplefin_maps_for(9-78)
🪛 ast-grep (0.39.9)
app/controllers/simplefin_items_controller.rb
[warning] 240-240: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 345-345: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 357-357: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci / test
🔇 Additional comments (4)
app/controllers/simplefin_items_controller.rb (4)
365-393: LGTM!The
errorsaction demonstrates good defensive coding: it handles both loaded and unloaded associations (lines 367-371), normalizes error formats from Hash or String (lines 376-382), falls back to item-level errors (lines 385-387), and deduplicates the results (line 390).
54-63: LGTM: Consistent Turbo Stream patterns.The Turbo Stream handling across
update,create,complete_account_setup, andlink_existing_accountfollows a consistent pattern: conditional rendering based onturbo_frame_request?, reloading data and recomputing UI maps viabuild_simplefin_maps_for(which uses batch queries per the MapsHelper), targeted DOM updates via multiple stream operations, and graceful fallback to redirects for non-Turbo requests. The pattern aligns with Rails and Turbo best practices.Also applies to: 95-104, 222-254, 321-362
121-129: LGTM: Proper cleanup before deletion.The destroy action now calls
unlink_all!(lines 122-127) to detach provider links and legacy associations before scheduling deletion, preventing foreign key violations and orphaned records. The rescue block ensures the deletion proceeds even if unlinking fails, which is appropriate for a destroy operation.
2-3: LGTM: Clean module inclusion and before_action.The
SimplefinItems::MapsHelperinclusion (line 2) provides thebuild_simplefin_maps_formethod used throughout the controller, and thebefore_actionlist (line 3) correctly covers all actions that require@simplefin_itemto be set.
sokie
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed and gtg
Signed-off-by: soky srm <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| # Starts a balances-only sync for this SimpleFin item | ||
| def balances | ||
| sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) | ||
| SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Blocking Sync Harms User Experience
The balances action performs a synchronous sync operation (SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)) directly in the controller request cycle, which can cause timeouts and poor user experience for long-running operations. The sync should be enqueued as a background job instead, similar to how the sync action uses sync_later. This blocks the HTTP response until the entire sync completes, potentially causing request timeouts.
| # follows the chosen account. | ||
| if previous_account && previous_account.id != @account.id && previous_account.family_id == @account.family_id | ||
| previous_account.disable! rescue nil | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Incorrect account disabling overlooks active links.
When relinking a SimpleFin account to a different manual account, the code disables previous_account without verifying it's actually orphaned. If previous_account has other active account_providers links (from different providers like Plaid), it will still be disabled incorrectly. The check should reload previous_account after the provider link update and verify previous_account.account_providers.none? before disabling.
|
|
||
| # Return without the reason field to the view | ||
| pairs.map { |p| p.slice(:sfa_id, :sfa_name, :manual_id, :manual_name) } | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Clean Up Obsolete Auto-Linking Code
The compute_relink_candidates method (and its helper normalize_name) is defined but never called anywhere in the controller or codebase. This appears to be dead code from a removed auto-linking feature mentioned in the PR discussion, where the team decided to make linking manual-only instead of automatic.
| format.html { redirect_back_or_to accounts_path } | ||
| format.json { render json: { ok: true, sync_id: sync.id } } | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Synchronous Balances Sync Degrades User Experience
The balances action performs a sync synchronously by calling SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) directly in the controller, blocking the HTTP request until completion. This differs from the sync action which uses sync_later for async processing. A SimplefinItem::BalancesOnlyJob exists but is never enqueued. Synchronous syncs can timeout or cause poor UX, especially with network delays or rate limits.
| end | ||
|
|
||
| simplefin_account.save! | ||
| begin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Deduplication Flaw Hides Unique Transactions
Transaction deduplication in import_account uses tx[:id] || tx[:fitid] || [tx[:posted], tx[:amount], tx[:description]] as the uniqueness key. When both tx[:id] and tx[:fitid] are nil, it falls back to an array composite key. This incorrectly treats legitimately different transactions as duplicates if they share the same posted date, amount, and description (e.g., two identical purchases on the same day from the same merchant).
jjmata
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wohoo! 🦣 PR going in ...
| t.decimal "expected_amount_avg", precision: 19, scale: 4 | ||
| t.index ["family_id", "merchant_id", "amount", "currency"], name: "idx_recurring_txns_merchant", unique: true, where: "(merchant_id IS NOT NULL)" | ||
| t.index ["family_id", "name", "amount", "currency"], name: "idx_recurring_txns_name", unique: true, where: "((name IS NOT NULL) AND (merchant_id IS NULL))" | ||
| t.index ["family_id", "merchant_id", "amount", "currency"], name: "idx_recurring_txns_on_family_merchant_amount_currency", unique: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Nulls Compromise Recurring Transaction Uniqueness
The unique index idx_recurring_txns_on_family_merchant_amount_currency on recurring_transactions includes merchant_id but doesn't handle NULL values correctly. After migration 20251115194500 allows merchant_id to be NULL, the unique constraint will fail for name-based recurring transactions (where merchant_id is NULL) because multiple NULL values in a unique index are treated as distinct in PostgreSQL. This breaks the uniqueness guarantee for name-based patterns with the same family/amount/currency, allowing duplicate recurring transaction records.
| # Starts a balances-only sync for this SimpleFin item | ||
| def balances | ||
| sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) | ||
| SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Balances Sync: Unblock User Experience
The balances action performs a synchronous sync by calling SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) directly in the controller, blocking the HTTP request until completion. This can cause request timeouts for slow API calls and prevents the user from seeing progress updates. The sync should be enqueued as a background job like the sync action does with sync_later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
app/controllers/simplefin_items_controller.rb (2)
298-319: Major: Disabling account overlooks other active provider links.Lines 316-317 disable
previous_accountafter relinking, but fail to check whether it has other activeAccountProviderrecords (e.g., from Plaid or other SimpleFin items). This incorrectly hides accounts that still have live provider data.Scenario:
- Manual account M has SimpleFin provider link (AP1)
- M also has Plaid provider link (AP2)
- User relinks SimpleFin account to M2
- Code updates AP1 to point to M2
- Code disables M ← WRONG: M still has AP2!
Apply this diff:
if previous_account && previous_account.id != @account.id && previous_account.family_id == @account.family_id - previous_account.disable! rescue nil + previous_account.reload + if previous_account.account_providers.none? + previous_account.disable! rescue nil + end end
143-152: Critical: Synchronous API call blocks HTTP request.The
balancesaction callsperform_syncdirectly at Line 146, blocking the request thread until the SimpleFin API responds. This is a reliability anti-pattern that can cause timeouts and poor UX, especially under rate limits or network delays.The
syncaction (lines 132-141) demonstrates the correct async pattern usingsync_later. Apply the same approach here.Apply this diff:
def balances - sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) - SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) + unless @simplefin_item.syncing? + sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true }) + SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id) + end respond_to do |format| format.html { redirect_back_or_to accounts_path } - format.json { render json: { ok: true, sync_id: sync.id } } + format.json { render json: { ok: true } } end endAlternatively, if a dedicated job exists for balances-only syncs, use that instead.
🧹 Nitpick comments (1)
app/controllers/simplefin_items_controller.rb (1)
397-500: Optional: Remove unused auto-linking code.The
compute_relink_candidatesmethod (and its helpernormalize_name) is never called in this controller or elsewhere in the codebase. According to PR discussion, the team chose manual-only linking via checkboxes instead of automatic heuristics.The actual linking flow uses
select_existing_account+link_existing_accountactions (lines 257-363), which don't invoke this code.Consider removing 100+ lines of dead code to reduce maintenance burden:
private - NAME_NORM_RE = /\s+/.freeze - - - def normalize_name(str) - s = str.to_s.downcase.strip - return s if s.empty? - s.gsub(NAME_NORM_RE, " ") - end - - def compute_relink_candidates - # ... entire method (lines 406-500) ... - end - def set_simplefin_item @simplefin_item = Current.family.simplefin_items.find(params[:id]) endIf you plan to use these heuristics in future UI features, consider moving them to a service object or model concern instead of a controller private method.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/controllers/settings/bank_sync_controller.rb(1 hunks)app/controllers/simplefin_items_controller.rb(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/controllers/settings/bank_sync_controller.rb
🧰 Additional context used
🧠 Learnings (7)
📓 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: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.
📚 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/controllers/simplefin_items_controller.rb
📚 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/simplefin_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/simplefin_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/simplefin_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/simplefin_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/simplefin_items_controller.rb
🧬 Code graph analysis (1)
app/controllers/simplefin_items_controller.rb (4)
app/models/account.rb (2)
include(1-227)destroy_later(154-157)app/models/simplefin_item.rb (3)
include(1-198)destroy_later(47-50)accounts(38-45)app/models/simplefin_item/syncer.rb (1)
perform_sync(8-48)app/controllers/concerns/simplefin_items/maps_helper.rb (1)
build_simplefin_maps_for(9-78)
🪛 ast-grep (0.40.0)
app/controllers/simplefin_items_controller.rb
[warning] 240-240: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 345-345: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
[warning] 357-357: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html
(hardcoded-secret-rsa-passphrase-ruby)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: ci / test
This commit addresses the remaining unresolved code review comments: 1. Fix down migration in drop_was_merged_from_transactions.rb - Add null: false, default: false constraints to match original column - Ensures proper rollback compatibility 2. Fix bare rescue in maps_helper.rb compute_duplicate_only_flag - Replace bare rescue with rescue StandardError => e - Add proper logging for debugging - Follows Ruby best practices by being explicit about exception handling These changes improve code quality and follow Rails/Ruby best practices.
Ports and expands #254 ideas to the new Providers Factory from #250, making SimpleFin more reliable, user-friendly, and secure. Addresses duplicate transactions (#110) and adds tools for common pain points like flaky syncs and account relinks. Builds on multi-provider support with guards to prevent "Other accounts" leaks.
Key Changes
Provider Framework Integration:
SimpleFin via Provider::SimplefinAdapter with centralized persistence in Account::ProviderImportAdapter.
Multi-provider ready: Auto-maintain AccountProvider links; guard against duplicates per type.
Sync Engine Enhancements:
Chunked first-sync (60-day with discovery) + 7-day buffer for regulars to respect Bridge limits.
Per-account error skipping with sync_stats; no full aborts.
Rate-limit detection (rate_limited_at timestamp) with UI feedback.
Balances-only mode: Quick refresh (updates balances/last_synced_at without transactions) via param; new controller action and dev-only card button (retainable in prod).
Optimized the balances import to run in the background for snappier UI
Transaction Dedup/Merging:
Matching: external_id + source → FITID → composite (±7d date, ±$0.01 amount, normalized names).
Pending→posted merges: Update existing entry, set transactions.was_merged = true, add note.
UI: Subtle "Merged" badge with tooltip.
UI Polish:
SimpleFin card badges: Unlinked/Skipped/Total; Rate-limited (time-ago tooltip).
Categorized error tooltips (auth/api/network/other) with samples.
"View errors" modal: Per-account lists + category counts.
Auto-relink on re-add (heuristics: last4/balance/name) to prevent orphans.
Manual relink modal: "Link existing accounts" button lists pairs; applies with dupe cleanup (skip/delete entries/holdings), data migration, and redundant account removal.
Rake Tasks & Utilities:
sure:simplefin:encrypt_access_urls: Idempotent backfill (dry-run/batch/limit, credentials-gated).
sure:backfill_was_merged: Heuristic for legacy pendings (dry-run/live).
Fixed private method crash; safe badge rendering.
Tests: 24+ green for adapters/import/UI; 100+ assertions covering chunking/skips/dedup/merges/relinks.
Impact
Smoother onboarding/syncs, fewer dupes from pendings/relinks.
Better handles Bridge quirks (quotas, errors) without overwhelming UI.
Future-proof: Aligns with #250's API layer TODO; extensible for extras like custom currencies.
Requesting reviews from @sokie and @jjmata. Fixes #110, #303 ; closes #254 (draft).
Summary by CodeRabbit
New Features
Bug Fixes
Note
Adds SimpleFin relink and balances-only sync, improves dedup/merge and pending→posted handling with richer UI/status and metadata, plus DB/index changes and rake utilities.
SimplefinItem::Importer#import_balances_onlyandSimplefinItem::Syncer(rate-limit detection, per-account error buckets, API request tracking).dedup_simplefin_accounts!andmerge_duplicate_provider_accounts!.apply_relinkto migrate entries/holdings and move provider links; auto-open viaauto_relink_controller.jsandopen_relink_for.SimplefinItem::Unlinkerunlinks provider links before destroy.Account::ProviderImportAdapter#import_transaction(external_id→FITID→composite; marktransactions.was_merged; persisttransaction.extra; notes, date heuristics by account type).find_or_create_by!withRecordNotUniquerescue).SimplefinItemsController: new actionsbalances,errors,relink,apply_relink; enhancedcreate/update/destroy/setup_accountsflows.simplefin_error_tooltip.transactions.was_mergedandtransactions.extra(GIN index).ON DELETE CASCADEFKs forentries,holdings,account_providers; schema updated.config/database.ymlusernamekey.sure:backfill_was_merged,sure:simplefin:fix_was_merged,sure:simplefin:backfill_extra, holdings backfill, unlink, encrypt access URLs, debug and cleanup utilities.Written by Cursor Bugbot for commit e84ea59. This will update automatically on new commits. Configure here.