Multiple/Class Table Inheritance в Rails

от автора

Наверняка многие сталкиваются с проблемой, когда есть несколько моделей, скажем, Client, Manager и User – у которых ряд полей – к примеру, name, email, position – одинаковые. При этом каждая из моделей обладает также уникальными полями и методами. В данном случае (рассуждая абстрактно) логично было бы общие поля с соответствующими валидациями вынести в отдельную таблицу people (модель Person), оставив в Client, Manager и User только специфику.

Ряд примеров можно продолжать: Product – Fridge, Phone, Toaster; Vehicle – Car, Truck, Motorcycle и так далее. Проблема довольно общая (хотя до сих пор как-то не приходилось с ней сталкиваться), но я был уверен в том, что в Rails давно есть готовое решение. Результаты поисков меня немного удивили.

Варианты

Single Table Inheritance (STI)

Описано в Rails Edge Guides и пока что отсутствует в стабильной версии. Суть в том, что записи для Client, Manager и User мы помещаем в одну таблицу people, используя специальное поле type для того, чтобы было понятно, «кто есть кто». Вот пример; он условный, общие поля помечены звёздочкой, частные – заглавными буквами названий моделей:

type    | name*          | email*  | position (M)    | company (C)           | hobby (U) ----------------------------------------------------------------------------------------- manager | Slartibartfast | a@b.com | Planet designer | NULL                  | NULL client  | Ford           | c@d.com |                 | Megadodo Publications | NULL user    | Arthur         | e@f.com | NULL            | NULL                  | Travelling 

Однако, в этом случае Manager приобретает несвойственное ему свойство hobby от User. Также, поле hobby менеджеров всегда будет пустовать. User, Client и Manager в данном случае являются подклассами Person, не имеющие своих собственных таблиц, и каждое уникальное свойство нужно объявлять в родительской таблице/модели.

В принципе, на это можно было бы закрыть глаза, но что, если Manager требует создания 42 собственных полей, не имеющих никакого отношения к Client и User? В этом случае было бы логичнее перенести специфичные поля в отдельные таблицы clients, users и managers, оставив в people только общие поля, а также type и id для построения нужных связей. Такая схема, как подсказывает Google, называется Multiple Table Inheritance, но, к большому сожалению, Rails о ней пока ничего не знает, и, как показывает беглый поиск по форуму разработчиков, в обозримом будущем не собирается.

Nested attributes + Delegation

Проблему можно решить и так:

class Manager < ActiveRecord::Base   belongs_to :person    # общее   accepts_nested_attributes_for :person   delegate :name, :email, to: :person    # частное   validates_presence_of :position end  Company.find(42).managers.create(position: 'Paranoid android', person_attributes: {   name: 'Marvin', email: 'whats_the@point_to.be' }) 

В принципе вариант, но в данном случае придётся всё время выделять общие атрибуты в отдельный хэш при создании или модификации записи, а также пользоваться хелпером fields_for при выводе форм, а также делать дополнительные приседания в контроллере и модели, о чём подробно написано в тех же Rails Guides.

Хочется же «бесшовного» слияния между обоими моделями и полной изоляции частностей реализации общих полей Manager, User и Client с точки зрения других классов приложения.

Свой огород

По понятным причинам, городить его совсем не хочется, хотя, если поискать по сочетанию «rails multiple table inheritance» или «rails class table inheritance», то найдётся множество вариантов собственноручной реализации MTI.

Gems

Логично было с самого начала предположить, что всё уже сделано до нас, и вот что мне удалось найти. Не очень подробное изучение репозиториев на Github показывает, что живёт и развивается из них только active_record-acts_as. Не буду дублировать Readme, в нём достаточно наглядно описано, как пользоваться гемом. Беглый взгляд на issues показывает, что проект ещё в начальной фазе, но IMO вполне применим при должном покрытии тестами.

Сталкивались ли вы с похожей проблемой? Известно ли вам о других вариантах решения? Буду рад услышать ваше мнение в комментариях.

ссылка на оригинал статьи http://habrahabr.ru/post/260621/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *