Множественное наследование таблиц против одиночного наследования таблиц в Ruby on Rails

В последние несколько часов я боролся за размышления о том, к какому маршруту я должен идти. У меня есть модель уведомления. До сих пор я использовал столбец notification_type для управления типами, но я думаю, что будет лучше создавать отдельные классы для типов уведомлений, поскольку они ведут себя по-разному.

Прямо сейчас есть три способа уведомления: отправить SMS, Twitter, электронную почту

Каждое уведомление будет иметь:

id
subject
message
valediction
sent_people_count
deliver_by
geotarget
event_id
list_id
processed_at
deleted_at
created_at
updated_at

Кажется, что STI - хороший кандидат? Конечно, у Твиттера/СМС не будет темы, а у Twitter не будет sent_people_count. Я бы сказал, что в этом случае они разделяют большинство своих полей. Однако что, если я добавлю поле "reply_to" для twitter и boolean для DM?

Моя точка зрения заключается в том, что сейчас STI имеет смысл, но это тот случай, когда я, возможно, буду пинать себя в будущем, не только начиная с MTI?

Чтобы еще больше усложнить ситуацию, я хочу, чтобы модель информационного бюллетеня была своего рода уведомлением, но разница в том, что она не будет использовать event_id или deliver_by.

Я мог видеть все подклассы уведомления, используя примерно 2/3 полей базового класса уведомлений. Является ли ИППП без проблем, или я должен использовать MTI?

Ответ 1

Учитывая ограниченную информацию, я бы сказал, придерживаюсь STI.

Ключевой вопрос: есть ли места в вашем приложении, где вы хотите рассматривать все типы уведомлений вместе? Если это так, то это сильный признак того, что вы хотите придерживаться ИППП.

Ответ 2

Вы рассматривали смешанный подход к модели?

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

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

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

class Notification < ActiveRecord::Base
  # common methods/validations/associations
  ...

  def self.relate_to_details
    class_eval <<-EOF
      has_one :details, :class_name => "#{self.name}Detail"
      accepts_nested_attributes_for :details
      default_scope -> { includes(:details) }
    EOF
  end
end

class SMS < Notification
  relate_to_details

  # sms specific methods
  ...
end

class Twitter < Notification
  relate_to_details

  # twitter specific methods
  ...
end

class Email < Notification

  # email specific methods
  ...
end

class SMSDetail < ActiveRecord::Base
  belongs_to :SMS, :class_name => "SMS"           

  # sms specific validations
  ...
end

class TwiterDetail < ActiveRecord::Base
  belongs_to :twitter

  # twitter specific validations
  ...
end

Каждая из таблиц подробностей будет содержать идентификатор уведомления и только столбцы, которые формируют коммуникационные потребности, которые не включены в таблицу уведомлений. Хотя это будет означать дополнительный вызов метода для получения информации, специфичной для СМИ.

Это здорово знать, но считаете ли вы его необходимым?

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

Что касается преимуществ:

Эта схема обеспечивает согласованность ИППП. С таблицами, которые не нужно воссоздавать. Связанная таблица содержит десятки столбцов, которые пусты в 75% ваших строк. Вы также получаете легкое создание подкласса. Если вам нужно создать таблицу соответствия, если ваш новый тип не полностью покрыт базовыми полями уведомлений. Он также сохраняет итерирование всех уведомлений простым.

Из MTI вы получаете экономию на хранении и простоту настройки в соответствии с требованиями класса, не требуя переопределения одинаковых столбцов для каждого нового типа уведомлений. Только уникальные.

Однако эта схема также переносит главный недостаток ИППП. Таблица будет заменять 4. Который может начать вызывать замедление, когда оно становится огромным.

Короткий ответ: никакой такой подход не требуется. Я считаю это самым суровым способом эффективного решения проблемы. В самом коротком периоде STI - это способ сделать это. В очень долгосрочной перспективе MTI - это путь, но мы говорим о том, что вы попали в миллионы уведомлений. Этот подход - это отличная промежуточная площадка, которая легко расширяется.

Подробный драгоценный камень

Я построил драгоценный камень над вашим решением: https://github.com/czaks/detailed. Используя его, вы можете упростить класс Notification:

class Notification < ActiveRecord::Base
  include Detailed
end

Остальные идут по-предыдущему.

В качестве дополнительного бонуса теперь вы можете напрямую обращаться к атрибутам подкласса (читать, писать и связывать): notification.phone_number, не прибегая к: notification.details.phone_number. Вы также можете написать весь код в основных классах и подклассах, оставив модель детали пустым. Вы также сможете выполнять меньше запросов (в приведенном выше примере 4 вместо N + 1) на больших наборах данных, используя Notification.all_with_details вместо обычного Notification.all.

Имейте в виду, что в настоящее время этот жемчуг не проверен очень хорошо, хотя он работает в моей утилите.

Ответ 3

Я знаю, что это старо, но после того, как я придумал решение, я вижу потенциальные ответы, которые могут использовать его везде! Недавно я разработал многообещающий проект для реализации множественного наследования таблиц и наследования классов в Rails. Я провел несколько дней, подвергая его быстрой разработке, исправлениям, комментариям и документации и повторно выпустил их как наследование класса CITIER и наследование наследования для Rails.

Я думаю, это должно позволить вам делать то, что вам нужно, просто конструируя модели, на которых Twitter, электронная почта и SMS наследуются от уведомления. Затем миграция для уведомлений включает только общие атрибуты, а те, что относятся к трем подтипам, включают их уникальные атрибуты.

Или даже определите функцию в корневом классе Notification и перегрузите ее в подклассах, чтобы вернуть что-то другое.

Рассмотрите возможность взглянуть на это: https://github.com/PeterHamilton/citier

Я нахожу это настолько полезным! Я бы (кстати) приветствовал любую помощь для сообщества в вопросах и тестировании, очистке кода и т.д.! Я знаю, это то, что многие люди оценят.

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

Ответ 4

has_one :details, :class_name => "#{self.class.name}Detail"

не работает. self.class.name в контексте определения класса - это "Класс", поэтому: class_name всегда "ClassDetail"

Итак, это должно быть:

has_one :details, :class_name => "#{self.name}Detail"

Но очень хорошая идея!