My User model has an attribute called "points" and when I try to update it in another model controller (decrementing points), the attribute will not save (even after adding it to attr_accessible).
The method in my Venture Controller code:
def upvote
#venture = Venture.find(params[:id])
if current_user.points < UPVOTE_AMOUNT
flash[:error] = "Not enough points!"
else
flash[:success] = "Vote submitted!"
current_user.vote_for(#venture)
decremented = current_user.points - UPVOTE_AMOUNT
current_user.points = decremented
current_user.save
redirect_to :back
end
I have even tried using the update_attributes method, but to no avail.
I added a quick little test with flash to see if it was saving:
if current_user.save
flash[:success] = "Yay"
else
flash[:error] = "No"
end
and the error was returned.
current_user comes from my Sessions helper:
def current_user
#current_user ||= user_from_remember_token
end
Thanks ahead of time.
My User model:
class User < ActiveRecord::Base
attr_accessor :password, :points
attr_accessible :name, :email, :password, :password_confirmation, :points
STARTING_POINTS = 50
acts_as_voter
has_karma :ventures
has_many :ventures, :dependent => :destroy
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
after_initialize :initialize_points
def has_password?(submitted_password)
password_digest == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
private
def initialize_points
self.points = STARTING_POINTS
end
def encrypt_password
self.salt = make_salt if new_record?
self.password_digest = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
This is what I get after printing <%= debug current_user %>
--- !ruby/object:User
attributes:
id: 1
name: Test User
email: a#s.com
created_at: 2011-08-27 21:03:01.391918
updated_at: 2011-08-27 21:03:01.418370
password_digest: 40d5ed415df384adaa5182a5fe59964625f9e65a688bb3cc9e30b4eef2a0614b
salt: ac7a332f5d63bc6ad0f61ceacb66bc154e1cad1164fcaed6189d8cea2b55ffe4
admin: t
points: 50
longitude_user:
latitude_user:
attributes_cache: {}
changed_attributes: {}
destroyed: false
errors: !omap []
marked_for_destruction: false
new_record: false
points: 50
previously_changed: {}
readonly: false
You are requiring the user's password to be present any time the user is saved. When the upvote is submitting, the password is not present, therefor validation is not passing.
This would suggest some kind of validation failed. An easy way to circumvent this, is to use update_attribute. This will update a single attribute and save without running the validations.
So instead write
current_user.update_attribute :points, current_user.points - UPVOTE_AMOUNT
This should work.
This does not solve the problem why saving an existing user could fail, so you still need to check your validations and before_save actions.
Hope this helps.
Ha. Indeed. The update_attribute does skip validations, but not the before_save.
So, if the before_save is the problem, you only want to trigger if the password has changed, so you could do something like
def encrypt_password
self.salt = make_salt if new_record?
self.password_digest = encrypt(password) if self.password_changed?
end
But this would only work if password is an actual attribute of your model, which seems unlikely. Why would you store the hash (for safety reasons) and the password in cleartext. So ... I guess you only have a password_digest field, and then it should become something like:
def encrypt_password
self.salt = make_salt if new_record?
self.password_digest = encrypt(password) if password.present?
end
Only if a password was given, try to recreate the digest.
Related
The User.authenticate method is returning nil even though the user exists in the database with the correct email and password. This happens when calling the authenticate method from the Create action in the Sessions controller or from the Rails Console (irb).
Any help with this problem will be greatly appreciated.
class SessionsController < ApplicationController
def new
end
def create
user = User.authenticate(params[:session][:email],
params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination"
render 'new'
else
sign_in user
redirect_to user
end
end
def destroy
sign_out
render 'pages/options'
end
end
Here is my User model:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :first_name, :last_name, :email, :password, :password_confirmation,
:account_type, :email_confirmed, :weight
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
private #################################################
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
def generate_email_conf_code
email_conf_code = secure_hash("#{Time.now.utc}")
self.email_conf_code = email_conf_code
end
end
Try to check your server log. You can also monitor them directly on the terminal. Look for the session email received on the server. It looks something like this on Rails 3.1.1
Parameters: {"session"=>{"email"=>"xxx#yyy.com", "password"=>"[FILTERED]"}}
See that you're getting the email correctly. If not, I guess you know what to do.
does your database store a password_digest or encrypted_password column? Older tutorials by michael hartl used password_digest, now they seem they are encrypted_password.
I have a problem and I need an idea how to fix my update method. I have an admin panel where I can create users. This form include name, mail, password, repeated password fields and it works fine. Then I want to have a list of all users and to edit these who I want. The problem is that I want to edit part of the information which is not included in the form of the registration and default is empty. In edit mode my form has two new fields - notes and absences. When I change these fields and call update method I see message that password and repeated password don't match which is validation in the registration but I do not have these files in edit mode. How could I fix this problem. This is part of my code:
class UsersController < ApplicationController
def edit
#user = User.find(params[:id])
#title = "Edit user"
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated."
redirect_to #user
else
#title = "Edit user"
render 'edit'
end
end
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => true
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
private
def encrypt_password
self.salt = make_salt unless has_password?(password)
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
The validation for password presence is true when you are creating a user, but once a user has an encrypted password, you don't want to force it to be present in all form submissions in the future.
Active record supports adding conditions to validations, so I would suggest putting a condition on the password validation to make it only execute if the user object does not already have an encrypted password. The relevant snippet would be:
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 },
:if => :needs_password?
def needs_password?
encrypted_password.nil?
end
Good day! I'm practising materials from "Ruby on Rails Tutorial" by Michael Hartle.
Below is the failure message I received, even though the "expected" and "got" seems to match. Would you please give me some suggestion to see how I should approach this issue?
Thank you so much!
Below is the implementation code:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :emp_id, :dept_id, :password, :password_confirmation
validates :emp_id, :presence => true
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(emp_id, submitted_password)
user = find_by_emp_id(emp_id)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
Below is the SPEC code:
require 'spec_helper'
describe User do
before(:each) do
#attr = {:name=>"Example", :dept_id=>01, :emp_id=>10, :password=>"pwdabcd", :password_confirmation => "pwdabcd" }
end
.
.
.
describe "password encryption" do
before(:each) do
#user = User.create!(#attr)
end
.
.
.
describe "authenticate method" do
it "should return the user on emp_id password match" do
matching_user = User.authenticate(#attr[:emp_id], #attr[:password])
matching_user.should == #user
end
end
end
end
Thank you so much for your kind assistance.
Have a nice day!
Kevin - when you see a failure message like that, the representation of the object (#<User ...>) is up to the object, so it's possible that it doesn't show you everything that is being compared by ==. My guess is it has something to do with :password_confirmation, but I'm not certain. It doesn't look like the implementation is using it yet, so try removing password_confirmation from #attr in the spec, and from the attr_accessible declaration, and see if it passes.
I'm very new to rails and I'm trying to accomplish the following authentication issue:
User makes a comment or grants "absolution" (similar to comment) and he gets some coins for it. Coins is the virtual currency in my app and is also a column in the users table.
Because of your kind help, I was already capable to update the coins value after writing a comment or grant absolution. However, when I write a comment and log out after that, my login name or password gets changed(?)...I can't login anymore with this account.
This is how my User model looks like:
require 'digest'
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation, :twitter_url, :homepage_url, :coins
has_many :comments, :dependent => :destroy
has_many :absolutions, :dependent => :destroy
has_many :ratings
has_many :rated_sins, :through => :ratings, :source => :sins
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
homepage_regex = /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$)/ix
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :twitter_url, :format => { :with => homepage_regex }
validates :homepage_url, :format => { :with => homepage_regex }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
class << self
def authenticate(email, submitted_password)
user = find_by_email(email)
(user && user.has_password?(submitted_password)) ? user : nil
end
def authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
And this is my comments controller:
class CommentsController < ApplicationController
before_filter :authenticate, :only => [:create, :destroy]
def new
#comment = Comment.new
end
def create
#sin = Sin.find(params[:sin_id])
#comment = current_user.comments.build(params[:comment])
#comment.sin_id = #sin.id
if #comment.save
flash[:success] = "Comment created! Earned 20 coins."
coins_new = current_user.coins.to_i + 20
current_user.update_attribute(:coins, coins_new)
redirect_to sin_path(#sin)
else
flash[:error] = "Comment should have 1 - 1000 chars."
redirect_to sin_path(#sin)
end
end
def destroy
end
private
def authenticate
deny_access unless signed_in?
end
end
I assume, that it has something to do with the before_save encrypt_password method, but its only a guess. I really appreciate your help and suggestions!
Edit:
It gets warmer...It has something to do with the following line in the Comments Controller:
current_user.update_attribute(:coins, coins_new)
When he updates the :coins column, something seems to go wrong. If you need further info, just drop a comment. Thanks for your help!
Your problem is that you're encrypting the already encrypted password in your "encrypt_password" method.
So, when the user is a new_record?, you're taking the password (say it's "cat"), hashing it, and storing it in the database.
So, what gets stored is a cryptographic hash of "cat", which we'll say is "dog"
Then, the next time you're saving the user record, you're taking the hashed password ("dog") in line #2 of your "encrypt_password" method, and hashing it again, which we'll say generates "kangaroo".
Next time you log in, your app is hashing the password you enter into the login form "cat", hashing it to "dog", and comparing it to the hashed version in the database, "kangaroo". Oh, but "dog" doesn't match "kangaroo", so the login fails.
So change:
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
To either:
def encrypt_password
self.salt = make_salt if new_record?
self.password = decrypt(password) # decrypt it first to the plain text
self.encrypted_password = encrypt(password) # then re-encrypt the plain text with the salt
end
Or:
def encrypt_password
self.salt = make_salt if new_record?
if (password_has_changed?) # somehow you'll have to figure this out
self.encrypted_password = encrypt(password)
end
I am extrapolating from a User model given in the Rails tutorial found here to learn more about creating models. I am trying to give a user a confirmation flag, which is initially set false until the user confirms their identity through clicking a link in an automated email sent after registration.
Everything worked before I added the confirmed attribute. I have added a confirmed column to the database through a migration, so it seems to me the error happens somewhere in the before_save :confirmed_false logic.
Can someone help me? The user model is below.
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
before_save :confirmed_false
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
private
def confirmed_false
self.confirmed = false if new_record?
end
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
1,1 Top
In your migration, if you set the confirmed column to be a boolean and the default value to be false then you don't need the before_save :confirmed_false callback at all as it will always be false when it's a new record.
Updated
class User < ActiveRecord::Base
# unlike before_save it's only run once (on creation)
before_create :set_registration_date
def set_registration_date
registration_date = Time.now # or Date.today
end
end
Can't really figure out what you're trying to do here. It seems like you want to set the default to be confirmed = false, then change it to confirmed = true if the user clicks on the appropriate link and sends you the correct token, or something like that.
So the flow would be something like this:
A user record is created with confirmed = false
There is no need for a before_filter do do anything yet
A user does some action that permits his confirmed column to be set to true
Still no need for a before_filter
What's the before_filter for? Are you trying to use it to set a default?