Ruby on rails user role always remains nil using enum - ruby-on-rails

I am trying to add multiple user role functionality in devise. I am using enum for different roles, but somehow user role always remains nil after a new user signs up.
here is my implementation
user model
class User < ApplicationRecord
rolify
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
enum role: { student: 0, assistant: 1, teacher: 2}
end
I also added role in strong params of registration controller
registration_controller
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
params.require(:user).permit(:username, :email, :password, :password_confirmation, keys: [:role])
end
def account_update_params
params.require(:user).permit(:username, :email, :password, :password_confirmation, :current_password, keys: [:role])
end
end
view
<%= f.select :role, User.roles %>
What I want is that role of new user should be whatever he/she selects from dropdown while registering
But its role is always set to nil after registering. Can someone please explain how to fix this
I have read many answers and added key: [:role] in strong params but still its not working
Thanks

If you intend on using Rolify you should remove that enum column.
class User < ApplicationRecord
rolify
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
end
Rolify uses two tables (roles and users_roles) to store the roles of a user. This allows a user to have many roles while a role can have many users.
While you can create your own primitive role system based on an enum column having both in tandem will certainly confusion as Rolify's methods such as .has_role? will not take your enum column into account.
If you want to let users select roles with rolify you would do:
<%= f.collection_select :role_ids,
Role.where(name: ['student', 'assistant', 'teacher']), :id, :name %>
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
params.require(:user).permit(:username, :email, :password, :password_confirmation, role_ids: [])
end
def account_update_params
params.require(:user).permit(:username, :email, :password, :password_confirmation, :current_password, role_ids: [])
end
end

Related

Devise gem registration model issue

I have an age check for my users in which if they want to sign up for my app, they have to be a certain age. I am using the devise gem, but created a method in my user model to check the age....I get an error stating that whatever I want to do they can't due to a nil class. Basically i have a user, but the birth_date on the user is not saving.
Which tells me that where I am putting this logic is in the wrong place. However I'm not sure where. I have a registration controller, and a user controller. I also have user model. I don't have a registration model, i'm wondering if either my method needs to be in a different model that I don't have? Or if i'm building it incorrectly.
My user model
class User < ActiveRecord::Base
before_create :age_restriction
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def age_restriction
if (self.birth_date&.to_date + 18.years) < Date.today # assuming dob format is mm/dd/yy
errors.add :birth_date, 'must be older than 18'
end
end
end
My registration model
Cass RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: :create
before_action :configure_account_update_params, only: :update
protected
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:bith_date, :first_name, :last_name])
binding.pry
end
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [:birth_date, :first_name, :last_name])
end
end
The breakpoint I have in there, when I type in devise_parameter_sanitizer I get
#permitted=
{:sign_in=>[:email, :password, :remember_me],
:sign_up=>
[:email, :password, :password_confirmation, :bith_date, :first_name, :last_name],
:account_update=>[:email, :password, :password_confirmation, :current_password]},
#resource_name=:user>
Typo. It's currently, :bith_date. Fix that to birth_date and the model should be able to read the proper attribute.
It was nil, since you're checking for self.birth_date, while the param you permitted is :bith_date
Allow :birth_date in the sign_up_params.

How to permit a new parameter in rails (devise) when implementing role based authorization

I'm working on creating an application with role based authorization.So,In i have created a migration to devise users to add a new column "role"
And I have the following code block in my applications controller to permit the new parameter(role).But still when i try to sign up as a new user.I get the error that the parameter role is unpermitted.Please help me to solve this issue.
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit( :email, :password, :password_confirmation, roles: [] ) }
end
end
This is what i've got in my user model
class User < ApplicationRecord
belongs_to :role
# has_many :Product
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
ROLES = %i[admin manager customer]
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation, :role)
end
end
migration is as follows
class AddRoleToUsers < ActiveRecord::Migration[5.0]
def change
add_column :users, :role, :string
end
end
Please help me to solve this issue.Thank you.
Your user model doesn't have access to params, so you can remove the user_params method from there. Unless you're nesting attributes, you won't need to pass in the array for the role attribute, so change
devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit( :email, :password, :password_confirmation, roles: [] ) }
to
devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit( :email, :password, :password_confirmation, :role ) }
#
And you should be good to go.

profile model for devise users on separate form

