Как автоматически получить имена столбцов внешнего ключа с полиморфными ассоциациями?

В моем приложении Ruby on Rails 7 у меня есть большая Account модель с различными has_many ассоциациями (большинство из них здесь опущено для краткости):

class Account < ApplicationRecord

  has_one   :address,         :dependent => :destroy, :as => :addressable # Polymorphic!
  has_many  :articles,        :dependent => :destroy
  has_many  :bank_accounts,   :dependent => :destroy, :as => :bankable # Polymorphic!
  has_many  :clients,         :dependent => :destroy
  has_many  :invoices,        :dependent => :destroy
  has_many  :languages,       :dependent => :destroy
  has_many  :payments,        :dependent => :destroy
  has_many  :projects,        :dependent => :destroy
  has_many  :reminders,       :dependent => :destroy
  has_many  :tasks,           :dependent => :destroy
  has_many  :users,           :dependent => :destroy

end

Users может создать гостевую учетную запись, а затем переключить ее на зарегистрированную учетную запись.

Я создал объект Service для достижения этой задачи:

class Accounts::Move < ApplicationService

  def initialize(account)
    @account = account
    @guest_account = @account.guest_account
  end

  def call
    if @guest_account
      update_dependencies
      @guest_account.delete
      reset_guest_account_id
    end
  end

private

  def update_dependencies
    dependencies.each do |dependency|
      dependency.constantize.where(:account_id => @guest_account.id).update_all(:account_id => @account.id) # Not working with polymorphic associations!
    end
  end

  def dependencies
    Account.reflect_on_all_associations.map(&:class_name)
  end

  def reset_guest_account_id
    @account.update_column(:guest_account_id, nil)
  end

end

Это хорошо работает для всех ассоциаций, имеющих столбец account_id, однако не работает для полиморфных ассоциаций, таких как address и bank_accounts.

Как я могу переключать их без необходимости жестко запрограммировать имена столбцов внешних ключей (например, addressable_id и bankable_id в данном случае)?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вместо жесткого кодирования внешних ключей (для любого типа ассоциации) вы можете использовать способность ActiveRecords отражать ассоциации:

# Do not use the scope resolution operator for defining namespaces - it leads to autoloading bugs
# and surprising constant lookups
module Accounts
  class Move < ApplicationService
    def initialize(account)
      @account = account
      @guest_account = @account.guest_account
    end
  
    def call
      # Why is this even needed? 
      # You should move this filtering up to where you enqueue the service
      if @guest_account
        update_dependencies
        reset_guest_account_id # Null the reference first
        @guest_account.delete
      end
    end
  
    private
  
    def update_dependencies
      # Instead of iterating accross an array of strings it's
      # ActiveRecord::Reflection::AssociationReflection instances
      dependencies.each do |reflection|
        scope = {
          reflection.foreign_key => @guest_account.id,
          reflection.type => reflection.type ? 'Account' : nil
        }.compact
        reflection.klass.where(scope)
                  .update_all(reflection.foreign_key => @account.id)
      end
    end
  
    def dependencies
      Account.reflect_on_all_associations(:has_many)
    end
  
    def reset_guest_account_id
      @account.update_column(:guest_account_id, nil)
    end
  end
end

Однако я должен настоятельно отговорить вас от этого.

Перебирать ассоциации такого класса и автоматически обновлять кучу строк кажется излишне безрассудным и опасным. Использование белого списка было бы намного лучше.

Еще одна большая проблема — полное отсутствие обработки ошибок: если какое-либо из обновлений завершится неудачей, ваши данные останутся в очень запутанном состоянии.

Другие вопросы по теме