From 45935db5f3b601422b5b556fbf94eac779a44759 Mon Sep 17 00:00:00 2001 From: Josh Pigford Date: Fri, 25 Oct 2024 13:09:02 -0500 Subject: [PATCH] Remove dependency on stock exchange table (#1368) --- app/models/provider/marketstack.rb | 3 +- app/models/security.rb | 3 +- app/models/security/importer.rb | 33 ++++++++++--------- config/initializers/good_job.rb | 2 +- .../20241025174650_add_mic_to_securities.rb | 10 ++++++ db/schema.rb | 8 ++--- 6 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 db/migrate/20241025174650_add_mic_to_securities.rb diff --git a/app/models/provider/marketstack.rb b/app/models/provider/marketstack.rb index 5a1b8de9..10e2aa5d 100644 --- a/app/models/provider/marketstack.rb +++ b/app/models/provider/marketstack.rb @@ -40,7 +40,8 @@ class Provider::Marketstack { name: ticker["name"], symbol: ticker["symbol"], - exchange: exchange_mic || ticker.dig("stock_exchange", "mic"), + exchange_mic: exchange_mic || ticker.dig("stock_exchange", "mic"), + exchange_acronym: ticker.dig("stock_exchange", "acronym"), country_code: ticker.dig("stock_exchange", "country_code") } end diff --git a/app/models/security.rb b/app/models/security.rb index 784d0b68..196835ad 100644 --- a/app/models/security.rb +++ b/app/models/security.rb @@ -3,7 +3,8 @@ class Security < ApplicationRecord has_many :trades, dependent: :nullify, class_name: "Account::Trade" - validates :ticker, presence: true, uniqueness: { case_sensitive: false } + validates :ticker, presence: true + validates :ticker, uniqueness: { scope: :exchange_mic, case_sensitive: false } def current_price @current_price ||= Security::Price.find_price(ticker:, date: Date.current) diff --git a/app/models/security/importer.rb b/app/models/security/importer.rb index 4970170b..ed515067 100644 --- a/app/models/security/importer.rb +++ b/app/models/security/importer.rb @@ -7,21 +7,24 @@ class Security::Importer def import securities = @provider.fetch_tickers(exchange_mic: @stock_exchange)&.tickers - stock_exchanges = StockExchange.where(mic: securities.map { |s| s[:exchange] }).index_by(&:mic) - existing_securities = Security.where(ticker: securities.map { |s| s[:symbol] }, stock_exchange_id: stock_exchanges.values.map(&:id)).pluck(:ticker, :stock_exchange_id).to_set + # Deduplicate securities based on ticker and exchange_mic + securities_to_create = securities + .map do |security| + { + name: security[:name], + ticker: security[:symbol], + country_code: security[:country_code], + exchange_mic: security[:exchange_mic], + exchange_acronym: security[:exchange_acronym] + } + end + .compact + .uniq { |security| [ security[:ticker], security[:exchange_mic] ] } - securities_to_create = securities.map do |security| - stock_exchange_id = stock_exchanges[security[:exchange]]&.id - next if existing_securities.include?([ security[:symbol], stock_exchange_id ]) - - { - name: security[:name], - ticker: security[:symbol], - stock_exchange_id: stock_exchange_id, - country_code: security[:country_code] - } - end.compact - - Security.insert_all(securities_to_create) unless securities_to_create.empty? + Security.upsert_all( + securities_to_create, + unique_by: [ :ticker, :exchange_mic ], + update_only: [ :name, :country_code, :exchange_acronym ] + ) unless securities_to_create.empty? end end diff --git a/config/initializers/good_job.rb b/config/initializers/good_job.rb index c3794ad6..5d8aae25 100644 --- a/config/initializers/good_job.rb +++ b/config/initializers/good_job.rb @@ -14,7 +14,7 @@ Rails.application.configure do # Auth for jobs admin dashboard ActiveSupport.on_load(:good_job_application_controller) do before_action do - raise ActionController::RoutingError.new("Not Found") unless current_user&.super_admin? + raise ActionController::RoutingError.new("Not Found") unless current_user&.super_admin? || Rails.env.development? end def current_user diff --git a/db/migrate/20241025174650_add_mic_to_securities.rb b/db/migrate/20241025174650_add_mic_to_securities.rb new file mode 100644 index 00000000..1d907141 --- /dev/null +++ b/db/migrate/20241025174650_add_mic_to_securities.rb @@ -0,0 +1,10 @@ +class AddMicToSecurities < ActiveRecord::Migration[7.2] + def change + add_column :securities, :exchange_mic, :string + add_column :securities, :exchange_acronym, :string + + remove_column :securities, :stock_exchange_id, :uuid + + add_index :securities, [ :ticker, :exchange_mic ], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index f008ff6f..7445d127 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_10_24_142537) do +ActiveRecord::Schema[7.2].define(version: 2024_10_25_174650) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -479,9 +479,10 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_24_142537) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "country_code" - t.uuid "stock_exchange_id" + t.string "exchange_mic" + t.string "exchange_acronym" t.index ["country_code"], name: "index_securities_on_country_code" - t.index ["stock_exchange_id"], name: "index_securities_on_stock_exchange_id" + t.index ["ticker", "exchange_mic"], name: "index_securities_on_ticker_and_exchange_mic", unique: true end create_table "security_prices", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -603,7 +604,6 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_24_142537) do add_foreign_key "imports", "families" add_foreign_key "institutions", "families" add_foreign_key "merchants", "families" - add_foreign_key "securities", "stock_exchanges" add_foreign_key "sessions", "impersonation_sessions", column: "active_impersonator_session_id" add_foreign_key "sessions", "users" add_foreign_key "taggings", "tags"