mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
* Include newer providers in automatic family sync
Coinbase, CoinStats, Mercury, and SnapTrade all implement Syncable
and have Syncer classes but were not listed in child_syncables,
meaning their data only refreshed on manual sync button clicks.
* refactor(syncer): Open/Closed principle for provider sync
- Adding new providers requires modifying child_syncables (violates O/C)
- plaid_items missing .active scope (bug: syncs deleted items)
- snaptrade_items can exist without user registration → fails on sync
- Scattered knowledge about 'ready to sync' logic
1. **Registry pattern**: SYNCABLE_ITEM_ASSOCIATIONS constant lists all
provider associations that participate in family sync
2. **Encapsulated sync-readiness**: Each item model defines its own
`syncable` scope that knows when it's ready for auto-sync:
- Most providers: `syncable = active` (not scheduled for deletion)
- SnapTrade: `syncable = active + user_registered` (has API creds)
3. **Single loop**: child_syncables iterates the registry, calling
`.syncable` on each association
- Adding a provider = add to registry + define syncable scope
- Each model owns its 'ready to sync' business logic
- Fixes plaid_items bug (now uses .active via .syncable)
- Fixes snaptrade auto-sync failures (filters unregistered items)
- Easy to extend with new conditions per provider
- family/syncer.rb: Registry + dynamic collection
- *_item.rb (7 files): Add `scope :syncable, -> { active }`
- snaptrade_item.rb: Add syncable with user_registered filter
* Fix rubocop bracket spacing in SnaptradeItem syncable scope
59 lines
1.6 KiB
Ruby
59 lines
1.6 KiB
Ruby
class Family::Syncer
|
|
attr_reader :family
|
|
|
|
# Registry of item association names that participate in family sync.
|
|
# Each model must:
|
|
# 1. Include Syncable
|
|
# 2. Define a `syncable` scope (items ready for auto-sync)
|
|
#
|
|
# To add a new provider: add its association name here.
|
|
# The model handles its own "ready to sync" logic via the syncable scope.
|
|
SYNCABLE_ITEM_ASSOCIATIONS = %i[
|
|
plaid_items
|
|
simplefin_items
|
|
lunchflow_items
|
|
enable_banking_items
|
|
indexa_capital_items
|
|
coinbase_items
|
|
coinstats_items
|
|
mercury_items
|
|
snaptrade_items
|
|
].freeze
|
|
|
|
def initialize(family)
|
|
@family = family
|
|
end
|
|
|
|
def perform_sync(sync)
|
|
# We don't rely on this value to guard the app, but keep it eventually consistent
|
|
family.sync_trial_status!
|
|
|
|
# Schedule child syncs
|
|
child_syncables.each do |syncable|
|
|
syncable.sync_later(parent_sync: sync, window_start_date: sync.window_start_date, window_end_date: sync.window_end_date)
|
|
end
|
|
end
|
|
|
|
def perform_post_sync
|
|
family.auto_match_transfers!
|
|
|
|
Rails.logger.info("Applying rules for family #{family.id}")
|
|
family.rules.where(active: true).each do |rule|
|
|
rule.apply_later
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
# Collects all syncable items from registered providers + manual accounts.
|
|
# Each provider model defines its own `syncable` scope that encapsulates
|
|
# the "ready to sync" business logic (active, configured, etc.)
|
|
def child_syncables
|
|
provider_items = SYNCABLE_ITEM_ASSOCIATIONS.flat_map do |association|
|
|
family.public_send(association).syncable
|
|
end
|
|
|
|
provider_items + family.accounts.manual
|
|
end
|
|
end
|