Всем привет.
Когда-то давно я писал статью про полиморфные ассоциации в Ruby on Rails и, помнится, некоторые негодовали: зачем, мол, писать про Rails 2, если на подходе новая версия.
Недавно мне пришлось столкнуться с полиморфными ассоциациями в Rails 3, а точнее придумать, как организовать на сайте два типа пользователей: заказчик и исполнитель. В этой статье речь пойдет о полиморфных ассоциациях и гемах Devise (для аутентификации) и CanCan (для авторизации).
Задача стояла следующая: есть два типа пользователей, регистрация и логин должны производиться из одной формы, при регистрации пользователь указывает, кем он хочет быть: заказчиком или исполнителем.
Соответственно, пользователи имели разные роли в проекте, могли делать разные вещи.
Таким образом, согласно стандартной документации Devise, я добавил его в проект. В миграцию, которую создал Devise, я добавил следующие поля:
Кроме имени, сюда можно вынести любые другие общие свойства всех типов пользователей: страна, адрес и тд. У меня этим полем было только имя.
Вторая строка, согласно документации (http://apidock.com/rails/ActiveRecord/ConnectionAdapters/Table/references), создаст для нас два поля: character_id и character_type: в первом будут храниться id «персонажа», а во втором — название класса, где искать этот id.
После этого выполним rake db:migrate, а затем дополним модель user.rb.
Здесь мы добавили новые поля в attr_accessible, добавили валидации, прописали ассоциации и добавили accepts_nested_attributes_for.
Изменения в attr_accessible нужны, чтобы можно было сохранять данные «пачками», а не по одной.
Валидации нужны, чтобы быть уверенными, что нужные нам записи добавлены (и их формат нас тоже устраивает).
Полиморфная ассоциация какбе намекает нам, что теперь у пользователя есть поле character, которое ссылается либо на заказчика, либо на исполнителя. По сути, это дополнение нашей модели User, которое может быть либо одного типа, либо другого.
assepts_nested_attributes_for нужны для того, чтобы можно было делать вложенные формы (форма для юзера, в которую вложена подформа для character’a (то есть либо для заказчика, либо для исполнителя).
Два метода в конце — просто для удобства, чтобы можно было легко и быстро определять в контроллерах и представлениям, какого типа у нас пользователь.
После этого создадим 2 модели: Customer и Executive. В миграциях можно прописать любые уникальные для каждого типа пользователя данные (пароли, явки и тд), в обоих моделях для связи с пользователем нужно прописать это:
Еще я добавил user_observer (rails g observer user), где написал такой код:
После этого в config/application.rb подключил этот обзервер:
Помните, я говорил, что при регистрации пользователь должен выбрать, кем он хочет быть? У меня это radio, но вы легко можете сделать и select. В зависимости от выбранного типа, они должны возвращать либо “Customer”, либо “Executive”. И, соответственно, перед созданием каждого пользователя, мы создаем для него либо заказчика, либо исполнителя. Код выше можно записать в виде:
но это как-то долго и неудобно, согласитесь.
С Devise’ом покончено, теперь можно переходить к CanCan. Его тоже нужно установить согласно документации, а потом прописать разные правила для обоих типов пользователей. Например, как-то так:
Допустим, как-то так. Таким образом, и у заказчиков, и у исполнителей есть доступ к одним и тем же контроллерам/ресурсам, но при этом каждому позволяются разные действия.
Теперь в представлении достаточно проверить, может ли текущий пользователь делать то или иное действия, и жизнь покажется Раем.
Теперь ссылку “Join” увидят только те, кому это разрешено в правилах выше. Нужно еще немного исправить контроллер, но там все согласно стандартной документации.
Подобным образом можно скрывать и показывать различные блоки на сайте, да и вообще что угодно.
Теперь еще пару слов об ассоциациях: большинство ассоциаций прописываются теперь в моделях заказчика и исполнителя, а не в модели пользователя. Таким образом достигается наибольшая гибкость при дальнейшей разработке.
Итог статьи таков: есть ситуации, когда полиморфные ассоциации действительно нужны и сильно упрощают жизнь. Не стоит фанатично добавлять их в каждую дырку, но стоит знать о них и уметь применить.
Ссылки по теме:
github.com/plataformatec/devise
github.com/ryanb/cancan
habrahabr.ru/blogs/ror/79431
Когда-то давно я писал статью про полиморфные ассоциации в Ruby on Rails и, помнится, некоторые негодовали: зачем, мол, писать про Rails 2, если на подходе новая версия.
Недавно мне пришлось столкнуться с полиморфными ассоциациями в Rails 3, а точнее придумать, как организовать на сайте два типа пользователей: заказчик и исполнитель. В этой статье речь пойдет о полиморфных ассоциациях и гемах Devise (для аутентификации) и CanCan (для авторизации).
Задача стояла следующая: есть два типа пользователей, регистрация и логин должны производиться из одной формы, при регистрации пользователь указывает, кем он хочет быть: заказчиком или исполнителем.
Соответственно, пользователи имели разные роли в проекте, могли делать разные вещи.
Таким образом, согласно стандартной документации Devise, я добавил его в проект. В миграцию, которую создал Devise, я добавил следующие поля:
- t.string :name, :null => false
- t.references :character, :polymorphic => true
* This source code was highlighted with Source Code Highlighter.
Кроме имени, сюда можно вынести любые другие общие свойства всех типов пользователей: страна, адрес и тд. У меня этим полем было только имя.
Вторая строка, согласно документации (http://apidock.com/rails/ActiveRecord/ConnectionAdapters/Table/references), создаст для нас два поля: character_id и character_type: в первом будут храниться id «персонажа», а во втором — название класса, где искать этот id.
После этого выполним rake db:migrate, а затем дополним модель user.rb.
- class User < ActiveRecord::Base
- # Include default devise modules. Others available are:
- # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
- devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable
-
- # Setup accessible (or protected) attributes for your model
- attr_accessible :name, :email, :password, :remember_me, :character_id, :character_type, :character, :character_attributes
-
- # Validations
- validates_presence_of :name, :character_type
- validates_inclusion_of :character_type, :in => %w(Customer Executive)
-
- # Associations
- belongs_to :character, :polymorphic => true, :dependent => :destroy
-
- # Nested attributes
- accepts_nested_attributes_for :character
-
- # Authorization helper methods
- def customer?
- character_type == "Customer"
- end
-
- def executive?
- character_type == "Executive"
- end
- end
* This source code was highlighted with Source Code Highlighter.
Здесь мы добавили новые поля в attr_accessible, добавили валидации, прописали ассоциации и добавили accepts_nested_attributes_for.
Изменения в attr_accessible нужны, чтобы можно было сохранять данные «пачками», а не по одной.
Валидации нужны, чтобы быть уверенными, что нужные нам записи добавлены (и их формат нас тоже устраивает).
Полиморфная ассоциация какбе намекает нам, что теперь у пользователя есть поле character, которое ссылается либо на заказчика, либо на исполнителя. По сути, это дополнение нашей модели User, которое может быть либо одного типа, либо другого.
assepts_nested_attributes_for нужны для того, чтобы можно было делать вложенные формы (форма для юзера, в которую вложена подформа для character’a (то есть либо для заказчика, либо для исполнителя).
Два метода в конце — просто для удобства, чтобы можно было легко и быстро определять в контроллерах и представлениям, какого типа у нас пользователь.
После этого создадим 2 модели: Customer и Executive. В миграциях можно прописать любые уникальные для каждого типа пользователя данные (пароли, явки и тд), в обоих моделях для связи с пользователем нужно прописать это:
- has_one :user, :as => :character, :dependent => :destroy
* This source code was highlighted with Source Code Highlighter.
Еще я добавил user_observer (rails g observer user), где написал такой код:
- class UserObserver < ActiveRecord::Observer
- def before_create(user)
- build_character_for user
- end
-
- private
- def build_character_for(user)
- user.character = user.character_type.classify.constantize.create!
- end
- end
* This source code was highlighted with Source Code Highlighter.
После этого в config/application.rb подключил этот обзервер:
- config.active_record.observers = :user_observer
* This source code was highlighted with Source Code Highlighter.
Помните, я говорил, что при регистрации пользователь должен выбрать, кем он хочет быть? У меня это radio, но вы легко можете сделать и select. В зависимости от выбранного типа, они должны возвращать либо “Customer”, либо “Executive”. И, соответственно, перед созданием каждого пользователя, мы создаем для него либо заказчика, либо исполнителя. Код выше можно записать в виде:
- if user.character_type == “Customer”
- user.character = Customer.create!
- else
- user.character = Executive.create!
- end
* This source code was highlighted with Source Code Highlighter.
но это как-то долго и неудобно, согласитесь.
С Devise’ом покончено, теперь можно переходить к CanCan. Его тоже нужно установить согласно документации, а потом прописать разные правила для обоих типов пользователей. Например, как-то так:
- class Ability
- include CanCan::Ability
-
- def initialize(user)
- user ||= User.new
-
- if user.admin? # Admin account
- can :manage, :all
- else
- if user.customer? # Customer account
- # RESTful
- can :read, Document
- can :create, Document
- can :update, Document, :customer_id => user.character.id
- can :read, Comment
-
- # Collections
- can :personal, Document
-
- elsif user.executive? # Executive account
- # RESTful
- can :read, Document
- can :read, Comment
- can :create, Comment
- can [:update, :destroy], Comment, :executive_id => user.character.id
-
- # Members
- can :join, Document
- can :leave, Document
-
- # Collections
- can :drafts, Comment
- can :archive, Comment
- end
- end
- end
- end
* This source code was highlighted with Source Code Highlighter.
Допустим, как-то так. Таким образом, и у заказчиков, и у исполнителей есть доступ к одним и тем же контроллерам/ресурсам, но при этом каждому позволяются разные действия.
Теперь в представлении достаточно проверить, может ли текущий пользователь делать то или иное действия, и жизнь покажется Раем.
- if can? :join, @document
- = link_to “Join”, [:join, @document]
* This source code was highlighted with Source Code Highlighter.
Теперь ссылку “Join” увидят только те, кому это разрешено в правилах выше. Нужно еще немного исправить контроллер, но там все согласно стандартной документации.
Подобным образом можно скрывать и показывать различные блоки на сайте, да и вообще что угодно.
Теперь еще пару слов об ассоциациях: большинство ассоциаций прописываются теперь в моделях заказчика и исполнителя, а не в модели пользователя. Таким образом достигается наибольшая гибкость при дальнейшей разработке.
Итог статьи таков: есть ситуации, когда полиморфные ассоциации действительно нужны и сильно упрощают жизнь. Не стоит фанатично добавлять их в каждую дырку, но стоит знать о них и уметь применить.
Ссылки по теме:
github.com/plataformatec/devise
github.com/ryanb/cancan
habrahabr.ru/blogs/ror/79431