So I wrote an app before that allowed for the standard way of encrypting a password using this and it worked fine:
before_save :create_hashed_password
Then:
def create_hashed_password
# validation code not shown
self.password = Digest::SHA1.hexdigest(password)
end
The problem is now in this app is that I have other user attributes I want to edit and every time I edit and save, I am hashing the already hashed password, thus making login impossible after updating.
I tested this in irb and it works:
irb(main):008:0> t.password = 'password'
=> "password"
irb(main):009:0> t.password_changed?
=> true
But when I use this line in the before filter:
before_save :create_hashed_password if password_changed?
It fails with the following error:
NoMethodError: undefined method `password_changed?' for User(no database connection):Class
(And before you ask, yes I do have a db connection, it's just with the User model because the before filter is there)
BTW I'm on Rails 4.
Try with:
before_save :create_hashed_password, if: :password_changed?
Short explanation: in your current syntax, the if part is not a param to the before_save method, this is why you need to add the coma, to send it as a parameter. Now it tries to call a class method: User.password_changed?, this doesn't make sense since you need to perform an instance method against a user object.
Try this:
before_save :create_hashed_password, if: Proc.new { &:password_changed? }
Hope this helps, happy coding
Related
I've looking for a way to allow users to change their settings (User model) without having to change their password (they still have to enter their current password). Devise out of the box seems to allow this, but when you remove the validatable module and setup custom validations, it seems you need to work around a bit.
I have setup the following validation in my user model :
validates :password, length: { in: 6..128 }
When signing up, user is required to specify his password (which is what I expect). When updating settings, though, if I leave password blank it raises an error to the user that says password must be at least 6 characters.
How can I work around this without having to change the way Devise works or having to implement a custom controller of some sort ?
Maybe this will look obvious for some, but it took me a while getting this together. After a few hours of trying different solutions and workarounds and looking all over the place, I dove deeper in Rails validations and found a few constructs that, when put together, make this really easy.
All I had to do was setup a validation for the create action and one for the update action and allow blanks on update.
validates :password, length: { in: 6..128 }, on: :create
validates :password, length: { in: 6..128 }, on: :update, allow_blank: true
With that, I'm getting the behaviour I want and it's only two short lines of code.
Additional note :
At first, I had tried this way :
validates :password, length: { in: 6..128 }, on: :create
This is wrong because it would skip the validation entirely on updates. Users would then be able to set short/long (or blank?) passwords when updating settings.
I tried Amal Kumar S solution, but unfortunately it didn't help me to solve the same issue, so here is modified version of solution tested in real project.
Here is the code from devise/models/validatable.rb module
protected
# Checks whether a password is needed or not. For validations only.
# Passwords are always required if it's a new record, or if the password
# or confirmation are being set somewhere.
def password_required?
!persisted? || !password.nil? || !password_confirmation.nil?
end
The validation fails when you leave blank password and password confirmation fields while updating user info, because this condition is always false:
!password.nil? || !password_confirmation.nil?
Password and password_confirmation fields equals blank string '' which is never equals to nil. So you can fix that by overriding password_required? method in your user model and change nil? check to blank? check.
protected
def password_required?
!persisted? || !password.blank? || !password_confirmation.blank?
end
Think that it's easiest way to fix that and it's doesn't ruin original logic. Maybe it's devise's bug.
Add this code to your user model.
private
def password_required?
new_record? ? super : false
end
Devise has its own method to achieve that, update_without_password, It updates record attributes without asking for the current password. Never allows a change to the current password.
Note. If you are using this method, you should probably override this method to protect other attributes you would not like to be updated without a password.
Example:
def update_without_password(params={})
params.delete(:email)
super(params)
end
It is all in the Devise Documentation. http://rdoc.info/gems/devise/index
This may or may not be a devise error, but I think it is.
In testing I tried assigning an integer as the email address. I am getting this error from the 'bowels' of active record save:
=> #<NoMethodError: undefined method `downcase!' for 1:Fixnum>
This is despite the fact that I have this validation on my User < ActiveRecord::Base.
validates :email, format: { with: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i, message: "Invalid email"}
I am guessing that somehow some devise validation or other hook is being called on the email address and blowing up. What can I try next?
Have a look at the Devise sourcecode:
The authenticable module adds before_validation :downcase_keys, which does the following:
def downcase_keys
self.class.case_insensitive_keys.each { |k| apply_to_attribute_or_variable(k, :downcase!) }
end
(see lib/devise/models/authenticatable.rb for reference)
It is used to downcase all case insensitive keys before applying any logic to the models. By default configuration provided by Devise, case_insensitive_keys contains the email field you are setting with a Fixnum.
# Keys that should be case-insensitive.
mattr_accessor :case_insensitive_keys
##case_insensitive_keys = [ :email ]
(see lib/devise.rb for reference)
This way, the validation is executed even before your own validation is checked. Perhaps an additional verification for the variable's type could be added to Devise, but by default you'll only assign a String provided by the login / etc. form to that field.
I hope i could clarify this a bit.
I have the following in my user.rb model:
INVALID_EMAILS = %w(gmail.com hotmail.com)
validates_format_of :email, :without => /#{INVALID_EMAILS.map{|a| Regexp.quote(a)}.join('|')}/, :message => "That email domain won't do.", :on => :create
For various reasons, I want to be able to use this logic in my controller to check an email's input before it is user.created, which is when the above normall runs.
How can I turn the above into a method that I can call in controllers other than user? Possible?
And if is called and returned false I then want to do errors.add so I can let the user know why?
Thanks
Trying:
def validate_email_domain(emailAddy)
INVALID_EMAILS = %w(gmail.com googlemail.com yahoo.com ymail.com rocketmail.com hotmail.com facebook.com)
reg = Regexp.new '/#{INVALID_EMAILS.map{|a| Regexp.quote(a)}.join('|')}/'
self.errors.add('rox', 'Hey, Ruby rox. You have to say it !') unless reg.match attribute
end
Update:
..
Rails.logger.info validate_email_domain(email)
...
def valid_email_domain(emailAddy)
reg = Regexp.new '/#{User::INVALID_EMAILS.map{|a| Regexp.quote(a)}.join("|")}/'
return true if emailAddy.scan(reg).size == 0
end
You cannot assign a constant inside a method, because that would make it "dynamic constant assignment". Instead, define this constant in your model class and then reference it in your controller by using User::INVALID_EMAILS
Okay, if I understand you.
You want to do something like below:
u = User.new
u.email = "jsmith#gmail.com"
if !u.valid?
puts u.errors.to_xml
//do something
return
end
What you do with those errors is going to come down to how you want those reported back, usually I just shoot them back as xml into a flash[:error], which is the normal default behavior if you're doing scaffolds. The puts is there so you can see how to access the errors.
Additional
As a rule try to avoid duplicating validation logic. Rails provides everything you need for validating without creating different methods in different places to accomplish the same thing.
I have a user model that has an authentication method in it.
If I test out using the Model in the rails console can create a user just fine and then I can do a find on the email and return the user perfectly like this.
user = User.find_by_email("someaddress#email.com")
Now if I try to call the authentication method like this and return the user the puts user statement in my authentication method returns nil
user = User.authenticate("someaddress#email.com", "foobar")
The model looks something like this
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :first_name, :last_name,
:email, :birth_date, :sex,
:password, :password_confirmation
def self.authenticate(email, submitted_password)
user = find_by_email(email)
puts user #this returns nil so my class is never able to authenticate the user
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
end
I am at a loss for what the issue is. Some in sight into this issue would be very much appreciated.
The way you are using the find_by method inside the Class method is fine; that should work.
Are you sure that the nil output is from puts? The nil maybe the output of your method. It's possible that user.has_password? has an error in it.
Instead of puts, try:
p user
... just to be sure.
Did you check the email's value before calling find_by_email? Maybe it has an invalid space in it, so check the sql log and copy it to dbconsole.
This method would return nil if either:
There was no user object (unlikely, since find_by_email works in the console)
If has_password? returns false, which is likely.
Check your has_password? method is doing the right thing.
I found the answer guys ... thanks so much for jumping in and suggesting some solution.
I had another method that I didn't include in the code above that was the "real" issue.
As for my original question it turns out that the method was working. I had typo in the string that I was passing to the method. Basically I left the ".com" off the end of the email.
As usually a simple typo makes me feel really dumb for posting the question but over all thinking though the problem and looking at your suggestions helped me find the solution so thanks so much.
I'm having a problem with validation in my RoR Model:
def save
self.accessed = Time.now.to_s
self.modified = accessed
validate_username
super
end
def validate_username
if User.find(:first, :select => :id, :conditions => ["userid = '#{self.userid}'"])
self.errors.add(:userid, "already exists")
end
end
As you can see, I've replaced the Model's save method with my own, calling validate_username before I call the parent .save method. My Problem is, that, even though the error is being added, Rails still tries to insert the new row into the database, even if the user name is a duplicate. What am I doing wrong here?
PS: I'm not using validate_uniqueness_of because of the following issue with case sensitivity: https://rails.lighthouseapp.com/projects/8994/tickets/2503-validates_uniqueness_of-is-horribly-inefficient-in-mysql
Update: I tried weppos solution, and it works, but not quite as I'd like it to. Now, the field gets marked as incorrect, but only if all other fields are correct. What I mean is, if I enter a wrong E-Mail address for example, the email field is marked es faulty, the userid field is not. When I submit a correct email address then, the userid fields gets marked as incorrect. Hope you guys understand what I mean :D
Update2: The data should be validated in a way, that it should not be possible to insert duplicate user ids into the database, case insensitive. The user ids have the format "user-domain", eg. "test-something.net". Unfortunately, validates_uniqueness_of :userid does not work, it tries to insert "test-something.net" into the database even though there already is an "Test-something.net". validate_username was supposed to be my (quick) workaround for this problem, but it didn't work. weppos solution did work, but not quite as I want it to (as explained in my first update).
Haven't figured this out yet... anyone?
Best regards,
x3ro
Why don't you use a callback and leave the save method untouched?
Also, avoid direct SQL value interpolation.
class ... < ActiveRecord::Base
before_save :set_defaults
before_create :validate_username
protected
def set_defaults
self.accessed = Time.now.to_s
self.modified = accessed
end
def validate_username
errors.add(:userid, "already exists") if User.exists?(:userid => self.userid)
errors.empty?
end
end
How about calling super only if validate_username returns true or something similar?
def save
self.accessed = Time.now.to_s
self.modified = accessed
super if validate_username
end
def validate_username
if User.find(:first, :select => :id, :conditions => ["userid = '#{self.userid}'"])
self.errors.add(:userid, "already exists")
return false
end
end
... I think that you could also remove totally the super call. Not sure, but you could test it out.