Недавно, при поптировании одного приложения с Java на RoR столкнулся с проблемой «отображения иерархии классов на реляционную модель». Изветное дело в Java или .NET я бы использовал Hibernate/NHibernate и Single Table Inheritance или STI (в данном случае это наиболее подходящий из 3 представленных способов). На мое счастье, оказывается ActiveRcord в RoR, также поддерживает STI. Итак, приступим.
В качестве предметной области я взял модель безопастности приложения (т.е. ее часть). У нас есть базовая сущность Principal(name, description), и три конкретные сущности наследника: User(login, passwordHash, lastLoginDate, expirationDate, accountDisabled), Role, Group. Для данного примера я решил не усложнять модель связями между User, Role и Group.
Дла реализации STI нам потребуется в талбицу principals добавить зарезервированное поле type и все поля из классов: Principal, User, Role и Group.
Привожу скрипт миграции для таблицы principals.
Сущность Principal
Предметная область
В качестве предметной области я взял модель безопастности приложения (т.е. ее часть). У нас есть базовая сущность Principal(name, description), и три конкретные сущности наследника: User(login, passwordHash, lastLoginDate, expirationDate, accountDisabled), Role, Group. Для данного примера я решил не усложнять модель связями между User, Role и Group.
Реализация
Дла реализации STI нам потребуется в талбицу principals добавить зарезервированное поле type и все поля из классов: Principal, User, Role и Group.
Скрипт миграции
Привожу скрипт миграции для таблицы principals.
class CreatePrincipals < ActiveRecord::Migration
def self.up
create_table :principals do |t|
t.column :name, :string, :limit => 64
t.column :description, :string
t.column :account_disabled, :boolean
t.column :last_login_date, :date
t.column :expiration_date, :date
t.column :type, :string, :limit => 48
end
end
def self.down
drop_table :principals
end
end
Модель
Сущность Principal
class Principal < ActiveRecord::Base
end
Сущность User
class User < Principal
end
Сущность Role
class Role < Principal
end
Сущность Group
class Group < Principal
end
Как видим в объявлениях классов модели нет ничего особенного. Обо всем остальном позаботится ActiveRecord.
Примеры работы с моделью
Тепрь можно создавать наши объекты, сохранять в базу данных и выполнять запросы. В качестве примеров приведу тесты модели (они простые, так что проше не бить ногами :) ). Примеры только для Role и Group, с User-ом аналогично.
Фикстуры для principals
users_role:
id: 100
name: users
description: system users
type: Role
testers_group:
id: 101
name: testers
description: testers group
type: Group
Тест для сущности Role
require 'test_helper'
class RoleTest < ActiveSupport::TestCase
fixtures :principals
# Save Role in database.
test "role_has_type_eq_role_after_save" do
role = Role.new(:name=>"admins", :description=>"system administrators")
role.save
assert role.id
assert_equal "Role", role.type
end
test "users_role_exists_in_db" do
role = Role.find_by_id(100);
assert_not_nil role
assert_equal 100, role.id
assert_equal "users", role.name
assert_equal "Role", role.type
end
end
Тест для сущности Group
require 'test_helper'
class GroupTest < ActiveSupport::TestCase
fixtures :principals
# Save Role in database.
test "group_has_type_eq_group_after_save" do
group = Group.new(:name=>"developers", :description=>"developers group")
group.save
assert group.id
assert_equal "Group", group.type
end
test "users_group_exists_in_db" do
group = Group.find_by_id(101);
assert_not_nil group
assert_equal 101, group.id
assert_equal "testers", group.name
assert_equal "Group", group.type
end
end
Как видим все достаточно прозрачно.
Ложка дегтя
Как уже можно было догадаться есть и обратная сторона медали. Котроль соответсвия набора значащих полей конкретной сущности лежит целиком на совести программиста. Для среды ActiveRecord значение имеет лишь колонка type. Так, что я вполне могу создать сущность Role с данными User-а. А это не очень хорошо отразится на отношениях ко мне моих коллег :) . По этому в Rails очень важно покрытие кода функциональными тестами.
Жду отзывов, исправлений и предложений. ;)