mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
Centralize sdk_object_to_hash logic in DataHelpers module and update all references for improved reusability and consistency. Add error handling for partial and failed SnapTrade account linking. (#741)
Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
This commit is contained in:
@@ -215,10 +215,23 @@ class SnaptradeItemsController < ApplicationController
|
||||
# Trigger sync to process the newly linked accounts
|
||||
# Always queue the sync - if one is running, this will run after it finishes
|
||||
@snaptrade_item.sync_later
|
||||
redirect_to accounts_path, notice: t(".success", count: linked_count, default: "Successfully linked #{linked_count} account(s).")
|
||||
|
||||
if errors.any?
|
||||
# Partial success - some linked, some failed
|
||||
redirect_to accounts_path, notice: t(".partial_success", linked: linked_count, failed: errors.size,
|
||||
default: "Linked #{linked_count} account(s). #{errors.size} failed to link.")
|
||||
else
|
||||
redirect_to accounts_path, notice: t(".success", count: linked_count, default: "Successfully linked #{linked_count} account(s).")
|
||||
end
|
||||
else
|
||||
redirect_to setup_accounts_snaptrade_item_path(@snaptrade_item),
|
||||
alert: t(".no_accounts", default: "No accounts were selected for linking.")
|
||||
if errors.any?
|
||||
# All failed
|
||||
redirect_to setup_accounts_snaptrade_item_path(@snaptrade_item),
|
||||
alert: t(".link_failed", default: "Failed to link accounts: %{errors}", errors: errors.first)
|
||||
else
|
||||
redirect_to setup_accounts_snaptrade_item_path(@snaptrade_item),
|
||||
alert: t(".no_accounts", default: "No accounts were selected for linking.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
# SnaptradeActivitiesFetchJob.perform_later(snaptrade_account, start_date: 5.years.ago.to_date)
|
||||
#
|
||||
class SnaptradeActivitiesFetchJob < ApplicationJob
|
||||
include SnaptradeAccount::DataHelpers
|
||||
|
||||
queue_as :default
|
||||
|
||||
# Prevent concurrent jobs for the same account - only one fetch at a time
|
||||
@@ -115,20 +117,6 @@ class SnaptradeActivitiesFetchJob < ApplicationJob
|
||||
activities.map { |a| sdk_object_to_hash(a) }
|
||||
end
|
||||
|
||||
def sdk_object_to_hash(obj)
|
||||
return obj if obj.is_a?(Hash)
|
||||
|
||||
if obj.respond_to?(:to_json)
|
||||
JSON.parse(obj.to_json)
|
||||
elsif obj.respond_to?(:to_h)
|
||||
obj.to_h
|
||||
else
|
||||
obj
|
||||
end
|
||||
rescue JSON::ParserError, TypeError
|
||||
obj.respond_to?(:to_h) ? obj.to_h : {}
|
||||
end
|
||||
|
||||
# Merge activities, deduplicating by ID
|
||||
# Fallback key includes symbol to distinguish activities with same date/type/amount
|
||||
def merge_activities(existing, new_activities)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class SnaptradeAccount < ApplicationRecord
|
||||
include CurrencyNormalizable
|
||||
include SnaptradeAccount::DataHelpers
|
||||
|
||||
belongs_to :snaptrade_item
|
||||
|
||||
@@ -60,7 +61,7 @@ class SnaptradeAccount < ApplicationRecord
|
||||
def upsert_from_snaptrade!(account_data)
|
||||
# Deep convert SDK objects to hashes - .to_h only does top level,
|
||||
# so we use JSON round-trip to get nested objects as hashes too
|
||||
data = deep_convert_to_hash(account_data)
|
||||
data = sdk_object_to_hash(account_data)
|
||||
data = data.with_indifferent_access
|
||||
|
||||
# Extract meta data
|
||||
@@ -118,7 +119,7 @@ class SnaptradeAccount < ApplicationRecord
|
||||
# and is set by upsert_from_snaptrade! from the balance.total field.
|
||||
def upsert_balances!(balances_data)
|
||||
# Deep convert each balance entry to ensure we have hashes
|
||||
data = Array(balances_data).map { |b| deep_convert_to_hash(b).with_indifferent_access }
|
||||
data = Array(balances_data).map { |b| sdk_object_to_hash(b).with_indifferent_access }
|
||||
|
||||
Rails.logger.info "SnaptradeAccount##{id} upsert_balances! - raw data: #{data.inspect}"
|
||||
|
||||
@@ -161,23 +162,6 @@ class SnaptradeAccount < ApplicationRecord
|
||||
)
|
||||
end
|
||||
|
||||
# Deep convert SDK objects to nested hashes
|
||||
# The SnapTrade SDK returns objects that only convert the top level with .to_h
|
||||
# We use JSON round-trip to ensure all nested objects become hashes
|
||||
def deep_convert_to_hash(obj)
|
||||
return obj if obj.is_a?(Hash)
|
||||
|
||||
if obj.respond_to?(:to_json)
|
||||
JSON.parse(obj.to_json)
|
||||
elsif obj.respond_to?(:to_h)
|
||||
obj.to_h
|
||||
else
|
||||
obj
|
||||
end
|
||||
rescue JSON::ParserError, TypeError
|
||||
obj.respond_to?(:to_h) ? obj.to_h : {}
|
||||
end
|
||||
|
||||
def log_invalid_currency(currency_value)
|
||||
Rails.logger.warn("Invalid currency code '#{currency_value}' for SnapTrade account #{id}, defaulting to USD")
|
||||
end
|
||||
|
||||
@@ -3,6 +3,23 @@ module SnaptradeAccount::DataHelpers
|
||||
|
||||
private
|
||||
|
||||
# Convert SnapTrade SDK objects to hashes
|
||||
# SDK objects don't have proper to_h but do have to_json
|
||||
# Uses JSON round-trip to ensure all nested objects become hashes
|
||||
def sdk_object_to_hash(obj)
|
||||
return obj if obj.is_a?(Hash)
|
||||
|
||||
if obj.respond_to?(:to_json)
|
||||
JSON.parse(obj.to_json)
|
||||
elsif obj.respond_to?(:to_h)
|
||||
obj.to_h
|
||||
else
|
||||
obj
|
||||
end
|
||||
rescue JSON::ParserError, TypeError
|
||||
obj.respond_to?(:to_h) ? obj.to_h : {}
|
||||
end
|
||||
|
||||
def parse_decimal(value)
|
||||
return nil if value.nil?
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class SnaptradeItem::Importer
|
||||
include SyncStats::Collector
|
||||
include SnaptradeAccount::DataHelpers
|
||||
|
||||
attr_reader :snaptrade_item, :snaptrade_provider, :sync
|
||||
|
||||
@@ -53,22 +54,6 @@ class SnaptradeItem::Importer
|
||||
|
||||
private
|
||||
|
||||
# Convert SnapTrade SDK objects to hashes
|
||||
# SDK objects don't have to_h but do have to_json
|
||||
def sdk_object_to_hash(obj)
|
||||
return obj if obj.is_a?(Hash)
|
||||
|
||||
if obj.respond_to?(:to_json)
|
||||
JSON.parse(obj.to_json)
|
||||
elsif obj.respond_to?(:to_h)
|
||||
obj.to_h
|
||||
else
|
||||
obj
|
||||
end
|
||||
rescue JSON::ParserError, TypeError
|
||||
obj.respond_to?(:to_h) ? obj.to_h : {}
|
||||
end
|
||||
|
||||
# Extract activities array from API response
|
||||
# get_account_activities returns a paginated object with .data accessor
|
||||
# This handles both paginated responses and plain arrays
|
||||
|
||||
@@ -16,6 +16,8 @@ en:
|
||||
success:
|
||||
one: "Successfully linked %{count} account."
|
||||
other: "Successfully linked %{count} accounts."
|
||||
partial_success: "Linked %{linked} account(s). %{failed} failed to link."
|
||||
link_failed: "Failed to link accounts: %{errors}"
|
||||
no_accounts: "No accounts were selected for linking."
|
||||
preload_accounts:
|
||||
not_configured: "SnapTrade is not configured."
|
||||
|
||||
Reference in New Issue
Block a user