Простенькая реализация ролей на примере Ruby

от автора

Всем привет. Я долгое время программировал на PHP и использовал Zend 1. Работал над крупным проектом платежной системы. Система авторизации подразумевала пользователей, их авторизацию и разделение по ролям. Разделение по ролям было довольно обширным и ветвистым. Вообще в большинстве проектов если уж требуется авторизация, то, наверняка, потребуется хотя бы минимальное разделение по ролям.
Совсем недавно я начал проект на 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"; 

Таким образом мы получаем таблицу с тремя полями:

  1. id — первичный ключ
  2. name — строка (название роли)
  3. 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/


Комментарии

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

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