Вчера, в заметке про полиморфные связи в комментариях был упомянут паттерн STI. Как выяснилось, не все знают что это такое, как работает и зачем нужно. Решил восполнить этот информационный пробел и вкратце рассказать об этом шаблоне проектирования и его реализации в Рельсе.
STI (Single Table Inheritance) — паттерн проектирования, который позволяет перенести объектно-ориентированное наследование на таблицу реляционной базы данных. В таблице БД должно присутствовать поле идентифицирующее название класса в иерархии. Зачастую, в том числе в RoR, поле называют type.
Таким образом, мы можем иметь одну таблицу и несколько типов объектов (моделей), которые будут в ней храниться. В случае с вышеупомянутой хабразаметкой — это одна таблица постов, которая хранит посты разных типов: ссылка, подкаст, статья, перевод и т.д.
Дабы не усложнять себе жизнь, в этой статье мы рассмотрим более простой пример: несколько типов пользователей с разными полномочиями и любой другой бизнес-логикой. Пусть это будут: администратор, менеджер и рядовой пользователь.
Приступим.
Допустим, в нашем приложении (например интернет-магазине) нам требуется реализовать иерархию пользователей с различными полномочиями и, возможно, дополнительной логикой. Пусть это будут: администратор, менеджер и рядовой пользователь. Вполне логично для каждого создать отдельную модель: Administrator, Manager, User. Учетные записи всех пользователей хранятся в одной таблице базы данных.
Нам требуется одна таблица, в целях максимального упрощения в ней будут два поля: username и password. Также, нам нужно как-то хранить тип пользователя, по умолчанию в Рельсе для этих целей используется поле под названием type.
В итоге получаем следующую структуру:
Разумеется, правильнее было бы написать миграцию, но это уже выходит за рамки данной статьи. Будем считать это заданием для самостоятельной работы :)
Создаем модель рядового пользователя:
Следует иметь ввиду: мы явно не указываем название таблицы для этой модели т.к. в данном случае название таблицы и модели соответствуют соглашениям, принятым в Рельсе.
Создаем модель менеджера, наследуемся от базового пользователя:
Создаем модель администратора, наследуемся от менеджера:
Для упрощения примера, мы не усложняем модели дополнительной логикой, которая могла бы нам понадобиться в реальном приложении. Если требуется вы всегда можете добавить специфический код для каждого типа пользователей в соответствующую модель.
Теперь в контроллере мы можем делать выборки из моделей привычным способом:
В данном случае мы получим всех пользователей, включая рядовых, менеджеров и администраторов.
Но если мы сделаем выборку из модели Administrator, то получим только администраторов:
В данной заметке мы пропустили многие аспекты, которые не имеют прямого отношения к основной теме от написания миграций до использования генераторов. Также упомянуты далеко не все возможности, которые предоставляет Рельсовый ActiveRecord при использовании STI. Интересующиеся могут изучить эти моменты самостоятельно.
Single Table Inheritance у Мартина Фаулера
Документация по ActiveRecord в Ruby on Rails
STI (Single Table Inheritance) — паттерн проектирования, который позволяет перенести объектно-ориентированное наследование на таблицу реляционной базы данных. В таблице БД должно присутствовать поле идентифицирующее название класса в иерархии. Зачастую, в том числе в RoR, поле называют type.
Таким образом, мы можем иметь одну таблицу и несколько типов объектов (моделей), которые будут в ней храниться. В случае с вышеупомянутой хабразаметкой — это одна таблица постов, которая хранит посты разных типов: ссылка, подкаст, статья, перевод и т.д.
Дабы не усложнять себе жизнь, в этой статье мы рассмотрим более простой пример: несколько типов пользователей с разными полномочиями и любой другой бизнес-логикой. Пусть это будут: администратор, менеджер и рядовой пользователь.
Приступим.
Определяемся с задачей
Допустим, в нашем приложении (например интернет-магазине) нам требуется реализовать иерархию пользователей с различными полномочиями и, возможно, дополнительной логикой. Пусть это будут: администратор, менеджер и рядовой пользователь. Вполне логично для каждого создать отдельную модель: Administrator, Manager, User. Учетные записи всех пользователей хранятся в одной таблице базы данных.
Создаем структуру БД
Нам требуется одна таблица, в целях максимального упрощения в ней будут два поля: username и password. Также, нам нужно как-то хранить тип пользователя, по умолчанию в Рельсе для этих целей используется поле под названием type.
В итоге получаем следующую структуру:
CREATE TABLE users (<br> id INT NOT NULL AUTO_INCREMENT,<br> username VARCHAR(20) NOT NULL UNIQUE,<br> password VARCHAR(32) NOT NULL,<br> type VARCHAR(40) NOT NULL,<br> PRIMARY KEY (id)<br>);
Разумеется, правильнее было бы написать миграцию, но это уже выходит за рамки данной статьи. Будем считать это заданием для самостоятельной работы :)
Создаем модели
Создаем модель рядового пользователя:
class User < ActiveRecord::Base<br>end
Следует иметь ввиду: мы явно не указываем название таблицы для этой модели т.к. в данном случае название таблицы и модели соответствуют соглашениям, принятым в Рельсе.
Создаем модель менеджера, наследуемся от базового пользователя:
class Manager < User<br>end
Создаем модель администратора, наследуемся от менеджера:
class Administrator < Manager<br>end
Для упрощения примера, мы не усложняем модели дополнительной логикой, которая могла бы нам понадобиться в реальном приложении. Если требуется вы всегда можете добавить специфический код для каждого типа пользователей в соответствующую модель.
Используем!
Теперь в контроллере мы можем делать выборки из моделей привычным способом:
users = User.find(:all)
В данном случае мы получим всех пользователей, включая рядовых, менеджеров и администраторов.
Но если мы сделаем выборку из модели Administrator, то получим только администраторов:
administrators = Administrator.find(:all)
Вместо заключения
В данной заметке мы пропустили многие аспекты, которые не имеют прямого отношения к основной теме от написания миграций до использования генераторов. Также упомянуты далеко не все возможности, которые предоставляет Рельсовый ActiveRecord при использовании STI. Интересующиеся могут изучить эти моменты самостоятельно.
Single Table Inheritance у Мартина Фаулера
Документация по ActiveRecord в Ruby on Rails