I have my devise users linked to a profile model with has_one :profile I would like to keep the initial user form very simple, with the standard username email and password. I would like then the users to be prompted the profile edit form at the first login, and I would like them to be forced to fill in some data.
at the moment my profile model is :
class Profile < ActiveRecord::Base
attr_accessible :time_zone, :telephone, :country, :opt_out,
:first_name, :last_name, :address, :city, :postcode, :birthdate,
:currency_id
belongs_to :currency
validates_presence_of :telephone, :country, :first_name, :last_name,
:address, :city, :postcode, :birthdate, :currency
belongs_to :user
end
my User model is:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
before_create :initialize_user
before_destroy :destroy_profile
has_one :profile
has_one :subscription
attr_accessible :email, :password, :password_confirmation, :remember_me,
:username, :terms
validates_acceptance_of :terms
validates_presence_of :username
private
def initialize_user
generate_profile
generate_free_subscription
end
def generate_free_subscription
subscription = Subscription.new() do |s|
s.expiration_date = nil
s.plan = :free
s.billing_name = username
s.billing_street = "unknown"
s.billing_zip = "unknown"
s.billing_city = "unknown"
s.billing_country = "unknown"
s.billing_email = email
end
if subscription.save
self.subscription = subscription
self.roles = [:free]
else
msg = "Error generating free subscription for user, #{subscription.errors.to_yaml}"
logger.error msg
raise msg
end
end
def generate_profile
p = Profile.new() do |p|
p.daily_capital_exposure = 50
p.risk_per_day = 60
p.risk_per_trade = 30
p.risk_per_week = 90
p.user_id = self.id
p.time_zone = "Rome"
end
if p.save
self.profile = p
else
msg = "Error generating profile for user #{p.errors}"
logger.error msg
raise msg
end
end
def destroy_profile
p = self.profile
t = self.trades
p.destroy
t.destroy_all
end
end
My problem is that when I create a User, the callback also creates its profile, which is missing some data and so fails creation of profile.
I wouldn't like to insert in profile temporary data just to make the profile validate correctly, because I would really like to have a nice way to force users to insert such information.
I guess my error is that I shouldn't be creating the profile at the time I create the User, but I'm not sure how else to make sure the Profile is created.
Try something like this to create a default profile in the beginning :
class User < ActiveRecord::Base
rolify
searchkick autocomplete: [:fullname]
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile
before_create :build_default_profile
private
def build_default_profile
# build default profile instance. Will use default params.
# The foreign key to the owning User model is set automatically
build_profile
true # Always return true in callbacks as the normal 'continue' state
# Assumes that the default_profile can **always** be created.
# or
# Check the validation of the profile. If it is not valid, then
# return false from the callback. Best to use a before_validation
# if doing this. View code should check the errors of the child.
# Or add the child's errors to the User model's error array of the :base
# error item
end
end
This will create a profile when you create the user.
Also if you want to take the fullname during the registration itself, I do something like this :
#application_controller
before_action :configure_permitted_parameters, if: :devise_controller?
private
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:fullname, :email, :password, :password_confirmation) }
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:avatar, :fullname, :email, :password, :password_confirmation, :current_password) }
end
This way, you should be able to take the firstname during the registration and the then create the profile and then after the user logs in you can redirect it to the profile creation page where the user can be asked to fill in the other details.
Hope I could help.
You could just use the on: option in your Profile validations:
#app/models/profile.rb
Class Profile < ActiveRecord::Base
validates_presence_of :telephone, :country, :first_name, :last_name,
:address, :city, :postcode, :birthdate, :currency, on: :update #-> means this will not fire on create
end
--
In terms of building your Profile model on creation of a User, we use the following setup:
#app/models/user.rb
Class User < ActiveRecord::Base
before_create :build_profile
end
This creates a profile for the User model upon creation of that parent model

Initializing user profile on user creation with the same form with Devise 3.2 Rails 4.1

Each user in the application has a profile that has to be filled out by the user when registering. The user and profile classes are as follows:
user.rb:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :rememberable, :trackable, :validatable
has_one :profile
end
profile.rb:
class Profile < ActiveRecord::Base
belongs_to :user
end
the view form:
= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
= f.input :email, required: true, autofocus: true
= f.simple_fields_for :profile do |pf|
= pf.input :name
= pf.input :bio
= f.input :password, required: true
= f.input :password_confirmation, required: true
= f.button :submit
The problem is that the profile object needs to be initialized before the form is rendered.
I decided to override the new method of the Devise::RegistrationsController:
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
def new
build_resource({}) # copied from super
resource.build_profile # my custom initialization code
respond_with self.resource # copied from super
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << { profile_attributes: [:name, :bio] }
end
end
This doesn't seem to be very DRY since I am duplicating the code in the super new method. I might also break things if the super controller method new changes when the gem is upgraded. Any better way to override the resource (user) creation without duplicating code?
You can try to only change the User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :rememberable, :trackable, :validatable
has_one :profile
accepts_nested_attributes_for :profile
def profile
super || build_profile
end
end

Update user with devise polymorphic association

I experiencing an issue on the update of a polymorphic association.
Actually, I've several type of users such as Admin, Customer, etc...
But on the update of a customer (for example), it fails because devise ask for a password.
I've the User model which only have devise logic:
class User < ActiveRecord::Base
devise :database_authenticatable,
:registerable,
:recoverable,
:rememberable,
:trackable,
:validatable
belongs_to :role, polymorphic: true
end
customer.rb:
class Customer < ActiveRecord::Base
has_one :user, as: :role, dependent: :destroy
end
And on the controller side, customers_controller.rb:
def update
if #customer.update customer_params
redirect_to dashboard_path, flash: { success: t('validation.update', model: #customer.class.model_name.human.downcase) }
else
render 'edit'
end
end
private
def customer_params
params.require(:customer).permit(:firstname, :lastname, user_attributes: [:email, :password, :password_confirmation])
end
Here is my form view:
= simple_form_for #customer do |f|
.form-inputs
= f.fields_for :user do |u|
= u.input :email, required: true, autofocus: true
= u.input :password, autocomplete: 'off', hint: t('devise.registrations.edit.leave_blank_if_you_don_t_want_to_change_it'), required: false
= u.input :password_confirmation, required: false
= u.input :current_password, hint: t('devise.registrations.edit.we_need_your_current_password_to_confirm_your_changes'), required: true
= f.input :firstname
= f.input :lastname
I see that in the form you have added required: false for password and password_confirmation field.
The required attribute is a boolean attribute. When present, it
specifies that an input field must be filled out before submitting the
form.
BUT that is not going to restrict Devise from asking for password. By default, in Devise its mandatory which will performed every time you update a record.
If you want to update the record without providing password then follow the guidelines mentioned in Devise How To: Allow users to edit their account without providing a password

Resources