I'm trying to update a user without having to provide a password, but approaches that worked on older devise/rails versions no longer work with devise 3 and rails 4 strong parameters.
I'm using my user_controller to update but I have also tried using a custom devise registration controller with devise_parameter_sanitizer, without success.
The form does not require a password (has no password field) and the user_controller handling the update looks like so:
# PATCH/PUT /users/1
def update
if user_params[:password].blank?
Rails.logger.info "entered if statement"
user_params.delete :password
user_params.delete :password_confirmation
Rails.logger.info(user_params.inspect)
end
#user = current_user
if #user.update(user_params)
redirect_to #user, notice: 'User was successfully updated.'
else
Rails.logger.info(#user.errors.inspect)
render action: 'edit'
end
end
private
def user_params
params.require(:user).permit(:screen_name, :full_name, :email, :about,
:location, :profile_pic, :password, :password_confirmation, :current_password)
end
.. the log after a submit looks like:
Started PATCH "/users/13" for 127.0.0.1 at 2013-05-29 11:18:18 +0100
Processing by UsersController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"20avah2OzaOVubAiam/SgvbYEQ4iijEWQqmNo7xD4rY=", "user"=>{"screen_name"=>"Darcbar", "full_name"=>"Barry Darcy", "about"=>"", "location"=>"", "website_url"=>"", "twitter_username"=>"", "email"=>"barry#gmail.com"}, "commit"=>"Save changes", "id"=>"13"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 13 ORDER BY "users"."id" ASC LIMIT 1
Entered if statement...
{"screen_name"=>"Darcbar", "full_name"=>"Barry Darcy", "email"=>"barry#gmail.com", "about"=>"", "location"=>"", "twitter_username"=>"", "website_url"=>""}
(0.2ms) BEGIN
User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE ("users"."email" = 'barry#gmail.com' AND "users"."id" != 13) LIMIT 1
(0.2ms) ROLLBACK
#<ActiveModel::Errors:0x007fedf45bb640 #base=#<User id: 13, username: "darcbar", full_name: "Barry Darcy", about: "", location: "", email: "barry#gmail.com", encrypted_password: "$2a$10$Mb4zsRPPqZ9CYz0zdLMBU.62NyIk/T8s6Zw/uRTwWov3...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 9, current_sign_in_at: "2013-05-28 17:51:20", last_sign_in_at: "2013-05-28 16:42:52", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", authentication_token: nil, created_at: "2013-05-27 14:03:41", updated_at: "2013-05-28 17:51:20", screen_name: "Darcbar", profile_pic_file_name: nil, profile_pic_content_type: nil, profile_pic_file_size: nil, profile_pic_updated_at: nil>,
#messages={:password=>["please enter a password with at least 5 characters", "please enter a password with at least 5 characters"]}>
Rendered users/edit.html.haml within layouts/application (3.0ms)
Rendered partials/head/_user_options.haml (1.8ms)
Completed 200 OK in 74ms (Views: 12.1ms | ActiveRecord: 1.7ms)
Does anyone know why the password errors are present?
The password validation is coming from the user model:
validates :password, presence: true
The solution is to only validate presence on create and allow_blank on update:
validates :password, presence: true, length: {minimum: 5, maximum: 120}, on: :create
validates :password, length: {minimum: 5, maximum: 120}, on: :update, allow_blank: true
As of 2014, you can simply override a protected method and do:
class RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
resource.update_without_password(params)
end
end
You can use #user.update_without_password(user_params) method to update your other fields.
For example, I have this in my custom users_controller.rb. I update with remote call (ajax).
#users_controller.rb
def update
respond_to do |format|
if needs_password?(#user, user_params)
if #user.update_with_password(user_params_password_update)
flash[:success] = 'User was successfully updated. Password was successfully updated'
format.js {render 'update'}
else
error = true
end
else
if #user.update_without_password(user_params)
flash[:success] = 'User was successfully updated.'
format.js {render 'update'}
else
error = true
end
end
if error
flash[:error] = #user.errors.full_messages.join(', ')
format.js {render json: #user.errors.full_messages, status: :unprocessable_entity}
end
end
end
private
def needs_password?(user, user_params)
!user_params[:password].blank?
end
def user_params
params[:user].permit(:email, :password, :password_confirmation, :username, :full_name)
end
#Need :current_password for password update
def user_params_password_update
params[:user].permit(:email, :password, :password_confirmation, :current_password, :username, :full_name)
end
The key is in this "user_params[:password].blank?". The next is a example of the code:
def update
if user_params[:password].blank?
params = user_params_without_password
else
params = user_params
end
respond_to do |format|
if #user.update(params)
format.html { redirect_to #user, notice: t(:user_update) }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
private
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:email, :username, :first_name, :last_name, :admin, :locked, :password)
end
def user_params_without_password
params.require(:user).permit(:email, :username, :first_name, :last_name, :admin, :locked)
end
Hope you help
I went round in circles on this for ages. The answers are all in validatable as suggested by mrstif above. If you use the validatable module Devise works out of the box (with configuration options) allowing you to update user details without supplying a password so be very careful about rolling your own password validations.
simply override the Devise by creating app/controller/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
resource.update(params.except(:current_password))
end
end
this code will directly update user params except :current_password
and update config/routes.rb
devise_for :users, controllers: {registrations: 'registrations'}
My goal was to enable editing user attributes without requiring a password, unless it's changing email, password or deleting the account. And here's what worked for me:
app/controllers/registrations_controller.rb:
class RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters
...
def update
params[:user][:team_attributes][:id] = current_user.team.id
account_update_params = devise_parameter_sanitizer.sanitize(:account_update)
if password_required?
successfully_updated = resource.update_with_password(account_update_params)
else
account_update_params.delete(:current_password)
successfully_updated = resource.update_without_password(account_update_params)
end
if successfully_updated
sign_in resource, bypass: true
redirect_to '/'
else
render :edit
end
end
def destroy
current_password = devise_parameter_sanitizer.sanitize(:account_update)[:current_password]
resource.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
error_messages = 'Current password ' + resource.errors[:current_password].join
if resource.destroy_with_password(current_password)
redirect_to '/'
else
redirect_to delete_account_path, notice: error_messages
end
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:account_update) do |user_params|
user_params.permit(:username, :email, :password, :password_confirmation, :current_password, :name, :phone_number
end
end
private
def password_required?
(resource.email != params[:user][:email] if params[:user][:email].present?) || params[:user][:password].present?
end
end
Update config/routes.rb:
devise_for :users, controllers: { registrations: 'registrations' }
In views/devise/registrations/edit.html.haml
# edit form
...
= simple_nested_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: 'mo-form' }, defaults: { placeholder: false, hint: false }) do |f|
...
# delete form
...
= simple_form_for(resource, as: resource_name, url: user_registration_path(resource_name), method: :delete, html: { class: 'mo-form' }, defaults: { placeholder: false, hint: false }) do |f|
...
I made use in that case of the method update_columns.
It avoids having to bypass password validation for instance.
#user.update_columns(user_params)
However, this method will not modify the timestamp of the updated_at field and this also needs to be addressed, either by merging { updated_at: Time.current } or by a touch.
Related
I'm building a blog that includes tiered admin permissions. When I'm signed in using our seeded admin account I can see the index just fine. When I go to the page not signed in, something that should be filtered by the before_action I get an error saying undefined method `admin=' for nil:NilClass in my index view.
When I'm not signed in, current_user should be nil and the if statement in the view should resolve then as false. Code snippets for reference are below.
Here's the view:
<div id="blog">
<%= render 'blogs/blog_header' %>
<div class="messages">
<%= render 'layouts/flash_messages' %>
</div>
<%= will_paginate #bloggers %>
<% #bloggers.each do |blogger| %>
<div class="post_wrapper">
<h2 class="title"><%= blogger.name %></h2>
<p class="date_and_author" style="display:inline"><%= blogger.email %></p>
<% if current_blogger.admin = 1 %>
<p class="date_and_author" style="display:inline">||</p>
<button class="button" style="display:inline"><%= link_to "delete", blogger, method: :delete,
data: { confirm: "You sure?" } %></button>
<% end %>
</div>
<% end %>
<%= will_paginate %>
The controller:
class BloggersController < ApplicationController
before_action :signed_in_blogger, only: [:show, :edit, :update, :destroy]
# GET /bloggers
# GET /bloggers.json
def index
#bloggers = Blogger.paginate(page: params[:page], per_page: 20)
end
# GET /bloggers/1
# GET /bloggers/1.json
def show
#blogger = Blogger.find(params[:id])
end
# GET /bloggers/new
def new
#blogger = Blogger.new
end
# GET /bloggers/1/edit
def edit
end
# POST /bloggers
# POST /bloggers.json
def create
#blogger = Blogger.new(blogger_params)
if #blogger.save
blog_sign_in #Blogger
flash.now[:success] = "New account successfully created."
redirect_to "/blogs"
else
render 'new'
end
end
# PATCH/PUT /bloggers/1
# PATCH/PUT /bloggers/1.json
def update
respond_to do |format|
if #blogger.update(blogger_params)
format.html { redirect_to #blogger, notice: 'User was successfully updated.' }
format.json { render :show, status: :ok, location: #blogger }
else
format.html { render :edit }
format.json { render json: #blogger.errors, status: :unprocessable_entity }
end
end
end
# DELETE /bloggers/1
# DELETE /bloggers/1.json
def destroy
Blogger.find(params[:id]).destroy
respond_to do |format|
format.html { redirect_to bloggers_url, notice: 'User was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def blogger_params
params.require(:blogger).permit(:name, :email, :password, :admin, :password_confirmation)
end
# Confirms a signed-in user
def signed_in_blogger
unless blog_signed_in?
flash[:error] = "Please sign in."
redirect_to blog_signin_path
end
end
end
And the helper:
# Logs in the given blogger
def blog_sign_in(blogger)
session[:blogger_id] = blogger.id
end
# Returns the current logged-in blogger (if any)
def current_blogger
#current_blogger ||= Blogger.find_by(id: session[:blogger_id])
end
# Returns true if the blogger is logged in, false otherwise.
def blog_signed_in?
!current_blogger.nil?
end
def blog_sign_out
session[:blogger_id] = nil
#current_blogger = nil
end
Could this be somewhat related to a separate error in which when trying to create a new blogger, after hitting create, I get "undefined method id for nil:Class?" Any help would be greatly appreciated.
Pasting blogger model per request:
class Blogger < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :admin, :remember_token
has_secure_password
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: true
validates :password, length: { minimum: 5 }
validates :password_confirmation, presence: true
def Blogger.new_remember_token
SecureRandom.urlsafe_base64
end
def Blogger.digest(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = Blogger.digest(Blogger.new_remember_token)
end
end
If you're not signed in and current_blogger is nil as you say, admin won't be an available method. To avoid this error, change your if statement in the view to something like this.
<% if current_blogger && current_blogger.admin == 1 %>
You could also use the try method, which will also work
<% if current_blogger.try(:admin) == 1 %>
I am currently unable to Sign Up or Sign In a user through my application and I'm unable to figure out why. I am using nested parameters with my User/Profile models.
When I try to sign up a new user, I get proper flash message saying
"Invalid email or password".
I created my authentication from scratch (not using Devise!). I also have a 'forgot password'/'remember me' feature but I have not displayed that information below as I think it is irrelevant.
Here is the console log (it seems to be a rollback, but no specific error given):
{Parameters: {"utf8"=>"✓", "authenticity_token"=>"Ek4cgnR3FQePCg/A4Wqc3atinU+WwRNgj+5hpXsd4mY=", "user"=>{"email"=>"sign#up.co", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "profile_attributes"=>{"first_name"=>"101001010", "last_name"=>"10101110101", "linkedin"=>"www.linkedin.com/signup", "twitter"=>"www.twitter.com/signup"}}, "commit"=>"Sign Up"}
(0.1ms) begin transaction
User Exists (0.1ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'sign#up.co' LIMIT 1
(0.1ms) rollback transaction
Rendered users/new.html.haml within layouts/application (60.3ms)
Completed 200 OK in 158ms (Views: 75.9ms | ActiveRecord: 0.3ms)}
models/user.rb
class User < ActiveRecord::Base
...
has_one :profile
accepts_nested_attributes_for :profile
delegate :full_name, to: :profile
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
VALID_URL_REGEX = /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9] {1,5})?(\/.*)?$/ix
validates :email, :uniqueness => true, :presence => true
validates :first_name, :presence => true, length: {minimum: 1}
validates :last_name, :presence => true, length: {minimum: 1}
validates :twitter, :format => { with: VALID_URL_REGEX, :multiline => true}
validates :linkedin, :format => { with: VALID_URL_REGEX, :multiline => true}
models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
def full_name
if first_name || last_name
"#{first_name} #{last_name}".squeeze(" ").strip
end
end
end
controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.build_profile
end
def create
#user = User.new(user_attributes)
if #user.save && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: "Thanks for signing in"
else
flash.now.alert = "Invalid email or password"
render :new
end
end
private
def user_attributes
params.require(:user).permit(:email, :password,
:password_confirmation,
{profile_attributes: [:first_name, :last_name, :linkedin, :twitter]})
end
end
controllers/profiles_controller.rb
class ProfilesController < ApplicationController
before_action :find_profile
def show
end
def update
if #profile.update_attributes(profile_params)
redirect_to posts_path
end
end
def edit
end
private
def profile_params
params.require(:profile).permit(:first_name, :last_name, :linkedin, :twitter)
end
def find_profile
#profile = current_user.profile
end
end
controllers/sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.find_by_email(params[:email])
respond_to do |format|
if user && user.authenticate(params[:password])
if params[:remember_me]
cookies.permanent[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
format.html { redirect_to root_path, notice: "You're logged in!" }
format.js do
flash.now.notice = "You're signed in!"
render
end
else
flash.now.alert = "Email or password is invalid"
format.html { render :new }
format.js { render :new }
end
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Logged out!"
end
end
Here are the views (haml)
views/user/new.html.haml
%h1 Sign Up for a New Account
= simple_form_for #user, html: { class: 'form-horizontal' } do |f|
.well
.container
= f.input :email, html_input: {class: "form-control"}
= f.input :password
= f.input :password_confirmation
= f.fields_for :profile do |p|
= p.input :first_name
= p.input :last_name
= p.input :linkedin
= p.input :twitter
= label_tag :remember_me
= check_box_tag :remember_me, 1, params[:remember_me]
= f.submit "Sign Up", class: "btn btn-primary"
**views/sessions/new.html.haml
%h1 Sign In
= simple_form_for "", url: sessions_path, html: {class: "form-horizontal"} do |f|
= f.input :email
= f.input :password
= f.submit "Sign In", class: "btn btn-primary"
%br
%p
=link_to "Forgot your password?", new_password_reset_path
%p
No account, yet?
= link_to "Sign up", signup_path
This problem has been bugging me for quite some time. I'd like to test out some of the user functionality but I cannot do so as I'm unable to login. Right now there is only one User record in the database and I created that manually in console. I am not able to log in to that user record either.
Any help is greatly appreciated.
def create
#user = User.new(user_attributes)
if #user.save && user.authenticate(params[:password])
session[:user_id] = user.id
...
end
In this code you create an instance variable (#user), but you call authenticate on user. You should either use User.authenticate or #user.authenticate (depending on how you implemented the authenticate method in your model). You should also change session[:user_id] = user.id to session[:user_id] = #user.id.
I have these validation rules:
class Employee < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
before_save { self.email = email.downcase }
end
Here is debug(params)
--- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: rqWNFu2/jeoZfTTZD5N6M080UcazPZR2hl0ON92sxnA=
employee: !ruby/hash:ActionController::Parameters
name: xxx
email: yyyy#zzzz.com
password: '123456'
password_confirmation: '123456'
commit: Create Employee
action: create
controller: employees
But the validation still fails saying:
Email can't be blank
Email is invalid
Password can't be blank
Password is too short (minimum is 6 characters)
edit: controller code
def create
#employee = Employee.new(employee_params)
respond_to do |format|
if #employee.save
format.html { redirect_to #employee, notice: 'Employee was successfully created.' }
format.json { render action: 'show', status: :created, location: #employee }
else
format.html { render action: 'new' }
format.json { render json: #employee.errors, status: :unprocessable_entity }
end
end
end
Anyone can explain to me what i am doing wrong? The params seems to be fine and filled correctly but i still can't pass the validation
The problem is caused by incorrect use of Strong Parameters. Declaring employee_params the following way should fix it:
private
def employee_params
params.require(:employee).permit(:name, :email, :password, :password_confirmation)
end
With Rails4 Strong Parameters concept, you must permit the parameters that you would like to insert(create action)/update(update action) in database explicitly else they won't pass through.
Permit email and password in employee_params method.
employee_params should look as below:
def employee_params
params.require(:employee).permit(:email, :password, :password_confirmation, :name)
end
After installing Bcrypt on my Rails app, there is a validation problem :password=>"Can't be blank", even though form is filled out:
This is my User model
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :username, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
end
I've tried in the rails c and now the digest appears:
User.create:
User.create(username: "Riprova", email:"testato#gmail.com", password: "nonfunzia", password_confirmation:"nonfunzia")
<User id: 15, username: "Riprova", name: nil, surname: nil, email: "testato#gmail.com", gender: nil, birth: nil, created_at: "2013-08-11 15:35:03", updated_at: "2013-08-11 15:35:03", password_digest: "$2a$10$Q/5qtZYDXRcFsUWgve3JL.wui4hSHLhGgsuO0C6TTkBY...">
User controller:
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
# GET /users.json
def index
#users = User.all
end
# GET /users/1
# GET /users/1.json
def show
end
# GET /users/new
def new
#user = User.new
end
# GET /users/1/edit
def edit
end
# POST /users
# POST /users.json
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render action: 'show', status: :created, location: #user }
else
format.html { render action: 'new' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /users/1
# PATCH/PUT /users/1.json
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# DELETE /users/1
# DELETE /users/1.json
def destroy
#user.destroy
respond_to do |format|
format.html { redirect_to users_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:username, :name, :surname, :email, :bids_left, :bids_left_free, :gender, :birth)
end
end
If you're using Rails 3.x you need to add attr_accessible :password or that parameter will be disallowed. On Rails 4, see strong parameters. The password param is probably being filtered out. Others (like username) probably as well.
I am creating first rails app and had everything working yesterday. I created association on my view today for players to pull to team page with has_many and belongs_to. Now I can not create a new player as it keeps giving me an ActiveModel::ForbiddenAttributesError in PlayersController#create message.
Extracted source (around line #27):
def create
#player = Player.create(params[:player])
respond_to do |format|
if #player.save
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"uw5w2sOgNF6y3+Jv6kvTj3X/dV2+PAVo2/OyHinIirY=",
"player"=>{"first_name"=>"test",
"last_name"=>"",
"address_1"=>"",
"address_2"=>"",
"city"=>"",
"state"=>"",
"zip"=>"",
"phone_number"=>"",
"email"=>"",
"birthday"=>"",
"position"=>"",
"bio"=>"",
"team"=>"",
"team_id"=>"1",
"number"=>""},
"commit"=>"Create Player"}
My players controller for create is:
def create
#player = Player.new(player_params)
respond_to do |format|
if #player.save
format.html { redirect_to #player, notice: 'Player was successfully created.' }
format.json { render action: 'show', status: :created, location: #player }
else
format.html { render action: 'new' }
format.json { render json: #player.errors, status: :unprocessable_entity }
end
end
end
You are supposed to have this player_params method in your controller, and use it to pass the params to your model actions (create, update)
class PlayersController
...
def create
#player = Player.create(player_params)
...
end
private
def player_params
allow = [:first_name, :last_name, :address_1, :address_2, :city, :state, :zip, :phone_number, :email, :birthday, :position, :bio, :team, :team_id, :number]
params.require(:player).permit(allow)
end
end
I've been going crazy searching for a solution for this, when I already had applied the params.require bit. So for people who are using cancan, you need to add this part to the ApplicationController:
See: https://github.com/ryanb/cancan/issues/571
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
class PlayersController
...
def create
#player = Player.new(player_params)
...
end
private
def player_params
params.require(:player).permit(:first_name, :last_name, :address_1, :address_2, :city, :state, :zip, :phone_number, :email, :birthday, :position, :bio, :team, :team_id, :number)
end
end
If you're using Rails 4 and you've added player_params method, don't forget to change params[:player] in your create method to just player_params.