I watched the RailCasts tutorial #274 on Remember Me and Reset Password. The code he adds is the following inside user.rb
def send_password_reset
generate_token(:password_reset_token)
save!
UserMailer.password_reset(self).deliver
end
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
Here what I don't understand is why the save! call inside send_password_reset? Also, I'm not familiar with the syntax in generate_token: self[column]=. Is this the way to set a column inside a database table?
Here's the create action of the password_resets_controller
def create
user = User.find_by_email(params[:email])
user.send_password_reset if user
redirect_to root_path, notice: "Email sent with password reset instructions."
end
save! saves the object and raises an exception if it fails.
self[column]=, is a slight meta-programming.
Usually, when you know the column name, you'd do: self.password_reset_token=. Which is the same as self[:password_reset_token]= or self["password_reset_token"]=.
So it's easy to abstract it a bit passing column name as string/symbol.
Clearer?
1) save! is like save, but raise a RecordInvalid exception instead of returning false if the record is not valid.
Example from my console:
User.new().save # => false
User.new().save! # ActiveRecord::RecordInvalid: Validation failed: Password can't be blank, Email can't be blank
2) self[column]= there for setting users column.
Related
I have an application which uses Devise as authentication. I don't want user to be able to changed there email address. I've done this by setting the email attribute to read only in the User model.
class User < ActiveRecord::Base
attr_readonly :email
end
This works fine: Rails rollback the transaction. Devise however thinks that the update was succesful and displays a message succesfull message.
"Your account has been updated successfully."
I've tried several things including creating my onw methode that would return a flash notice but it keeps saying that the account has been succesfully updated.
Is there a way to raise an error when the record is not saved succesfully?
Edit after Ashvin's anwser. This is what I have in my model:
def email=(address)
begin
if new_record?
write_attribute(:email, address)
end
rescue Exception => error
flash[:alert] = error.message
end
end
I dont know I got your question or not, But from what I get following is solution. You can use exception handling while record is rollback
begin
# do some stuff here
rescue Exception => e
flash[:notice] = e.message
end
I'm trying to build a controller action which will create a new user in my application. I want it to attempt to create the user and if it succeeds, return the User model and if it fails show the validation errors. So far I've got this mostly up and running:
# app/controllers/v1/users_controller.rb
def create
#user = User.create(user_params)
end
private
def user_params
params.require(:user).permit(:email, :password) if params[:user]
end
and I'm using JBuilder in my view like so:
# app/views/v1/users/create.json.jbuilder
if #user.errors
json.errors #user.errors
else
json.user #user
end
This looks like it'd work to me, however when I create a new user that doesn't have any validation errors I get this output rather than the User that just got created:
{"errors":{}}
How come it's printing out the empty errors when the user has no validation errors?
In Ruby, only nil and false evaluate to false in the if statement. In your example above, #user.errors is an ActiveModel::Errors class and therefore evaluate to true. Try changing the code above to:
if #user.errors.present?
json.errors #user.errors
else
json.user #user
end
In my Rails app I have an update action that users can use to update their profile.
The tricky thing I want to achieve is that if a user enters a new email address and saves it, that email address won't get saved to the email database field straightaway, but rather to a database field called new_email. The field email should remain untouched (at least until the user has confirmed that email address later on).
def update
current_email = #user.email
new_email = params[:user][:email].downcase.to_s
if #user.update_attributes(params[:user])
if new_email != current_email
#user.change_email(current_email, new_email)
flash[:success] = "Profile updated. Please confirm your new email by clicking on the link that we've sent you."
else
flash[:success] = "Profile updated."
end
redirect_to edit_user_path(#user)
else
render :edit
end
end
User model:
def change_email(old_email, new_email)
self.new_email = new_email.downcase
self.email = old_email
self.send_email_confirmation_link
end
The function above kind of works but is hard to test and doesn't feel right. Is there a smoother way to achieve this?
Thanks for any help.
If you change your form so that you're updating new_email, you can just put it all in a simple after_update hook.
after_update :check_new_email
private
def check_new_email
send_email_confirmation_link if new_email_changed?
end
I think you could use "virtual" attribute, called - let's say - email_input and show field for this attribute (instead of email) in view:
<%= f.text_field :email_input %>
Then in your model you should have:
class User < ActiveRecord::Base
attr_accessor :email_input
attr_accessible :email_input
before_save :set_email, :if => lambda{|p| p.email_input.present?}
# ...
def set_email
email_input.downcase!
if new_record?
self.email = email_input
else
self.new_email = email_input
send_email_confirmation_link
end
end
end
I have a member model with a reset_token method (which assigns a user a new token in order to send them an email to reset their password). But update_attribute never saves anything in the database. I have :new_password_token assigned to attr_accessible and attr_accessor. The log picks up the salt and token but it always returns nil
def self.reset_token(email)
member = find_by_email(email)
if member
#Reset token, and then send email
salt = BCrypt::Engine.generate_salt
logger.error "Salt is #{salt}"
token = BCrypt::Engine.hash_secret(email, salt)
logger.error "token is #{token}"
if member.update_attribute(:new_password_token, token)
member
end
end
nil
end
Controller method in which it is called:
def reset_password
#member = Member.reset_token(params[:email])
if #member
redirect_to(root_url, :notice => "Please check your email for instructions")
else
redirect_to(root_url, :notice => "Sorry we have no record of your account")
end
end
Try removing attr_accessor from your model. attr_accessor is creating reader and writer methods for new_password_token. The writer method is equivalent to:
def new_password_token=(new_password_token)
#new_password_token = new_password_token
end
So when you update_attribute it is just setting an instance variable on your object and bypassing the database altogether.
I'm adding a password reset feature to my Rails application that uses Authlogic. I was following the guide here: http://www.binarylogic.com/2008/11/16/tutorial-reset-passwords-with-authlogic/ and everything works as I'd like except for one thing: the password reset form accepts blank passwords and simply doesn't change them.
I've been searching around, and have learned that this is the intended default behavior because it allows you to make user edit forms that only change the user's password if they enter a new one, and ignore it otherwise. But in this case, I specifically want to enforce validation of the password like when a user initially registers. I've found two possible solutions for this problem but haven't been able to figure out how to implement either of them.
1) Someone asked this same question on Google Groups:
User model saves with blank password
Ben's response was to use #user.validate_password = true to force validation of the password. I tried this but I get an undefined method error: undefined method 'validate_password_field=' for #<User>.
2) There seems to be an Authlogic configuration option called ignore_blank_passwords. It is documented here:
Module: Authlogic::ActsAsAuthentic::Password::Config#ignore_blank_passwords
This looks like it would work, but my understanding is that this is a global configuration option that you use in your initial acts_as_authentic call in the User model, and I don't want to change it application-wide, as I do have a regular edit form for users where I want blank passwords to be ignored by default.
Anyone found a solution to this? I see validate_password= in the change log for Authlogic 1.4.1 and nothing about it having been removed since then. Am I simply using it incorrectly? Is there a way to use ignore_blank_passwords on a per-request basis?
This is kind of an old thread, but since it is unanswered I'll post this.
I've managed to do it a bit more cleanly than the other solutions, "helping" authlogic validations with my own.
I added this to user:
class User < ActiveRecord::Base
...
attr_writer :password_required
validates_presence_of :password, :if => :password_required?
def password_required?
#password_required
end
...
end
You can reduce it to two lines by making an attr_accessor and using :if => :password_required (no interrogation), but I prefer this other syntax with the interrogation sign.
Then your controller action can be done like this:
def update
#user.password = params[:user][:password]
#user.password_confirmation = params[:user][: password_confirmation]
#user.password_required = true
if #user.save
flash[:notice] = "Password successfully updated"
redirect_to account_url
else
render :action => :edit
end
end
This will have a local effect; the rest of the application will not be affected (unless password_required is set to true in other places, that is).
I hope it helps.
This what I did.
class User < ActiveRecord::Base
attr_accessor :ignore_blank_passwords
# object level attribute overrides the config level
# attribute
def ignore_blank_passwords?
ignore_blank_passwords.nil? ? super : (ignore_blank_passwords == true)
end
end
Now in your controller, set the ignore_blank_passwords attribute to false.
user.ignore_blank_passwords = false
Here, you are working within the confines of AuthLogic. You don't have to change the validation logic.
User.ignore_blank_passwords = false
Use model, not object for setting this property.
def update_passwords
User.ignore_blank_passwords = false
if #user.update_attributes(params[:user])
...
end
User.ignore_blank_passwords = true
end
Maybe test the value of the parameter in the controller? (air code):
def update
#user.password = params[:user][:password]
#user.password_confirmation = params[:user][: password_confirmation]
if #user.password.blank?
flash[:error] = "Password cannot be blank"
render :action => :edit
return
end
if #user.save
flash[:notice] = "Password successfully updated"
redirect_to account_url
else
render :action => :edit
end
end
Apart from zetetic's solution you could do it this way:
def update
#user.password = params[:user][:password]
#user.password_confirmation = params[:user][: password_confirmation]
if #user.changed? && #user.save
flash[:notice] = "Password successfully updated"
redirect_to account_url
else
render :action => :edit
end
end
You're basically checking if authlogic changed the user record (which it doesn't if the password is empty). In the else block you can check if the password was blank and add an appropriate error message to the user record or display a flash message.