1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-18 20:59:39 +02:00

Handle case sensitive values when creating securities
Some checks are pending
Publish Docker image / ci (push) Waiting to run
Publish Docker image / Build docker image (push) Blocked by required conditions

This commit is contained in:
Zach Gollwitzer 2025-05-08 14:31:43 -04:00
parent 867318cbc1
commit d8e058d7c6
4 changed files with 49 additions and 8 deletions

View file

@ -106,8 +106,8 @@ class PlaidInvestmentSync
# Find any matching security
security = Security.find_or_create_by!(
ticker: plaid_security.ticker_symbol,
exchange_operating_mic: operating_mic
ticker: plaid_security.ticker_symbol&.upcase,
exchange_operating_mic: operating_mic&.upcase
)
[ security, plaid_security ]

View file

@ -1,7 +1,7 @@
class Security < ApplicationRecord
include Provided
before_save :upcase_ticker
before_validation :upcase_symbols
has_many :trades, dependent: :nullify, class_name: "Trade"
has_many :prices, dependent: :destroy
@ -29,8 +29,8 @@ class Security < ApplicationRecord
end
private
def upcase_ticker
def upcase_symbols
self.ticker = ticker.upcase
self.exchange_operating_mic = exchange_operating_mic.upcase if exchange_operating_mic.present?
end
end

View file

@ -90,7 +90,7 @@ class TradeImport < Import
return internal_security if internal_security.present?
# If security prices provider isn't properly configured or available, create with nil exchange_operating_mic
return Security.find_or_create_by!(ticker: ticker, exchange_operating_mic: nil) unless Security.provider.present?
return Security.find_or_create_by!(ticker: ticker&.upcase, exchange_operating_mic: nil) unless Security.provider.present?
# Cache provider responses so that when we're looping through rows and importing,
# we only hit our provider for the unique combinations of ticker / exchange_operating_mic
@ -104,9 +104,9 @@ class TradeImport < Import
).first
end
return Security.find_or_create_by!(ticker: ticker, exchange_operating_mic: nil) if provider_security.nil?
return Security.find_or_create_by!(ticker: ticker&.upcase, exchange_operating_mic: nil) if provider_security.nil?
Security.find_or_create_by!(ticker: provider_security[:ticker], exchange_operating_mic: provider_security[:exchange_operating_mic]) do |security|
Security.find_or_create_by!(ticker: provider_security[:ticker]&.upcase, exchange_operating_mic: provider_security[:exchange_operating_mic]&.upcase) do |security|
security.name = provider_security[:name]
security.country_code = provider_security[:country_code]
security.logo_url = provider_security[:logo_url]

View file

@ -0,0 +1,41 @@
require "test_helper"
class SecurityTest < ActiveSupport::TestCase
# Below has 3 example scenarios:
# 1. Original ticker
# 2. Duplicate ticker on a different exchange (different market price)
# 3. "Offline" version of the same ticker (for users not connected to a provider)
test "can have duplicate tickers if exchange is different" do
original = Security.create!(ticker: "TEST", exchange_operating_mic: "XNAS")
duplicate = Security.create!(ticker: "TEST", exchange_operating_mic: "CBOE")
offline = Security.create!(ticker: "TEST", exchange_operating_mic: nil)
assert original.valid?
assert duplicate.valid?
assert offline.valid?
end
test "cannot have duplicate tickers if exchange is the same" do
original = Security.create!(ticker: "TEST", exchange_operating_mic: "XNAS")
duplicate = Security.new(ticker: "TEST", exchange_operating_mic: "XNAS")
assert_not duplicate.valid?
assert_equal [ "has already been taken" ], duplicate.errors[:ticker]
end
test "cannot have duplicate tickers if exchange is nil" do
original = Security.create!(ticker: "TEST", exchange_operating_mic: nil)
duplicate = Security.new(ticker: "TEST", exchange_operating_mic: nil)
assert_not duplicate.valid?
assert_equal [ "has already been taken" ], duplicate.errors[:ticker]
end
test "casing is ignored when checking for duplicates" do
original = Security.create!(ticker: "TEST", exchange_operating_mic: "XNAS")
duplicate = Security.new(ticker: "tEst", exchange_operating_mic: "xNaS")
assert_not duplicate.valid?
assert_equal [ "has already been taken" ], duplicate.errors[:ticker]
end
end