Наследование нескольких таблиц против наследования отдельных таблиц в Ruby on Rails - PullRequest
11 голосов
/ 28 октября 2009

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

В настоящее время существует 3 способа отправки уведомлений: 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 / SMS не будет темы, а у Twitter не будет send_people_count, valediction. Я бы сказал, что в этом случае они разделяют большинство своих полей. Однако что, если я добавлю поле «reply_to» для твиттера и логическое значение для DM?

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

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

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

Ответы [ 4 ]

20 голосов
/ 28 октября 2009

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

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

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

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

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

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

Это приятно знать, но вы думаете, что это необходимо?

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

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

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

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

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

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

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

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

class Notification < ActiveRecord::Base
  include Detailed
end

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

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

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

2 голосов
/ 29 апреля 2011

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

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

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

Подумайте над тем, чтобы посмотреть: https://github.com/PeterHamilton/citier

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

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

2 голосов
/ 28 октября 2009

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

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

1 голос
/ 13 ноября 2009
has_one :details, :class_name => "#{self.class.name}Detail"

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

Так должно быть:

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

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

...