📜 ⬆️ ⬇️

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 a new version is coming.

Recently I had to deal with polymorphic associations in Rails 3, or rather, to figure out how to organize two types of users on the site: the customer and the performer. This article will focus on polymorphic associations and Devise gems (for authentication) and CanCan (for authorization).

The task was the following: there are two types of users, registration and login should be made from one form, when registering, the user indicates whether he wants to be a customer or a contractor.
Accordingly, users had different roles in the project, could do different things.
')
Thus, according to 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 .
  1. t. string :name, : null => false t.references :character, :polymorphic => true * This source code was highlighted with Source Code Highlighter .
  2. t. string :name, : null => false t.references :character, :polymorphic => true * This source code was highlighted with Source Code Highlighter .
t. string :name, : null => false t.references :character, :polymorphic => true * This source code was highlighted with Source Code Highlighter .

In addition to the name, any other common properties of all types of users can be brought here: country, address, and so on. I had only the name of 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 one the “character” id will be stored, and in the second - the class name where to look for this id.

After that, run rake db: migrate and then add the user.rb model.
  1. class User <ActiveRecord :: Base
  2. # Include default devise modules. Others available are:
  3. #: token_authenticatable,: encryptable,: confirmable,: lockable,: timeoutable,: trackable and: omniauthable
  4. devise: database_authenticatable,: registerable,: recoverable,: rememberable,: validatable
  5. # Setup accessible (or protected ) attributes for your model
  6. attr_accessible: name,: email,: password,: remember_me,: character_id,: character_type,: character,: character_attributes
  7. # Validations
  8. validates_presence_of: name,: character_type
  9. validates_inclusion_of: character_type,: in =>% w (Customer Executive)
  10. # Associations
  11. belongs_to: character,: polymorphic => true ,: dependent =>: destroy
  12. # Nested attributes
  13. accepts_nested_attributes_for: character
  14. # Authorization helper methods
  15. def customer?
  16. character_type == "Customer"
  17. end
  18. def executive?
  19. character_type == "Executive"
  20. end
  21. 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 in attr_accessible are needed so that you can save data in "batches", rather than one by one.
Validations are needed to make sure that the records we need are added (and their format also suits us).
The polymorphic association kakbe hints to us that the user now has a character field that refers to either the customer or the performer. In essence, this is an addition to our User model, which can be either of one type or another.
assepts_nested_attributes_for are needed in order to be able to make nested forms (a form for the user in which the form for the character is embedded (that is, either for the customer or for the performer).
Two methods at the end - 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 for each type of user (passwords, attendances, etc.), in both models you need to register this in order to communicate with the user:
  1. 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:
  1. class UserObserver <ActiveRecord :: Observer
  2. def before_create (user)
  3. build_character_for user
  4. end
  5. private
  6. def build_character_for (user)
  7. user.character = user.character_type.classify.constantize.create!
  8. end
  9. end
* This source code was highlighted with Source Code Highlighter .

After that, in the config / application.rb connected this observer:
  1. 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 this radio, but you can easily make and select. Depending on the type chosen, they must return either “Customer” or “Executive”. And, accordingly, before creating each user, we create for him either a customer or a contractor. The code above can be written in the form:

  1. if user.character_type == “Customer”
  2. user.character = Customer.create!
  3. else
  4. user.character = Executive.create!
  5. end
* This source code was highlighted with Source Code Highlighter .

but this is somehow long and inconvenient, agree.

With Devise'om done, you can now go to CanCan. It also needs to be installed according to the documentation, and then prescribe different rules for both types of users. For example, something like this:
  1. class ability
  2. include CanCan :: Ability
  3. def initialize (user)
  4. user || = User. new
  5. if user.admin? # Admin account
  6. can: manage,: all
  7. else
  8. if user.customer? # Customer account
  9. # RESTful
  10. can: read, Document
  11. can: create, Document
  12. can: update, Document,: customer_id => user.character.id
  13. can: read, Comment
  14. # Collections
  15. can: personal, Document
  16. elsif user.executive? # Executive account
  17. # RESTful
  18. can: read, Document
  19. can: read, Comment
  20. can: create, Comment
  21. can [: update,: destroy], Comment,: executive_id => user.character.id
  22. # Members
  23. can: join, Document
  24. can: leave, Document
  25. # Collections
  26. can: drafts, Comment
  27. can: archive, Comment
  28. end
  29. end
  30. end
  31. end
* This source code was highlighted with Source Code Highlighter .

Suppose something like that. Thus, both customers and implementers have access to the same controllers / resources, but each is allowed different actions.

Now in the view it is enough to check whether the current user can do this or that action, and life will seem like Paradise.
  1. if can? : join @document
  2. = link_to “Join”, [: join, @document]
* This source code was highlighted with Source Code Highlighter .

Now the link "Join" see only those who are allowed in the rules above. It is necessary to correct the controller a little more, but everything is according to standard documentation.

Similarly, you can hide and show the various blocks on the site, and indeed anything.

Now a few words about associations: most associations are now registered in customer and artist models, 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 shouldn't fanatically add them to every hole, but you should be aware of them and be able to apply.

Related Links:
github.com/plataformatec/devise
github.com/ryanb/cancan
habrahabr.ru/blogs/ror/79431

Source: https://habr.com/ru/post/118035/


All Articles