Rails 3: Devise add roles checkboxes to registration form - ruby-on-rails

I'm using Devise for user registration. I've been reading the all famous tutorial about customizing Devise, but can't understand this simple task. I followed his model (HABTM)
I want to add a roles check box to the Devise edit form. I don't have a Controller cause Devise doesn't provide one, but managed to add a default role to new users. I was able to display the checkboxes with the correct info checked but can't edit it (it won't save anydata). Do I need a custom controller? if yes, how exactly? I'm new to HABTM relations!
My User model
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
before_save :setup_role
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable, :lockable and :timeoutable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
def role?(role_sym)
roles.any? { |r| r.name.underscore.to_sym == role_sym }
end
# Default role is "User"
def setup_role
if self.role_ids.empty?
self.role_ids = [3]
end
end
end
My edit form (devise/registrations/edit.html.rb
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<p><%= f.label :email %><br />
<%= f.text_field :email %></p>
<p><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password %></p>
<p><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></p>
<p><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password %></p>
<% for role in Role.find(:all) %>
<div>
<%= check_box_tag "user[role_ids][]", role.id, #user.roles.include?(role) %>
<%= role.name %>
</div>
<% end %>
<p><%= f.submit "Update" %></p>
<% end %>
<h3>Cancel my account</h3>
<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p>
<%= link_to "Back", :back %>

check your console, I was getting a 'Can't mass assign" error, then I put :role_ids into the user model's attr_accessible and it worked.

Related

Problem saving custom attribute in rails devise gem

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.

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

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 %>

Rails + Devise custom views don't show nested fields well filled after error

Hy using rails-3.2.13 with devise-3.4.1.
I have override the views:
rails g devise:views
And I have customized new.html.erb view:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<%= f.fields_for :profile,resource.build_profile do |organization_form| %>
<%= organization_form.text_field :name %>
<%= organization_form.text_field :surname %>
<% end %>
<%= f.email_field :email %>
<%= f.password_field :password %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Sign Up" %>
<% end %>
Everything works fine, except when there is a validation error. The form: organization_form isn't re-filled with the user input while the form f is well filled. So: :name and :surname are not re-filled after a validation error.
This is my User model:
class User < ActiveRecord::Base
...
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile, :dependent => :destroy
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
attr_accessible :profile_attributes
accepts_nested_attributes_for :profile
....
end
Can you help me?
It should be <% resource.profile || resource.build_profile %>, otherwise you won't get the passed values filled in on form failure.
So the new.html.erb now is like this:
<% resource.profile || resource.build_profile %>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<%= f.fields_for :profile do |organization_form| %>
<%= organization_form.text_field :name %>
<%= organization_form.text_field :surname %>
<% end %>
<%= f.email_field :email %>
<%= f.password_field :password %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Sign Up" %>
<% end %>

How to avoid "Can't mass-assign protected attributes" error

Even though I added accepts_nested_attributes_for to my model.
it still says "Can't mass-assign protected attributes"
What else am I supposed to do in order to avoid this???
models/user.rb
class User < ActiveRecord::Base
validates_presence_of :username
validates_uniqueness_of :username
validates_length_of :username, :within => 4..10
acts_as_messageable
has_one :user_profile
accepts_nested_attributes_for :user_profile
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :username, :user_profile_attributes
def mailboxer_email(message)
email
end
# def name
# email
# end
end
models/user_profile.rb
class UserProfile < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user
attr_accessible :nickname
end
views/registration/edit.html.erb
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :nickname %><br />
<%= f.fields_for :nickname_attributes, #user.user_profile do |user_profile| %>
<%= user_profile.text_field :nickname %>
<% end %>
</div>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password %></div>
<%= recaptcha_tags :display => {:theme => 'red'} %>
<div><%= f.submit "Update" %></div>
<% end %>
<h3>Cancel my account</h3>
<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p>
<%= link_to "Back", :back %>
attr_accessible defines the attributes you want the user to be able to mass assign. Just make sure it has all the attributes you want in there.
To be fair, you can remove attr_accessible if you don't care about it and the error will disappear (but all your model fields will be mass assignable).
in edit.html.erb
wrong:
f.fields_for :nickname_attributes,
correct:
f.fields_for :user_profile_attributes,

Resources