Совсем недавно я начал проект на Ruby и подыскивал гем для авторизации. Но толком так и не нашел красивого и четкого гема, реализующего разделения по ролям, а может плохо искал. Теперь хочу рассказать о своем методе решения этой проблемы.
Задача.
Реализовать разделение по ролям с возможностью наследования малыми силами, не создавая кучу таблиц и лишних полей.
Реализация:
1. Для начала поговорим о БД.
Первым делом необходимо создать таблицу ролей. Раз уж нам необходимо использовать наследование ролей, то очевидно, что придется хранить древовидную структуру. Я использовал реляционную БД (Postgresql). В реляционных БД хранить деревья не очень удобно, но методов для такого хранения предостаточно. Я использовал «material path». Этот метод крайне прост и заключается в том, что в некотором поле (например path) мы храним путь данной строки таблицы относительно корня дерева из id элементов, которые являются предками для данного элемента. Например запись 0/12/23/97 означает, что текущий элемент c id 97 вложен в элемент 23, который вложен в 12. А 12 — является корневым. Ну да не буду подробно останавливаться на методах хранения древовидных структур.
Итак Ruby on Rails использует миграции для внесения изменений в БД, поэтому привожу пример миграции, которую я использовал для таблицы ролей:
class CreateRoles < ActiveRecord::Migration def up create_table :roles do |t| t.string :name, :unique => true t.string :path end execute <<SQL ALTER TABLE roles ADD CONSTRAINT uniq_role_name UNIQUE(name); INSERT INTO roles (name,path) VALUES ('registred',':registred'); INSERT INTO roles (name,path) VALUES ('admin',':registred:admin'); SQL end def down drop_table :roles end end
Сразу оговорюсь, что я использую в миграция SQL код для того, чтобы на уровне БД организовывать Foreign key. Предполагаю, что многие сочтут это неправильным. Не могу привести никаких доводов ни за ни против этого метода, а потому прошу не заострять на этом внимание.
Также приведу SQL для создания таблицы (для тех, кто не на руби или не использует миграции):
CREATE TABLE "public"."roles" ( "id" int4 DEFAULT nextval('roles_id_seq'::regclass) NOT NULL, "name" varchar(255), "path" varchar(255), CONSTRAINT "roles_pkey" PRIMARY KEY ("id"), CONSTRAINT "uniq_role_name" UNIQUE ("name") ) WITH (OIDS=FALSE) ; ALTER TABLE "public"."roles" OWNER TO "nalogovaya.ru";
Таким образом мы получаем таблицу с тремя полями:
- id — первичный ключ
- name — строка (название роли)
- path — путь до роли во мнимом дереве ролей.
И тут же вставляю 2 роли.
Раз у нас присутствует авторизация — значит есть некая таблица users в которой хранится информация о пользователе. В ней необходимо создать поле-ключ для указания роли пользователя.
Привожу пример миграции:
class AddRoleToUser < ActiveRecord::Migration def up execute <<SQL ALTER TABLE users ADD COLUMN role_id integer DEFAULT 1; ALTER TABLE users ADD CONSTRAINT "role_relation" FOREIGN KEY ("role_id") REFERENCES "public"."roles" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; SQL end def down execute <<SQL ALTER TABLE users DROP COLUMN role_id; SQL end end
Итак! Мы имеем таблицу ролей в БД и указатель роли в таблице пользователей. Работа с БД на этом завершена.
2. Исходный код
Для начала создадим модель ролей. Приведу исходный код файла role.rb а затем поясню:
#!/bin/env ruby # encoding: utf-8 class Role < ActiveRecord::Base extend ActiveModel::Callbacks has_many :users ROLES = Role.all def self.find_by_name(name) ROLES.select{|item| item.name==name}.first end def >= (value) value = checkRole(value) !self.path.index(value.path).nil? end def <= (value) value = checkRole(value) !self.>=(value) end def == (value) value = checkRole(value) self.name==value.name end protected def checkRole(value) value.instance_of?(String) ? Role.find_by_name(value) : value value end end
ROLES = Role.all — сомнительный ход для уменьшения количества запросов к БД. При старте рельс сразу подгружаются все роли, и к БД запросов больше не будет.
Метод checkRole — (назовите как вам больше понравится) служит исключительно для удобства, чтобы роли можно было сравнивать просто по названию, а не только по обьектам класса Role.
Остальные методы — просто функции сравнения ролей.
Теперь перейдем к пользователям. Для начала укажем связку:
belongs_to :role
Если вы используете Rails 4 и гем protected_attributes, то не забудьте еще добавить
attr_accessible :role
3. Использование.
Самая приятная часть. Если, конечно, первых 2 пункта — это то что Вам было нужно =)
Пример:
<% if current_user.role>='admin' #если роль пользователя "админ" или старше, что вряд ли, показываем ему что-то %> <%= link_to 'Удалить всю входящую почту!', requests_path, :method => :delete %> <% end %>
Спасибо за внимание, надеюсь кому-нибудь понадобится такая реализация ролей.
ссылка на оригинал статьи http://habrahabr.ru/post/188112/
Добавить комментарий