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
Related
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
I have two models in my app, one 'user' model with devise, one 'employee' model made by scaffolding.
I need a way to have the employee table populated as soon as a new user registers, both tables share some params, some are exclusive. The employee belongs to the user model, each user has one employee.
The view I use is the devise user registration form with nested attributes to allow for the employee params. Creation is handled by the user controller.
Problems that occur:
undefined method 'email' for empty password field until the first user is created
cannot make the error message for the exclusive 'name' parameter of the employee model show up in the same place as the error messages for the user model
employee-model:
class Employee < ApplicationRecord
audited
validates :email, presence: true, uniqueness: true
validates :name, presence: true
belongs_to :user, inverse_of: :employee, dependent: :destroy, optional: true
user model
class User < ApplicationRecord
audited
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable
has_one :employee, inverse_of: :user
accepts_nested_attributes_for :employee
validates_presence_of :password
validates :password, format: { with: /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§#?!`´~#'._,;<>|°()"{}="#$%^&*+-]]).{8,}/}, if: -> {password.present?}
validates_presence_of :password_confirmation, if: -> {password.present? and password =~ /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§#?!`´~#'._,;<>|°()"{}="#$%^&*+-]]).{8,}/}
validates_confirmation_of :password, if: -> {password.present? and password_confirmation.present? and password =~ /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§#?!`´~#'._,;<>|°()"{}="#$%^&*+-]]).{8,}/}
validates_presence_of :email
validates :email, format: { with: /\A([^#[0-9]\s]+\.)+([^#[0-9]\s]+)#((thisapp+\.)+com)\z/i}, uniqueness: true, if: -> {email.present?}
end
user controller
class Users::RegistrationsController < Devise::RegistrationsController
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
def new
super
end
def new_employee
#employee = Employee.new
end
# POST /resource
def create
#employee = Employee.new(employee_params)
super
#employee.email = User.last.email
#employee.user_id = User.last.id
#employee.created_by_id = User.last.id
#employee.save
end
user registration view
.card style='text-align:center;'
.card-body
h2.card-title style='text-align:center;' = t('.sign_up')
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
= render "users/shared/error_messages", resource: resource
br
.field
= f.label :email, t('users.email_field')
br
= f.text_field :email, autofocus: true, autocomplete: "email"
.field
= f.label :password, t('users.password_field')
/- if #minimum_password_length
em
= t('devise.shared.minimum_password_length', count: #minimum_password_length)
br
= f.password_field :password, autocomplete: "new-password"
.field
= f.label :password_confirmation, t('users.password_confirmation_field')
br
= f.password_field :password_confirmation, autocomplete: "new-password"
= fields_for :employee do |e|
= e.label :name
= e.text_field :name
So, when I use #employee.email = User.last.email in the controller I get a 'unknown method 'email' error when not filling out the password field, unless I have a preexisting user, then I get my custom error messages for not filling out the email. I guess it is because I am looking for a last user who does not exist at this point. Could seed a user, but that seems hackish. Tried using #employee.email = User.last(params[:email]) which leads to the email being saved as some hash value, but at least I get my error messages. Is there a way to convert that hash to the real email address again?
The other issue is the validation of the name field. Validation is asked for in the employee model, and user model accepts nested attributes, but that does not seem to be enough.
I did
if params[:employee][:name].blank?
flash[:notice] = t('.noname')
which works insofar as that the form cannot be submitted without some value in the name field, but messes up my error messages. Shows a flash message where all other errors (no email/password/pw confirmation) are handled by the devise's shared error messages as non-flash messages:
- resource.errors.full_messages.each do |message|
li
= message
So having the blank name as a flash message would look inconsistent, and the spot for the flash message is already reserved.
Flash message is on top, 'following errors prevent..', actual error messages are below 'Registrieren', and that is also where the error message for blank name would need to be.
Any ideas on how to approach this or maybe a better solution than handling this stuff in the user controller?
class Users::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
super
end
def new_employee
#employee = Employee.new
end
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation, employee_attributes: %i[name])
end
This signup param will be used while creating a user from devise registration controller. Since we used nested attributes, passing the arguments along with the parent object will handle employee creation(https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html)
For assigning email for employee
The one way is to merge the email from the user params.
def sign_up_params
sign_up_param = params.require(:user).permit(:email, :password, :password_confirmation,employee_attributes: %i[name])
sign_up_param[:employee_attributes].merge!(email: params[:user][:email])
sign_up_param
end
Or maybe you could assign the email of the employee from the user before the validation.
class Employee < ApplicationRecord
audited
validates :email, presence: true, uniqueness: true
validates :name, presence: true
belongs_to :user, inverse_of: :employee, dependent: :destroy, optional: true
# Callbacks
before_validation :set_email
# Methods
# Set email of the employee from the user.
def set_email
self.email = self.user.email
end
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
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
FINAL SOLUTION:
I have a Rails 3 app that uses Devise to handle authentication. In the signup form i have the following fields:
<p><%= f.label :first_name %><br />
<%= f.text_field :first_name %></p>
<p><%= f.label :last_name %><br />
<%= f.text_field :last_name %></p>
I need to capitalize first and last names and combine them in the User model in a database field called 'login' (e.g. Lastname, Firstname). Here is the complete user model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :first_name, :last_name, :email, :password, :password_confirmation, :remember_me, :login
validates :first_name, :last_name, :email, :password, :password_confirmation, :presence => true
before_create :create_login
def create_login
self.login = "#{last_name.capitalize}, #{first_name.capitalize}"
end
end
Thanks.
I genuinely, honestly, and truly applaud your use of the power of Ruby, but since this is such a straightforward and static concatenation of two strings, I'd go with:
def create_login
login = "#{last_name.capitalize}, #{first_name.capitalize}"
end
As for the nil:NilClass issue, are you adding first_name and last_name columns to your users table in your associated migration?
before_create :create_login
validates :first_name, :presence => true
validates :last_name, :presence => true
def create_login
login = [last_name, first_name].map(&:capitalize).join(", ")
end
Short explanation
I think it is good to get first_name and last_name on registration: so we will ad validation to it. Also it is good idea to validate length and match it with some regexp.
Then, as far as login is creates only once, we will add before_create callback, which will be executed only when object is creating (not updating). before_create callback will be run only if validation is passed, so if first_name or last_name is blank - validation won't be passed and callback won't be executed till first_name and last_name is filled.
UPD
Ok, as far as you get your error:
def create_login
login = [last_name, first_name].compact.map(&:capitalize).join(", ")
end