Polymorphic Associations and Devise in Ruby on Rails
Hello.
Once upon a time I wrote an article about polymorphic associations in Ruby on Rails and, I remember, some were indignant: why, they say, write about Rails 2, if the new version is coming.
Recently, I had to deal with polymorphic associations in Rails 3, or rather come up with how to organize two types of users on a site: a customer and an executor. This article will focus on polymorphic associations and gems Devise (for authentication) and CanCan (for authorization).
The task was as follows: there are two types of users, registration and login must be made from one form, during registration the user indicates who he wants to be: the customer or the contractor.
Accordingly, users had different roles in the project, they could do different things.
Thus, according to the standard Devise documentation, I added it to the project. In the migration that Devise created, I added the following fields:
In addition to the name, here you can make any other common properties of all types of users: country, address, etc. I had only a name in this field.
The second line, according to the documentation (http://apidock.com/rails/ActiveRecord/ConnectionAdapters/Table/references), will create two fields for us: character_id and character_type: in the first, the id of the “character” will be stored, and in the second - the name of the class where to look for this id.
After that, run rake db: migrate, and then add the user.rb model.
Here we added new fields to attr_accessible, added validations, registered associations and added accepts_nested_attributes_for.
Changes to attr_accessible are needed so that you can save data in batches, rather than one at a time.
Validations are needed to make sure that the records we need are added (and their format also suits us).
The Kakbe polymorphic association hints to us that the user now has a character field that refers to either the customer or the artist. In fact, this is an addition to our User model, which can be either of one type or of another.
assepts_nested_attributes_for are needed so that you can make nested forms (a form for the user, in which a subform for character is embedded (that is, either for the customer or for the performer).
The two methods at the end are just for convenience, so that you can easily and quickly determine in controllers and views what type of user we have.
After that we will create 2 models: Customer and Executive. In migrations, you can register any data unique to each type of user (passwords, attendance, etc.), in both models, to communicate with the user, you need to register this:
I also added user_observer (rails g observer user), where I wrote this code:
After that, I connected this observer in config / application.rb:
Remember, I said that when registering, the user must choose who he wants to be? I have it radio, but you can easily make select. Depending on the type selected, they should return either “Customer” or “Executive”. And, accordingly, before creating each user, we create either a customer or an executor for him. The code above can be written as:
but it’s somehow long and uncomfortable, agree.
With Devise'om over, now you can go to CanCan. It also needs to be installed according to the documentation, and then different rules should be set for both types of users. For example, something like this:
Let's say something like that. Thus, both customers and performers have access to the same controllers / resources, but at the same time, each person is allowed different actions.
Now in the view it is enough to check whether the current user can do one or another action, and life will seem like Paradise.
Now the “Join” link will be visible only to those who are allowed in the rules above. You need to fix the controller a little more, but everything is there according to the standard documentation.
Similarly, you can hide and show various blocks on the site, and indeed anything.
Now a few words about associations: most associations are now registered in the models of the customer and the executor, and not in the user model. Thus, the greatest flexibility is achieved with further development.
The result of the article is as follows: there are situations when polymorphic associations are really needed and greatly simplify life. You should not fanatically add them to each hole, but you should know about them and be able to apply them.
Related links:
github.com/plataformatec/devise
github.com/ryanb/cancan
habrahabr.ru/blogs/ror/79431
Once upon a time I wrote an article about polymorphic associations in Ruby on Rails and, I remember, some were indignant: why, they say, write about Rails 2, if the new version is coming.
Recently, I had to deal with polymorphic associations in Rails 3, or rather come up with how to organize two types of users on a site: a customer and an executor. This article will focus on polymorphic associations and gems Devise (for authentication) and CanCan (for authorization).
The task was as follows: there are two types of users, registration and login must be made from one form, during registration the user indicates who he wants to be: the customer or the contractor.
Accordingly, users had different roles in the project, they could do different things.
Thus, according to the standard Devise documentation, I added it to the project. In the migration that Devise created, I added the following fields:
- t.string :name, :null => false
- t.references :character, :polymorphic => true
* This source code was highlighted with Source Code Highlighter.
In addition to the name, here you can make any other common properties of all types of users: country, address, etc. I had only a name in this field.
The second line, according to the documentation (http://apidock.com/rails/ActiveRecord/ConnectionAdapters/Table/references), will create two fields for us: character_id and character_type: in the first, the id of the “character” will be stored, and in the second - the name of the class where to look for this id.
After that, run rake db: migrate, and then add the user.rb model.
- 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.
Here we added new fields to attr_accessible, added validations, registered associations and added accepts_nested_attributes_for.
Changes to attr_accessible are needed so that you can save data in batches, rather than one at a time.
Validations are needed to make sure that the records we need are added (and their format also suits us).
The Kakbe polymorphic association hints to us that the user now has a character field that refers to either the customer or the artist. In fact, this is an addition to our User model, which can be either of one type or of another.
assepts_nested_attributes_for are needed so that you can make nested forms (a form for the user, in which a subform for character is embedded (that is, either for the customer or for the performer).
The two methods at the end are just for convenience, so that you can easily and quickly determine in controllers and views what type of user we have.
After that we will create 2 models: Customer and Executive. In migrations, you can register any data unique to each type of user (passwords, attendance, etc.), in both models, to communicate with the user, you need to register this:
- has_one :user, :as => :character, :dependent => :destroy
* This source code was highlighted with Source Code Highlighter.
I also added user_observer (rails g observer user), where I wrote this code:
- 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.
After that, I connected this observer in config / application.rb:
- config.active_record.observers = :user_observer
* This source code was highlighted with Source Code Highlighter.
Remember, I said that when registering, the user must choose who he wants to be? I have it radio, but you can easily make select. Depending on the type selected, they should return either “Customer” or “Executive”. And, accordingly, before creating each user, we create either a customer or an executor for him. The code above can be written as:
- if user.character_type == “Customer”
- user.character = Customer.create!
- else
- user.character = Executive.create!
- end
* This source code was highlighted with Source Code Highlighter.
but it’s somehow long and uncomfortable, agree.
With Devise'om over, now you can go to CanCan. It also needs to be installed according to the documentation, and then different rules should be set for both types of users. For example, something like this:
- 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.
Let's say something like that. Thus, both customers and performers have access to the same controllers / resources, but at the same time, each person is allowed different actions.
Now in the view it is enough to check whether the current user can do one or another action, and life will seem like Paradise.
- if can? :join, @document
- = link_to “Join”, [:join, @document]
* This source code was highlighted with Source Code Highlighter.
Now the “Join” link will be visible only to those who are allowed in the rules above. You need to fix the controller a little more, but everything is there according to the standard documentation.
Similarly, you can hide and show various blocks on the site, and indeed anything.
Now a few words about associations: most associations are now registered in the models of the customer and the executor, and not in the user model. Thus, the greatest flexibility is achieved with further development.
The result of the article is as follows: there are situations when polymorphic associations are really needed and greatly simplify life. You should not fanatically add them to each hole, but you should know about them and be able to apply them.
Related links:
github.com/plataformatec/devise
github.com/ryanb/cancan
habrahabr.ru/blogs/ror/79431