Problem saving custom attribute in rails devise gem - ruby-on-rails

The authentication system in my app is handled by devise and now I want each user in my system to belong to an organisation. So each organisation will have multiple users.
When signing up, each user will select which organisation they want to join.
When a user is signing up, and they select and organisation from a combo-box, they get the following error:
ActiveRecord::AssociationTypeMismatch in Devise::RegistrationsController#create
Organisation(#70213198483780) expected, got "1" which is an instance of String(#70213152374240)
The following is what my source code looks like:
app/models/organisation.rb
class Organisation < ApplicationRecord
has_many :users
end
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :activities
belongs_to :organisation
end
app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="field">
<%= f.label :password %>
<% if #minimum_password_length %>
<em>(<%= #minimum_password_length %> characters minimum)</em>
<% end %><br />
<%= f.password_field :password, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :organisation %><br />
<%= f.select :organisation, Organisation.all.collect { |o| [ o.organisation_name, o.id ] }%>
</div>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :configure_sign_up_params, if: :devise_controller?
protected
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:organisation])
end
end

I suggest you should change in your form to
<%= f.select :organisation_id, Organisation.all.collect { |o| [ o.organisation_name, o.id ] }%>
Because the dropdown makes organisation.name as key and organisation.id as value.
Then change devise_parameter_sanitizer.permit(:sign_up, keys: [:organisation_id]) to allow organisation_id to be assigned to user
Instead of using collect on Organisation.all, use Organisation.all.pluck(:name, :id). It will give same result as but a more optimised query.

Related

How to validate a role with devise gem?

Im trying to validate a enum with the gem devise, but it doesn't save in my active record.
this is my model:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates :full_name, presence: true
enum role: %i[admin_user regular_user]
end
this is my controller:
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) do |user_params|
user_params.permit({ roles:[] }, :full_name,:email, :password, :password_confirmation)
end
end
end
and this is my views:
Sign up
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="field">
<%= f.label :email%><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class= "field">
<%= f.label :full_name %>
<%= f.text_field :full_name, autocomplete: "full_name"%>
</div>
<div class="field">
<%=f.label :roles %>
<%= f.select :role, collection: User.roles.keys.to_a %>
</div>
<div class="field">
<%= f.label :password %>
<% if #minimum_password_length %>
<em>(<%= #minimum_password_length %> characters minimum)</em>
<% end %><br />
<%= f.password_field :password, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
What should i change to save the roles in active record?
You're whitelisting an array when you just need a single param:
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) do |user_params|
user_params.permit(:role, :full_name,:email, :password, :password_confirmation)
end
end
You should also fix the view so that you're using the same argument for the label and input:
<div class="field">
<%= f.label :role %>
<%= f.select :role, collection: User.roles.keys.to_a %>
</div>
This is important for assistive technologies such as screenreaders so that they can properly link the label to the input via the for attribute.

custom password digest issue

