adding user profiles in devise registrations controller - ruby-on-rails

I have a Rails app with three kinds of users, professionals, students and regular citizens. I therefore made the User model polymorphic with three different kinds of profiles.
User.rb
belongs_to :profile, polymorphic: true
Professional.rb
has_one :user, as: :profile, dependent: :destroy
Student.rb
has_one :user, as: :profile, dependent: :destroy
Citizen.rb
has_one :user, as: :profile, dependent: :destroy
I want to use Devise as central sign up and created the devise for the User model
rails generate devise User
and then I created a Registrations controller that inherited from Devise Registrations controller, and in the after_sign_up_path_for method, I assigned the user to whichever of the profiles the user selected on the signup form.
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(user)
if user.professional === true
user.profile = ProfessionalProfile.create!
user.save!
elsif
....
end
end
Right now, this works, in that, by overriding the def after_sign_in_path_for(resource) in the Application controller, I can redirect users to whatever profile they created
def after_sign_in_path_for(resource)
if current_user.profile_type === 'ProfessionalProfile'
professional_profile_path(current_user)
elsif
....
end
However, even though this works, I have very little experience with Rails (in terms of making my own application; i've followed a few tutorials) and devise, so I'm wondering, before I continue on developing the app, if I'm going to run into problems either with devise or anything else by having created profiles that way. Is there a better way to do this?
I guess as one possible alternative I was wondering if I should try to override Devise's create user action, so that it creates the relevant Profile at the same time.

You can add columns to the user table by using the code:
rails generate migration add_professional_to_users professional:boolean
and similarly
rails generate migration add_student_to_users student:boolean
and also
rails generate migration add_citizen_to_users citizen:boolean
this is a better method according to me as a similar method is described by devise to create an admin, see it here :devise,option1
Similarly you may add these roles. Just an option but one that I feel is better.

Related

Devise current_user vs user_id passed in params, nested resources update action

I am using Devise for authentication. I have two models where a user has one profile and profile belong to user:
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
end
class Profile < ActiveRecord::Base
belongs_to :user
end
I am using nested resources e.g.
resources :users do
resource :profile
end
To create a new user profile I use the prefix new_user_profile_path(current_user) that routes to prifile#new etc
To update a user profile I do the following
# e.g. users/123/profile
current_user.profile.update(profile_params)
This doesn't feel right because I am not using the user_id => 123 in profile params.
Should I be finding the user profile by user_id instead e.g.
#profile = Profile.find_by(user_id: params[:user_id])
#profile.update(profile_params)
Additionally, user's cannot edit other users' profile.
Thanks for the feedback.
current_user.profile.update(profile_params) is an acceptable way of updating the profile for the current user.
This also helps secure the profile from being edited by another user. If you base the user ID on params passed in from the query string, that is insecure and would allow any logged in user to be able to update another users profile.
For example, using restful routes anyone with access could post to /users/profiles/:id even if it wasn't their own ID.
current_user is an instance of the User model, and already contains the user_id attribute.

Two distinct types of users want to view same routes but expecting different returns

I have two distinct types of users that want to use the same RESTful routes. I have a polymorphic association between users and profiles, and the two types of profiles are PatientProfile and DoctorProfile. What I'm trying to do is to come up with a clean way for both user types to be able to use the same controller actions.
class User
belongs_to :profile, polymorphic: true
class PatientProfile
has_one :user, as: :profile
class DoctorProfile
has_one :user, as: :profile
I have a Review model.
# reviews_controller.rb
def index
# for patients this would be all reviews they wrote
# for doctors it would be all reviews about them
current_user.reviews
end
I've taken care to run everything off of the user instead of the profile because I thought it would be cleaner. However now I'm thinking the current_user method should really returning the profile instead of just the user. All associations should then be made in the respective profile model.
My question: Am I thinking about this the correct way or am I missing something? I looked into CanCanCan and Pundit but both seem to be for allowing specific actions whereas I'm trying to return distinct information based on who is requesting it.

Rails Automatically create 1:1 dependency

I have a Rails 4 app that uses Devise.
I'm trying to let the User model alone so that I don't cross paths with Devise, so I created a Profile model for all my settings/views the user will have.
The Profile has
belongs_to :user
The User has
has_one :profile
Where do I put the logic that the app needs to know to create a profile upon new user creation, and delete it upon user deletion?
You can use Active Record Callbacks to create a profile. And you can use dependent: :destroy to make sure the profile will get destroyed when the user is destroyed.
In your models/user.rb
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy # destroy user's profile
after_create :create_default_profile
... ...
private
def create_default_profile
Profile.create(user_id: self.id)
end
end
Rails associations have a dependent option, which handles deleting associated records.
User < ActiveRecord::Base
has_one :profile, dependant: :destroy
end
:destroy, when the object is destroyed, destroy will be called on its
associated objects.
:delete, when the object is destroyed, all its
associated objects will be deleted directly from the database without
calling their destroy method.
Creating the associated Profile can be done with ActiveRecord callbacks or in the controller. I have getting more and more weary of callbacks since its difficult to control when and where they are actually run.
In the case of Devise you would override the Devise::RegistrationsController.

How would I implement a multi-type user system for Rails?

I'm creating a Rails application where users can sign up by checking a box in a form where they are either a "person" or "organization". I'm struggling to find a way to implement this into Rails. Both of these user types would have the same authorization. I have no idea if I want to use a string or a boolean as a data type in my ActiveRecord database. Also, what would I need to put in my model (User.rb) and my controller in order to validate it and implement it respectively?
There are many ways to implement this; it depends on what your needs are. Ask yourself: "Do people and organizations share the same attributes?"
AR Enum
If they do, the only thing that differentiates the two is role (or whatever you want to call it), i.e., person or organization. For that scenario, Rails 4.1 provides AR enums. This is the simplest solution, it could go something like this:
class User < ActiveRecord::Base
enum role: [ :person, :organization ] # #user.role => 'person', #user.person? => true
end
Polymorphic Association
On the other hand, if people and organizations share only some attributes, you might consider using a polymorphic association (If people and organizations share no attributes—not even role—they should be two different models). The base model should contain the attributes that both people and organizations share. The person/organization models should contain attributes specific to that model.
# common attributes
class User < ActiveRecord::Base
belongs_to :profile, polymorphic: true
def self.roles
%w(person organization)
end
end
# person-specific attributes
class PersonProfile < ActiveRecord::Base
has_one :user, as: :profile, dependent: :destroy
end
# organization-specific attributes
class OrganizationProfile < ActiveRecord::Base
has_one :user, as: :profile, dependent: :destroy
end
For user signup, you can create users#new and users#create actions. In your user signup form (perhaps app/views/users/new.html.erb), you could use a select_tag to let the user specify their role. Then, use that to determine what kind of profile to attach to your user model. For example (users#create):
def create
#user = User.new(user_params)
if role = params[:role]
# return HTTP 400
head :bad_request and return unless User.roles.include?(role)
# Assign the User's profile
#user.profile = "#{role.capitalize}Profile".constantize.new
else
# enter your own logic here
end
#user.save ? redirect_to(#user) : render(:new)
end
The handling of sessions (user signin/signout), in my opinion, should be handled in a separate SessionsController.
Add a new table to the database named user_types with fields role and id. And in users table you need to add user_type_id column. Then, in UserType model
class UserType < ActiveRecord::Base
has_many :users
end
And in User model you need to add
class User < ActiveRecord::Base
belongs_to :user_type
end
You can create UserType records in seeds, but make sure it runs everytime the database is reset, add this to seeds.rb
UserType.create!(role: "person")
UserType.create!(role: "organization")
Hope this makes sense!
If you have only two types of users (Person and Organization) as indicated in your question, you could just have a Person model, and add a bool field is_organization to it. For more details, See how devise, a popular authentication gem, handles this here (this approach is option 2 on the linked page, you can also check out option 1, which is to create an entire new model).

Adding A LOT Attributes to Rails Devise

I am implementing a login system, which require to collect a lot of user data, for example:
college, course, graduate year, start year, hobby, .... about 20-30 of them.
Is it wise to put them all into Devise? Or create another Model to handle that?
Its not good idea to put so much of data in devise model. Devise model record is always fetched from database for every request.(You can see it from logs)
You can add it in another model and add association.
e.g. you can add profile model
Assuming you have User model as devise model.
You have to take care of creating profile record after either User creation or User logs in first time or as per your requirement.
class User < ActiveRecord::Base
has_one :profile, :dependent => :destroy
end
class Profile < ActiveRecord::Base
belongs_to :user
end
You can create a user_info model with this association, in user.rb
has_one :user_info
On, sign_in it should create an instance of user_info if its not present in the databse
This approach would be better if you want to add 20-30 columns

Resources