В моем приложении 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
в данном случае)?
Вместо жесткого кодирования внешних ключей (для любого типа ассоциации) вы можете использовать способность 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
Однако я должен настоятельно отговорить вас от этого.
Перебирать ассоциации такого класса и автоматически обновлять кучу строк кажется излишне безрассудным и опасным. Использование белого списка было бы намного лучше.
Еще одна большая проблема — полное отсутствие обработки ошибок: если какое-либо из обновлений завершится неудачей, ваши данные останутся в очень запутанном состоянии.