Rails 4, Rolify and Assigning Roles - ruby-on-rails

I am trying to make an app with Rails 4. I use, Devise, Rolify and Simple Form.
My current problem is in trying to assign rolify roles to users.
I have the following code:
User.rb
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
# p '11111'
# Create the user if needed
if user.nil?
# p 22222
# Get the existing user by email if the provider gives us a verified email.
# If no verified email was provided we assign a temporary email and ask the
# user to verify it on the next step via UsersController.finish_signup
email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
email = auth.info.email
user = User.where(:email => email).first if email
# Create the user if it's a new registration
if user.nil?
# p 33333
user = User.new(
# at least one problem with this is that each provider uses different terms to desribe first name/last name/email. See notes on linkedin above
first_name: auth.info.first_name,
last_name: auth.info.last_name,
email: email,
#username: auth.info.nickname || auth.uid,
password: Devise.friendly_token[0,20])
#
# debugger
# if email_is_verified
# user.skip_confirmation!
# end
user.skip_confirmation!
user.save!
end
end
# Associate the identity with the user if needed
if identity.user != user
identity.user = user
identity.save!
end
user
end
def email_verified?
self.email && TEMP_EMAIL_REGEX =~ self.email
end
def full_name
[*first_name.capitalize, last_name.capitalize].join(" ")
end
after_create :add_default_role
def add_default_role
add_role(:pending) if self.roles.blank?
end
Role.rb
class Role < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => :users_roles
belongs_to :resource, :polymorphic => true
validates :resource_type,
:inclusion => { :in => Rolify.resource_types },
:allow_nil => true
scopify
end
Users/omniauth_callbacks_controller
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
#user = User.find_for_oauth(env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
# sign_in_and_redirect_user(:user, event: :authentication)
[:twitter, :facebook, :linkedin, :google_oauth2].each do |provider|
provides_callback_for provider
end
def after_sign_in_path_for(resource)
if resource.email_verified?
super resource
else
finish_signup_path(resource)
end
end
Users controller
private
def set_user
#user = User.find(params[:id])
end
def user_params
accessible = [ :first_name, :last_name, :email, {role_ids: []}] # extend with your own params
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
accessible << [:approved] if user.admin
params.require(:user).permit(accessible)
end
Users#index
<% Users.each do |user| %>
<div class="row">
<div class="col-xs-5">
<%= link_to "#{user.full_name}", user %>
</div>
<div class="col-xs-5">
<%= link_to "#{user.email}", user %>
</div>
<div class="col-xs-2">
<%= link_to "edit", edit_user_path(user) %>
</div>
</div>
<% end %>
Users#form
<%= simple_form_for(#user) do |f| %>
<%= f.error_notification %>
<% Role.all.each do |role| %>
<div class="form-inputs">
<%= f.input "user[role_ids][]", role.id, collection: #user.role_ids.include?(role.id) %>
<%= role.name %>
</div>
<div class="form-actions">
<%= f.button :submit, "Add role", :class => 'formsubmit' %>
</div>
I have also tried:
<%= f.association :roles %>
<%= role.name %>
in the user#form
Migration to add roles to role table:
class AddRolesToRolifyTable < ActiveRecord::Migration
def change
['admin', # internal admin
'manager', # internal manager
'editor', # internal web content editor
'support', # internal user support
'educator', # lecturers
'faculty_manager', #manage the business side
'project_manager',
'pending', # new people that are not yet confirmed in a role - default role assignment
].each do |role_name|
Role.create! name: role_name
end
end
end
When I save this and try to run the local host and go to users#index, I get an error that says:
Couldn't find User with 'id'=
This method is highlighted:
private
def set_user
#user = User.find(params[:id])
end
I can't say I've properly understood how rolify works with devise. My console shows that I have two test users in the db, each of which has an id (so im not sure how to explore this error further). Does anyone see where I've gone wrong?
I have adapted this setup using advice in this post:
Defining Roles with Rolify

Rolify and Devise
I can't say I've properly understood how rolify works with devise.
Rolify and Devise do very different jobs. There is no actual integration between the two.
Devise is an authentication solution, authentication is only about determining if there is a signed in user and if that user is who he/she claims to be.
Authorization on the other hand is about who is allowed to do what in a system. Rolify is a generic roles library which is meant to be used as a smaller part of an authentication solution. Just defining your authorization with .has_role? is going to get very messy quick.
It is most often combined with something like CanCanCan or Pundit which provide facilities to define authentication rules.
https://github.com/CanCanCommunity/cancancan/wiki/Role-Based-Authorization
Collection helpers
Rails has built in helpers for creating selects and checkboxes for associations and simple_form takes it a bit further.
So lets say you have a users/:user_id/edit route where admins edit a users profile and add roles:
<%= simple_form_for(#user) do |f| %>
<fieldset>
<legend>Roles</legend>
<%= f.association :roles, as: :check_boxes, collection: Role.all %>
</fieldset>
<% f.submit %>
<% end %>
Note that you don't need a special form for editing the roles in this case - this will work both when creating and editing users.
Your params sanitation is bit off:
def user_params
accessible = [ :first_name, :last_name, :email, role_ids: []]
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
accessible << [:approved] if user.admin? # don't do this. There are better ways.
params.require(:user).permit(accessible)
end
Also:
Migrations are for altering the database schema, not for seeding the system with default info. Use rake db:seeds or create your own rake task instead.
I would also question if you really need a pending role and the approved param. I would simply check if the user has no roles - which defacto means that he is pending. When a admin or peer verifies a user you would add the approved role.

Related

Is it possible limit the number of emails sent for devise recoverable in a period of time for each user?

Is it possible limit the number of emials sent for devise recoverable in a period of time for each user? How can I defend against malicious requests?
I would use grecaptcha to protect your form where you let the user rescue his account.
It's really easy and simple to use and include it in your rails app.
In your view:
<%= form_for #user do |f| %>
<%= recaptcha_tags %>
<% end %>
In your controller, create action:
def create
verify_recaptcha(model: #user) # => returns true or false
end
To limit: "emials sent for devise recoverable"
Example Gemfile:
gem 'simple_captcha2'
routes:
devise_for :users, :controllers => { ..., :passwords => 'passwords', ... }
app/controllers/passwords_controller.rb:
class PasswordsController < Devise::PasswordsController
prepend_before_action :require_no_authentication
#
# GET /resource/password/new
def create
if simple_captcha_valid?
super
else
....
end
end
end
app/views/devise/passwords/new.html.erb
into the form_for:
<%= show_simple_captcha %>

Sending a confirmation email without creating a model/controller and use of a db?

I've created a single page landing site which contains a simple waitlist form that dumps the info into a google doc (trying to keep it low tech for now, since an app will be built on top of this as well).
I'm trying to send the people who submit their email address for the waiting list a confirmation email. Can I pass the email address to a mailer without creating a model/controller and use of a db?
I've done this before by creating a model/controller (see below code) and use of a db (postgreSQL / Heroku), but it seems messy for such a simple task.
Here's my setup in the past, looking to get rid of the model/controller and pass the email address (w/ .downcase transformation) to the mailer directly.
models/waitlist.rb
class WaitList < ActiveRecord::Base
before_save {self.email = email.downcase}
attr_accessible :email
validates :email, :presence => true, format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }
end
controllers/waitlist_controller.rb
class WaitlistsController < ApplicationController
def create
#waitlist = Waitlist.new(params[:waitlist])
if #waitlist.save
# Tell the ProspectMailer to send an email to us
WaitlistMailer.waitlist_email(#waitlist).deliver
end
end
private
end
mailer/waitlist.rb
class Waitlist < ActionMailer::Base
default from: "hello#domain.com"
def waitlist_email(waitlist)
#waitlist = waitlist
mail( :to => #waitlist.email,
:subject => 'Thanks for signing up!' )
end
end
views/waitlist/waitlist_email.html.erb
Email text (possibly HTML) for the body of the email sent via the mailer
Thanks for the help!
Your controller should tell the Mailer to send the email.
In your controller create action, why not just pass the email param to your mailer?
app/controllers/wait_lists_controller.rb
class WaitListsController < ApplicationController
def create
send_wait_list_email(params[:wait_list][:email])
end
private
def send_wait_list_email(email)
WaitListMailer.wait_list_email(email).deliver
end
end
app/mailers/wait_list_mailer.rb
class WaitListMailer < ActionMailer::Base
def wait_list_email(email)
mail(to: email, subject: "Thanks for signing up!")
end
end
app/views/wait_lists/new.html.erb
<%= form_for(:wait_list, url: wait_lists_path) do |f| %>
<%= f.text_field :email, placeholder: "Email" %>
<%= f.submit "Join" %>
<% end %>
If you want to do validation on the email, I'd recommend keeping your WaitList model and including ActiveModel::Model — not all models have to inherit from ActiveRecord::Base, only when they need it.
app/models/wait_list.rb
class WaitList
include ActiveModel::Model
attr_reader :email
def initialize(email)
#email = email
post_initialize
end
validates(
:email,
presence: true,
format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }
)
private
def post_initialize
if email
email.downcase!
end
end
end
app/controllers/wait_lists_controller.rb
class WaitListsController < ApplicationController
def new
#wait_list = WaitList.new
end
def create
#wait_list = WaitList.new(wait_list_params)
if #wait_list.valid?
send_wait_list_email(#wait_list)
# redirect somewhere
else
render :new
end
end
private
def send_wait_list_email(wait_list)
WaitListMailer.wait_list_email(wait_list).deliver
end
def wait_list_params
params.require(:wait_list).permit(:email)
end
end
app/views/wait_lists/new.html.erb
<%= form_for(#wait_list) do |f| %>
<%= f.text_field :email, placeholder: "Email" %>
<%= f.submit "Join" %>
<% end %>
Hope that helps.
http://edgeguides.rubyonrails.org/action_mailer_basics.html#calling-the-mailer
http://api.rubyonrails.org/classes/ActiveModel/Model.html

Ruby on Rails, find if a certain value exists in a column

I'm building a website with user authentication. And I just noticed that if I create a user with an existing email, it just doesn't work which is normal, but I'd like to give feedback to the user. So for that I need to determine if any user has already that email.
I've tried some things like:
if User.email.include? params[:user][:email]
flash.now[:error] = "A user with this password already exists"
render :action => :new, :layout => 'signin-layout.html.erb'
Those are the columns for User:
2.1.0 :014 > User.column_names
=> ["id", "name", "email", "created_at", "updated_at", "password_digest", "remember_token", "admin", "team_id", "teamLeader"]
And the result I get is a big fat error:
undefined method `email' for #<Class:0x00000102b9a908>
So if anybody sees what I'm doing wrong, or knows another way to do it, that would be great.
Cheers
Try this:
if User.exists?(:email => params[:user][:email])
flash.now[:error] = "A user with this password already exists"
render :action => :new, :layout => 'signin-layout.html.erb'
...
else
# more code here..
end
Also, you can add validations when you're creating the object:
class User
validates_uniqueness_of :email
More on different validations here: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
I believe this way of doing the validation is wrong, you should validate the uniqueness of the email in the User model itself like below
validates :email, uniqueness: true #User model
This way the validation would be on the the User model. The problem with the condition you are using is that it is accessing an instance method specific to objects as a class method. So User.email means that there is a method called email that has the same logic for all the instances of the user class or more formally a class method which you don't have here. The email is an attribute specific to each user an instance attribute/variable (Each user has a different email).
You can see/show the validation errors present on the model using #user.errors.full_messages where #user is the instance you are trying to register/save.
This is how I would normally do it if this action is for registering users i.e. creating new users.
class User < ActiveRecord::Base
#attribute accessors and accessible
validates :email, uniqueness: true
end
class UsersController < ApplicationController
def create
#user = User.new params[:user]
if #user.save
#code for redirect or rendering the page you want
else
render 'new'
end
end
end
#new.html.erb
<%= form_for #user do |f| %>
<% if #user.errors.any? %>
<div>
<ul>
<% #job.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
#form fields
<% end %>
This way you display all the error messages to the user at the top of the registration form.

Editing Users With Devise and Omniauth

I'm working through the Railscast on implementing Devise and OmniAuth (along with the Devise documentation) -- currently, I've got a site going where visitors can sign up using their facebook accounts or by filling out a form.
I'm running into trouble when users that sign up via OmniAuth try to edit their profiles, though. Devise looks for the user's current password when they submit changes to their profiles, but those that logged in with facebook don't know their passwords (they're set automatically in the user model):
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
user = User.create(first_name:auth.extra.raw_info.first_name,
last_name:auth.extra.raw_info.last_name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20]
)
end
user
end
When a user edits his information, the app should not require password confirmation if he set up his account through OmniAuth. The tutorial suggests that the handy password_required? method will help me achieve this outcome. Specifically, adding this method to the user model means that it should only return true if the user didn't sign up through OmniAuth (the provider attribute would be nil in that case):
def password_required?
super && provider.blank?
end
Thus, a piece of code like:
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<%= render :partial => "essential_user_info_inputs", :locals => { :f => f } %>
<%= render :partial => "inessential_user_info_inputs", :locals => { :f => f } %>
<% if f.object.password_required? %>
<%= render :partial => "password_inputs", :locals => { :f => f } %>
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password %>
<% end %>
<%= f.submit "Update" %>
<% end %>
would theoretically only display password inputs when needed. It also suggests that Devise has built in logic saying that OmniAuth users don't need to use passwords to edit their accounts. I have no idea if this is true, but the tutorial kind of makes it look like that. But when an OmniAuth user tries to edit his account, I get "Current password can't be blank." Same thing with non-OmniAuth users (this makes sense, since the password fields don't show up on those users' edit pages either).
Some poking around confirms that the password_required? method is returning false, both when the user signed up through OmniAuth and through the site's regular user signup. Even when I change it to simply run the superclass method, it returns false.
Any ideas of what's going on with the password_required method? I can't find anything about it anywhere, but I feel like that's what's tripping things up right now.
Update:
This is now working, but not using the method outlined in the Railscast, which relies on requires_password? method, a topic that I still know nothing about. Instead, I implemented the solution outlined here, as suggested here. So I am now only requiring passwords to update non-OmniAuth accounts with the code:
class Users::RegistrationsController < Devise::RegistrationsController
def update
#user = User.find(current_user.id)
email_changed = #user.email != params[:user][:email]
is_facebook_account = !#user.provider.blank?
successfully_updated = if !is_facebook_account
#user.update_with_password(params[:user])
else
#user.update_without_password(params[:user])
end
if successfully_updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to root_path
else
render "edit"
end
end
end
The easiest way is to overwrite the update_resource method in your RegistrationsController. This is advised by devise in their own implementation of the controller:
# By default we want to require a password checks on update.
# You can overwrite this method in your own RegistrationsController.
def update_resource(resource, params)
resource.update_with_password(params)
end
So the solution is to overwrite this method in your own controller like this:
class Users::RegistrationsController < Devise::RegistrationsController
# Overwrite update_resource to let users to update their user without giving their password
def update_resource(resource, params)
if current_user.provider == "facebook"
params.delete("current_password")
resource.update_without_password(params)
else
resource.update_with_password(params)
end
end
end
I've added an update to the link below that includes my solution to the Devise/ OmniAuth change user profile/password issue and collected some helpful links:
stackoverflow - Allowing users to edit accounts without saving passwords in devise
I saw this used somewhere.
def update
params[:user].delete(:current_password)
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
if current_user.update_without_password(params[:user])
redirect_to somewhere_wicked_path, notice => "You rock"
else
render 'edit', :alert => 'you roll'
end
end
use something like this in your update method in your controller. Pretty sure that method is in Devise too.

Multiple user models with Ruby On Rails and devise to have separate registration routes but one common login route

First, I've searched intensely with Google and Yahoo and I've found several replies on topics like mine, but they all don't really cover what I need to know.
I've got several user models in my app, for now it's Customers, Designers, Retailers and it seems there are yet more to come. They all have different data stored in their tables and several areas on the site they're allowed to or not. So I figured to go the devise+CanCan way and to try my luck with polymorphic associations, so I got the following models setup:
class User < AR
belongs_to :loginable, :polymorphic => true
end
class Customer < AR
has_one :user, :as => :loginable
end
class Designer < AR
has_one :user, :as => :loginable
end
class Retailer < AR
has_one :user, :as => :loginable
end
For the registration I've got customized views for each different User type and my routes are setup like this:
devise_for :customers, :class_name => 'User'
devise_for :designers, :class_name => 'User'
devise_for :retailers, :class_name => 'User'
For now the registrations controller is left as standard (which is "devise/registrations"), but I figured, since I got different data to store in different models I'd have to customize this behaviour as well!?
But with this setup I got helpers like customer_signed_in? and designer_signed_in?, but what I'd really need is a general helper like user_signed_in? for the areas on the site that are accessible to all users, no matter which user type.
I'd also like a routes helper like new_user_session_path instead of the several new_*type*_session_path and so on. In fact all I need to be different is the registration process...
So I was wondering IF THIS IS THE WAY TO GO for this problem? Or is there a better/easier/less must-customize solution for this?
Okay, so I worked it through and came to the following solution.
I needed to costumize devise a little bit, but it's not that complicated.
The User model
# user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
belongs_to :rolable, :polymorphic => true
end
The Customer model
# customer.rb
class Customer < ActiveRecord::Base
has_one :user, :as => :rolable
end
The Designer model
# designer.rb
class Designer < ActiveRecord::Base
has_one :user, :as => :rolable
end
So the User model has a simple polymorphic association, defining if it's a Customer or a Designer.
The next thing I had to do was to generate the devise views with rails g devise:views to be part of my application. Since I only needed the registration to be customized I kept the app/views/devise/registrations folder only and removed the rest.
Then I customized the registrations view for new registrations, which can be found in app/views/devise/registrations/new.html.erb after you generated them.
<h2>Sign up</h2>
<%
# customized code begin
params[:user][:user_type] ||= 'customer'
if ["customer", "designer"].include? params[:user][:user_type].downcase
child_class_name = params[:user][:user_type].downcase.camelize
user_type = params[:user][:user_type].downcase
else
child_class_name = "Customer"
user_type = "customer"
end
resource.rolable = child_class_name.constantize.new if resource.rolable.nil?
# customized code end
%>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= my_devise_error_messages! # customized code %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></div>
<% # customized code begin %>
<%= fields_for resource.rolable do |rf| %>
<% render :partial => "#{child_class_name.underscore}_fields", :locals => { :f => rf } %>
<% end %>
<%= hidden_field :user, :user_type, :value => user_type %>
<% # customized code end %>
<div><%= f.submit "Sign up" %></div>
<% end %>
<%= render :partial => "devise/shared/links" %>
For each User type I created a separate partial with the custom fields for that specific User type, i.e. Designer --> _designer_fields.html
<div><%= f.label :label_name %><br />
<%= f.text_field :label_name %></div>
Then I setup the routes for devise to use the custom controller on registrations
devise_for :users, :controllers => { :registrations => 'UserRegistrations' }
Then I generated a controller to handle the customized registration process, copied the original source code from the create method in the Devise::RegistrationsController and modified it to work my way (don't forget to move your view files to the appropriate folder, in my case app/views/user_registrations
class UserRegistrationsController < Devise::RegistrationsController
def create
build_resource
# customized code begin
# crate a new child instance depending on the given user type
child_class = params[:user][:user_type].camelize.constantize
resource.rolable = child_class.new(params[child_class.to_s.underscore.to_sym])
# first check if child instance is valid
# cause if so and the parent instance is valid as well
# it's all being saved at once
valid = resource.valid?
valid = resource.rolable.valid? && valid
# customized code end
if valid && resource.save # customized code
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => redirect_location(resource_name, resource)
else
set_flash_message :notice, :inactive_signed_up, :reason => inactive_reason(resource) if is_navigational_format?
expire_session_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords(resource)
respond_with_navigational(resource) { render_with_scope :new }
end
end
end
What this all basically does is that the controller determines which user type must be created according to the user_type parameter that's delivered to the controller's create method by the hidden field in the view which uses the parameter by a simple GET-param in the URL.
For example:
If you go to /users/sign_up?user[user_type]=designer you can create a Designer.
If you go to /users/sign_up?user[user_type]=customer you can create a Customer.
The my_devise_error_messages! method is a helper method which also handles validation errors in the associative model, based on the original devise_error_messages! method
module ApplicationHelper
def my_devise_error_messages!
return "" if resource.errors.empty? && resource.rolable.errors.empty?
messages = rolable_messages = ""
if !resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
end
if !resource.rolable.errors.empty?
rolable_messages = resource.rolable.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
end
messages = messages + rolable_messages
sentence = I18n.t("errors.messages.not_saved",
:count => resource.errors.count + resource.rolable.errors.count,
:resource => resource.class.model_name.human.downcase)
html = <<-HTML
<div id="error_explanation">
<h2>#{sentence}</h2>
<ul>#{messages}</ul>
</div>
HTML
html.html_safe
end
end
UPDATE:
To be able to support routes like /designer/sign_up and /customer/sign_up you can do the following in your routes file:
# routes.rb
match 'designer/sign_up' => 'user_registrations#new', :user => { :user_type => 'designer' }
match 'customer/sign_up' => 'user_registrations#new', :user => { :user_type => 'customer' }
Any parameter that's not used in the routes syntax internally gets passed to the params hash. So :user gets passed to the params hash.
So... that's it. With a little tweeking here and there I got it working in a quite general way, that's easily extensible with many other User models sharing a common User table.
Hope someone finds it useful.
I didn't manage to find any way of commenting for the accepted answer, so I'm just gonna write here.
There are a couple of things that don't work exactly as the accepted answer states, probably because it is out of date.
Anyway, some of the things that I had to work out myself:
For the UserRegistrationsController, render_with_scope doesn't exist any more, just use render :new
The first line in the create function, again in the UserRegistrationsController doesn't work as stated. Just try using
# Getting the user type that is send through a hidden field in the registration form.
user_type = params[:user][:user_type]
# Deleting the user_type from the params hash, won't work without this.
params[:user].delete(:user_type)
# Building the user, I assume.
build_resource
instead of simply build_resource. Some mass-assignment error was coming up when unchanged.
If you want to have all the user information in Devise's current_user method, make these modifications:
class ApplicationController < ActionController::Base
protect_from_forgery
# Overriding the Devise current_user method
alias_method :devise_current_user, :current_user
def current_user
# It will now return either a Company or a Customer, instead of the plain User.
super.rolable
end
end
I was following the above instructions and found out some gaps and that instructions were just out of date when I was implementing it.
So after struggling with it the whole day, let me share with you what worked for me - and hopefully it will save you few hours of sweat and tears
First of all, if you are not that familiar with RoR polymorphism, please go over this guide:
http://astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/
After following it you will have devise and user users models installed and you will be able to start working.
After that please follow Vapire's great tutorial for generating the views with all the partails.
What I found most frustrating was that dut to using the latest version of Devise (3.5.1), RegistrationController refused to work.
Here is the code that will make it work again:
def create
meta_type = params[:user][:meta_type]
meta_type_params = params[:user][meta_type]
params[:user].delete(:meta_type)
params[:user].delete(meta_type)
build_resource(sign_up_params)
child_class = meta_type.camelize.constantize
child_class.new(params[child_class.to_s.underscore.to_sym])
resource.meta = child_class.new(meta_type_params)
# first check if child intance is valid
# cause if so and the parent instance is valid as well
# it's all being saved at once
valid = resource.valid?
valid = resource.meta.valid? && valid
# customized code end
if valid && resource.save # customized code
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
set_minimum_password_length
respond_with resource
end
end
end
and also add these overrides so that the redirections will work fine:
protected
def after_sign_up_path_for(resource)
after_sign_in_path_for(resource)
end
def after_update_path_for(resource)
case resource
when :user, User
resource.meta? ? another_path : root_path
else
super
end
end
In order that devise flash messages will keep working you'll need to update config/locales/devise.en.yml instead of the overridden RegistraionsControlloer by UserRegistraionsControlloer all you'll need to do is add this new section:
user_registrations:
signed_up: 'Welcome! You have signed up successfully.'
Hope that will save you guys few hours.

Resources