I am trying to add hashed custom password into User model. What I did is:
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :authenticate_user!
before_action :check_domain
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:slug, :custom_password_digest])
devise_parameter_sanitizer.permit(:account_update, keys: [:slug, :custom_password_digest])
end
end
new.html
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="field">
<%= f.label :password %>
<% if #minimum_password_length %>
<em>(<%= #minimum_password_length %> characters minimum)</em>
<% end %><br />
<%= f.password_field :password, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :slug %><br />
<%= f.text_field :slug %>
</div>
<div class="field">
<%= f.label :custom_password %><br />
<%= f.password_field :custom_password %>
</div>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
class User
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
has_secure_password :custom_password
validates :slug, presence: true, uniqueness: true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
end
I am getting error now:
https://i.stack.imgur.com/ILHjc.png
Could you please advise me what i am doing wrong?
Purpose is to save hashed custom_password to User table.
You're missing the field to store your encrypted password.
Create a migration to add it to the database...
rails g migration AddCustomPasswordDigestToUser custom_password_digest:string
then run the migration...
rails db:migrate

Rails Devise: "Foreign Key was set to nil" error when Updating nested Attributes on Form

I am creating a form to update a User in my Rails app with Devise.
I have separated my User data into a User model for the email and password, and a profile table for all the other data.
Created a form to update these details. Form renders fine, and parameters are being sent, but I am getting this error when I try to update the first_name for my nested record.
ActiveRecord::RecordNotSaved in Devise::RegistrationsController#update
Failed to remove the existing associated profile. The record failed to save after its foreign key was set to nil.
Extracted source (around line #93):
if target.persisted? && owner.persisted? && !target.save
set_owner_attributes(target)
raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. "
"The record failed to save after its foreign key was set to nil."
end
Models look like
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one :profile
after_create :create_profile
accepts_nested_attributes_for :profile
end
class Profile < ApplicationRecord
belongs_to :user
end
Controllers look like
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) { |u|
u.permit(:email, :password, [profile_attributes: [:id, :first_name, :last_name]])
}
devise_parameter_sanitizer.permit(:account_update) { |u|
u.permit(:email, :password, [profile_attributes: [:id, :first_name, :last_name]])
}
end
end
class UsersController < Devise::RegistrationsController
def create
super
end
def show
#user = current_user
end
def edit
#user = current_user
super
end
def update
#user = current_user
super
end
end
and the View looks like
<h1>Account Details</h1>
<p><strong>Email Address:</strong> <%= #user.email %></p>
<p><strong>First Name:</strong> <%= #user.profile.first_name %></p>
<p><strong>Last Name:</strong> <%= #user.profile.last_name %></p>
<p><strong>Description:</strong> <%= #user.profile.description %></p>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<%= f.fields_for :profile_attributes, {html: { method: :put}} do |p| %>
<div class="field">
<%= p.label :first_name %><br />
<%= p.text_field :first_name %>
</div>
<% end %>
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
<% end %>
<div class="field">
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password, autocomplete: "new-password" %>
<% if #minimum_password_length %>
<br />
<em><%= #minimum_password_length %> characters minimum</em>
<% end %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "current-password" %>
</div>
<div class="actions">
<%= f.submit "Update" %>
</div>
<% end %>
<br><br>
<%= button_to "Delete Account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>
How do I resolve this?
You need to use existing user's profile inside the form. Change line with fields_for to:
<%= f.fields_for resource.profile do |p| %>
Note, you don't need a method here, since it is not a separate form

rails undefined method status

This is a very easy question I am using devise for authentication...So I am trying to modify my sign up .I am trying to add a radio button on my sign up but it is giving me error .undefined method `status' for User:0x00000001a66368. So i want to know why i can't use radio button on my sign up page
[registration/new.html.erb]
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
<%= f.email_field :email, autofocus: true %></div>
<div><%= f.label :password %> <% if #validatable %><i>(<%= #minimum_password_length %> characters minimum)</i><% end %><br />
<%= f.password_field :password, autocomplete: "off" %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "off" %></div>
<div >
<%= f.label 'InActive' do %>
<%= f.radio_button :status,'In Active'%>
<% end %>
</div>
<div><%= f.submit "Sign up" %></div>
<% end %>
<%= render "devise/shared/links" %>
[user.rb]
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :posts
has_many :comments
validates :status,presence:true
validates :name, presence: true
def self.find_role(id)
User.find(id).roles
end
end
You need to add status field in the user table.
rails g migration add_user_status_to_user user_status:boolean
rake db:migrate
Then , In application controller, use strong parameters
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << [:user_status]
end
end
rails generate migration add_status_to_user status:boolean
rake db:migrate
You need to by pass Strong Parameters to add additional new attributes to the sign up page.
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up).push(:status)
end
end

how to write parameter sanitiser method to sanitize the parameters of other model in devise sign_up action?

i am having two tables employee , and company. i want to register the company name of an employee while Employee is registering by using the devise sign_up action. how to write devise parameter sanitiser method to save the company name while an employee is registering?
The trick is using accepts_nested_attributes_for and overriding the sign_up_params method on the registrations controller.
1. Set up the User model to accept attributes for company
class User < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
2. Override the default Registrations controller
# config/routes.rb
Rails.application.routes.draw do
# ...
devise_for :users, controllers: { registrations: "registrations" }
end
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
# GET /users/sign_up
def new
#user = User.new(company: Company.new)
end
def sign_up_params
params.require(:user).permit(
:email, :password, :password_confirmation,
company_attributes: [:name]
)
end
end
By digging into the source of Devise::RegistrationsController we can se that it calls build_resource(sign_up_params) which is about equivalent to User.new(sign_up_params). So we can simply add our own params handling by declaring our own sign_up_params method.
Note that in sign_up_paramswe use the built Rails 4 strong parameter handling instead of the Devise sanitized params which is a home rolled solution that predates Rails 4. It might be possible to do it with Devise sanitized params but there is no real reason unless you have to have backwards compatibility with Rails 3.
3. Customize the Registration form
To get the correct params hash we want the company name input to have the following name attribute:
user[company_attributes][name]
Rails has a nice helper called fields_for which lets us do that:
<%# app/views/devise/registrations/new.html.erb %>
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true %>
</div>
<div class="field">
<%= f.label :password %>
<% if #minimum_password_length %>
<em>(<%= #minimum_password_length %> characters minimum)</em>
<% end %><br />
<%= f.password_field :password, autocomplete: "off" %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "off" %>
</div>
<fieldset>
<legend>Company</legend>
<%= f.fields_for :company do |c| %>
<div class="field">
<%= c.label :name %><br />
<%= c.text_field :name%>
</div>
<% end %>
</fieldset>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
Notice that fields_for gives us a new form builder instance in the block (c) which we create our nested inputs from.
4. Beer.
for adding custom info in sign_up form Try this :
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.for(:sign_up) { |u| u.permit( :company_name, :email,:password, :password_confirmation ) }
end
end
and change in your views/devise/registrations/new.html.erb.
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :company_name %><br />
<%= f.text_field :company_name %>
</div>
<% end %>

Resources