В проекте, над которым я работаю, у нас есть модель Address. В нем есть все обычные вещи, которые вы могли бы ожидать:

  • Несколько address_line полей
  • Поле country
  • Поле postcode
  • Некоторые проверки, чтобы гарантировать, что данные, хранящиеся в этих полях, разумны.
class Address < ApplicationRecord
  belongs_to :addressable, polymorphic: true
  validates :address_line_1, :postcode, presence: true
  validates :address_line_1, length: { minimum: 5 }
  validates :address_line_1, :address_line_2, :address_line_3, :city, length: { maximum: 255 }
  validates :postcode, ...
  ...
end

Чего еще можно хотеть?

Address относится к другим моделям в нашей области через полиморфную ассоциацию. Для реализации этого у нас есть проблема PhysicalAddressable:

module PhysicalAddressable
  extend ActiveSupport::Concern
  included do
    has_one :address, as: :addressable, dependent: :destroy, inverse_of: :addressable
    delegate :address_line_1,  to: :address
    delegate :address_line_2,  to: :address
    delegate :address_line_3,  to: :address
    delegate :city,            to: :address
    delegate :postcode,        to: :address
    def self.search_by_postcode(postcode)
      ...
    end
    ...
  end
end

Включение этой проблемы в модель позволяет ей иметь адресную запись и взаимодействовать с ней.

Хорошо! Все довольно стандартные вещи Railsy, ​​как вы говорите 👍

На этой неделе я внес некоторые изменения в PhysicalAddressable. Эти изменения были совместимы со всеми адресуемыми моделями, кроме одной: DistributionCentre.

В оставшейся части этого поста я расскажу о своем мыслительном процессе для решения этой проблемы и, в конечном итоге, удаления ассоциации адресов с DistributionCentre .

Контекст

Чтобы помочь лучше смоделировать общую структуру, которая существует между различными типами Order, мы недавно представили новую модель для нашего домена: EpisodeOfCare. Там, где раньше у нас было несколько адресных моделей заказов, теперь каждая из этих моделей будет принадлежать эпизоду ухода, а эпизод ухода будет обрабатывать связь с адресом.

Что мне в конечном итоге нужно сделать, так это отключить Address от каждой из адресуемых моделей и вместо этого связать Address с EpisodeOfCare.

Один шаг к этой цели требует изменения PhysicalAddressable таким образом, чтобы каждая из адресуемых моделей передавала вызовы #address на соответствующий эпизод ухода.

delegate :address, to: :episode_of_care

Это сработало, как и планировалось, для всех адресуемых моделей, кроме одной…

Проблема с DistributionCentre

В отличие от других адресных моделей, DistributionCentre не относится к эпизоду ухода. И что же мне делать?

Параметры:

  1. Предоставьте средства для прямой связи DistributionCentre с Address (в обход PhysicalAddressable)
  2. Удалите связь между DistributionCentre и Address, вместо этого сохраняя значения адресов непосредственно в центре распределения.

Второй вариант кажется проще; посмотрим, насколько это жизнеспособно.

Нужна ли DistributionCentre собственная адресная запись?

В контексте DistributionCentre нам нужно:

  1. A (big A) Address — полноценный адресный объект.
  2. Адрес (маленькое а) — значение адреса.

Чтобы ответить на этот вопрос, я хочу знать следующее:

  • Сколько у нас логики, зависящей от адреса распределительного центра?
  • Как часто мы делаем запросы к базе данных для DistributionCentre, которые зависят от адреса?

\Приглушенные звуки скрипа\

Что ж, получается, что ответ на оба вопроса: никто/никогда/мы не делаем.

Фактически:

  • Большинство распределительных центров даже не имеют адреса.
  • Адреса распределительных центров не взаимодействуют с пользователем (без CRUDing!).

Таким образом, мы не получаем особой выгоды от использования ассоциации записи адреса для DistributionCentre.

  • Проверки не особенно полезны, так как мы только создаем адреса центров распространения в начальном задании и никогда их не обновляем.
  • Мы никогда не запрашиваем — и даже не заботимся о содержании этих записей на уровне приложения. Таким образом, хранение их в отдельной таблице также не добавляет большой ценности.

В контексте распределительных центров: адрес является необязательным и статическим элементом информации. Он будет время от времени считываться, никогда не запрашиваться и никогда не изменяться.

Зная это, а также поскольку существующая ассоциация теперь мешает необходимому рефакторингу, у нас есть вполне убедительный аргумент в пользу удаления связи между Address и DistributionCentre.

Отключение Address и DistributionCentre

Вот краткое объяснение того, как я это сделал:

Шаг 1.

Мне нужно добавить несколько адресных полей в таблицу центра распределения. Rails предоставляет удобный генератор миграции, который может сделать это в одну строку.

rails generate migration AddAddressFieldsToDistributionCentres address_line_1:string address_line_2:string address_line_3:string city:string postcode:string country:string

Шаг 2.

Далее мне нужно заполнить новые поля адреса центра распределения, скопировав данные из старых адресных записей. Это было сделано с помощью задачи после развертывания:

Address.where(addressable_type: 'DistributionCentre').find_each do |address|
  distribution_centre = address.addressable
  address_values = address.slice(*ADDRESS_FIELDS)
  distribution_centre.update!(address_values)
end
ADDRESS_FIELDS = %i[
  address_line_1
  address_line_2
  address_line_3
  city
  postcode
  country
].freeze

Я также обновил наш файл seed, чтобы отразить новую схему базы данных.

Шаг 3.

Все записи центров распределения теперь содержат необходимые адресные данные. Затем я проверил, что публичный интерфейс DistributionCentre не сломается после удаления ассоциации адресов (я провел тесты). Это показало, что нам нужен метод для предоставления адресной строки, разделенной запятыми.

class DistributionCentre < ApplicationRecord
  ...
  def address_string
    [
      address_line_1,
      address_line_2,
      address_line_3,
      city,
      postcode,
      country
    ].compact.reject(&:empty?).join(', ')
  end
  ...
end

Шаг 4.

Наконец, я избавился от ассоциации, удалив проблему PhysicalAddressable из DistributionCentre. Старые записи были удалены с помощью другой задачи после развертывания:

Address.where(addressable_type: 'DistributionCentre').destroy_all

Дело сделано.

Заключительные мысли

Мне приходит в голову, что я мог бы заменить адрес всего одним строковым полем в центре распределения. Сохранение полей отдельно упрощает запрос к ним, если это когда-либо понадобится… но это не то, что нам сейчас нужно делать. Ну, это не имеет особого значения в данный момент.

Наконец, мне кажется интересным, что у большинства распределительных центров нет адреса — это может указывать на то, что в нашем моделировании предметной области в этой области есть возможности для улучшения. В настоящее время это не вызывает каких-либо очевидных проблем, но это может быть чем-то, что следует учитывать в будущем.