I'm working on a modified version of Michael Hartl's Learn Rails Tutorial. I'm on chapter 6, modeling users. For some reason, my users aren't creating correctly on ActiveRecord and aren't saving at all.
I placed these users in my seeds.rb file
user_1 = User.create(id: 1, name: 'Han Solo', email: 'han#example.com')
user_2 = User.create(id: 2, name: 'Luke Skywalker', email: 'luke#example.com')
Then I run rails db:seed, but if I go to my rails console, it appears like no users have been created:
Running via Spring preloader in process 24358
Loading development environment (Rails 5.0.0.1)
2.2.2 :001 > User.delete_all
SQL (1.1ms) DELETE FROM "users"
=> 0
2.2.2 :002 >
user.rb User Model
class User < ApplicationRecord
#Ensure Email Uniqueness by Downcasing the Email Attribute
before_save {self.email = email.downcase }
#validates name, presence, and length
validates :name, presence: true, length: { maximum: 100 }
#Validate presence, length, format, and uniqueness (ignoring case)
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: {maximum: 250}, format: {with: VALID_EMAIL_REGEX }, uniqueness: {case_sensitive: false}
#Adds ability to save securely hashed password_digest attribute to database
#Adds a pair of virtual attributes (password and password_confirmation)
#including presence validations upon object creation and a validation
#requiring that they match
#adds authenticate method that returns the user when the password is correct (and false otherwise)
has_secure_password
PASSWORD_FORMAT = /\A
(?=.{8,}) # Must contain 8 or more characters
(?=.*\d) # Must contain a digit
(?=.*[a-z]) # Must contain a lower case character
(?=.*[A-Z]) # Must contain an upper case character
(?=.*[[:^alnum:]]) # Must contain a symbol
/x
validates :password, presence: true, length: {minimum: 8}, format: {with: PASSWORD_FORMAT}
end
schema.rb
ActiveRecord::Schema.define(version: 20161020211218) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
end
end
Anyone have any idea what might be going on?
Ok, I figured it out.
I took out id from the seeds.rb. When I took out the id attribute, it started throwing me the error of the user not having a valid password. I then added a password and password confirmation to conform to password standards set in my user model.
Updated my model and added a password confirmation.
Here's my updated user.rb
class User < ApplicationRecord
#Ensure Email Uniqueness by Downcasing the Email Attribute
before_save {self.email = email.downcase }
#validates name, presence, and length
validates :name, presence: true, length: { maximum: 100 }
#Validate presence, length, format, and uniqueness (ignoring case)
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: {maximum: 250}, format: {with: VALID_EMAIL_REGEX }, uniqueness: {case_sensitive: false}
#Adds ability to save securely hashed password_digest attribute to database
#Adds a pair of virtual attributes (password and password_confirmation)
#including presence validations upon object creation and a validation
#requiring that they match
#adds authenticate method that returns the user when the password is correct (and false otherwise)
has_secure_password
PASSWORD_FORMAT = /\A
(?=.{8,}) # Must contain 8 or more characters
(?=.*\d) # Must contain a digit
(?=.*[a-z]) # Must contain a lower case character
(?=.*[A-Z]) # Must contain an upper case character
(?=.*[[:^alnum:]]) # Must contain a symbol
/x
validates :password, presence: true, length: {minimum: 8}, format: {with: PASSWORD_FORMAT}
validates :password_confirmation, presence: true
end
And seeds.rb
user_1 = User.create!(name: 'Han Solo', email: 'han#example.com', password: 'Password123!', password_confirmation: 'Password123!')
user_2 = User.create!(name: 'Luke Skywalker', email: 'luke#example.com', password: 'Password123!', password_confirmation: 'Password123!')
I tested that the user is being created properly by opening up the rails console, running User.delete_all , and seeing them wiped off the SQL record.
Related
I am testing my user input validation in my application and I am getting two errors in regards to my password presence.
This is what I have written for my model.
class User < ActiveRecord::Base
include Slugifiable
extend Slugifiable::Find
has_secure_password
has_many :posts
validates :email, uniqueness: true, presence: true
validates :username, uniqueness: true, presence: true
validates :password, presence: true
end
Below is my migration table:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.string :username
t.string :password
t.string :password_digest
end
end
end
Each time I run my application with no input it should give me three error messages: "Password can't be blank", "Email can't be blank", "Username can't be blank". Instead I get an extra "Password can't be blank" error. I am using a password_digest variable which is a salted hash of the users password once the data persists in the database.
has_secure_password comes with its own presence validation on the create action. Therefore, validating the presence of password is redundant and is causing you to get two "Password can't be blank" error messages.
Simply remove validates :password, presence: true or add a condition to the validation for a specific controller action/other context...ie
validates :password, presence: true, on: :some_action
I have been following Michael Hartl's Ruby on Rails tutorial book to try and add users to my application. Reading chapter 6, I have added what I believe to be the necessary fields for my user, specifically password and password confirmation via "has_secure_password".
I thought that adding "has_secure_password" to my user model would include the attributes "password" and "password_confirmation" provided I add a "password_digest" to the model. I have done that as the book instructed me to. However, when I run a test, Rails gives me the following error:
Error:
UserTest#test_should_be_valid:
ActiveModel::UnknownAttributeError: unknown attribute 'password' for User.
test/models/user_test.rb:8:in `setup'
I tried this solution and it still gave me the same error, not recognizing the attributes "password" or "password_confirmation". I installed bcrypt using "gem install bcrypt" and included the following in my gem file:
gem 'bcrypt-ruby', :require => 'bcrypt'
I am using Rails 5 and it seems like "has_secure_password" is not supplying the password attributes that I need. Can anyone see what I missed or did wrong that caused "has_secure_password" to not work as intended? Thanks
User Model:
class User < ApplicationRecord
has_many :activities
class User < ActiveRecord::Base
attr_accessor :name, :email, :password, :password_confirmation
has_secure_password
validates :first_name, presence: true, length: {minimum: 1}
validates :last_name, presence: true, length: {minimum: 1}
validates :email, presence: true, uniqueness: true, length: {minimum: 5}
validates :username, presence: true, uniqueness: true, length: {minimum: 1}
validates :password_digest, length: {minimum: 6}
validates :password, :confirmation => true, length: {minimum: 4}
validates :password_confirmation, presence: true
#-----------------------New Stuff ---------------------------------------
acts_as_authentic do |c|
c.crypto_provider = Authlogic::CryptoProviders::Sha512
end
#------------------------------------------------------------------------
#---------------Unsure if working--------------
#validates_presence_of :password, :on => :create
#validates_presence_of :email
#validates_uniqueness_of :email
#----------------------------------------------
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
end
end
Apologies for the messy code on the model as I am still learning Rails.
User Controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
flash[:success] = 'Account created'
else
flash[:notice] ='ERROR: Account was not created'
redirect_to 'users/new'
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation)
end
end
User Table:
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "username"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "persistence_token"
t.string "password_digest"
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
end
User Test:
require 'test_helper'
class UserTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
def setup
#user = User.new(first_name: 'test', last_name: 'tester', password: '1234',email: 'test1#mail.com',
password: 'foobar', password_confirmation: 'foobar')
end
test 'should be valid' do
assert #user.valid?
end
end
Update:
I have tested this out and it works. So hope will work for you as well :) Looks like MiniTest doesn't work well with BCrypt. I received the same error - undefined password, but later implemented my change and it went further well.
Original answer:
As of your founded solution it made me think that this makes no sence - adding getter and especially setter methods for :password and :password_confirmation. Because has_secure_password creates those virtually that runs through BCrypt. So doesn't it goes around crypting / encrypting? If so it is not safe. So only option left for testing I see take the BYcript into the testing suite. I think something like this might do the trck:
In User Test:
require 'bcrypt'
def setup
#user = User.new(first_name: 'test', last_name: 'tester', password: BCrypt::Password.create("my password") ,email: 'test1#mail.com', password_confirmation: 'my password')
end
test 'should be valid' do
assert #user.valid?
end
Note that I removed duplicated password: 'foobar. Since with that particular test you are testing if User can be created, so shouldn't pass a different password or even duplicated attribute... Make another test for this (also checkout fixtures, they are great for creating test objects, as well as factories for more complicated cases).
And of course, remove the atr_accessor :password, :password_confirmation form your User model.
p.s. and please fix you code snippet for User class. Or is it really defined twice like this?:
class User < ApplicationRecord
has_many :activities
class User < ActiveRecord::Base
I have the following model:
class User < ActiveRecord::Base
validates :password, presence: true, length: { in: 6..255 }
end
... and a standard controller and view. The user form does not present the current password. I want to avoid this blank password being set on the model. I only want to update the password when it is non-blank.
I tried:
model_params = params.reject {|k, v| v == ""}
#model.update!(model_params)
But it did not work, can not be skip model validates.
When I call model.update! Rails reports:
Validation failed: Password can't be blank, Password is too short (minimum is 6 characters)
I take either of the 2 approaches (in order):
First, Adding :if condition to validation:
validates :password,
presence: true,
length: { in: 6..255 },
if: lambda { new_record? || !password.blank? }
Second, Running this validation on create only
validates :password,
presence: true,
length: { in: 6..255 },
on: create
HTH
You can just add ! to reject like this,
model_params = params.reject! {|k, v| v == ""}
#model.update!(model_params)
try to modify the migration file of your model , so that the fields you want to leave blank wont be problem in the database for example :
create_table(:users) do |t|
t.string :email, null: true, default: ""
t.string :encrypted_password, null: true, default: ""
end
then you should
rake db:migrate
if the table is already created try to run a change_column migration
I created a users model in a rails 4.1.8 application with the attributes email and password. I fired up rails console, user = User.new(email: "user#example.com" , password: "example") work but user.save saves the password and omits the email. below is the model, migrated database file and rails console log.
USER MODEL
class User < ActiveRecord::Base
before_save {self.email = email.downcase! }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format:{with: VALID_EMAIL_REGEX},
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
end
DATABASE FILE (For the User)
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email, unique: true
t.string :password_digest
t.timestamps null: false
end
end
end
RAILS CONSOLE LOG
<r.new(email: "user#example.com" , password: "example")
=> #<User id: nil, email: "user#example.com", password_digest: "$2a$10$jxwd/oriT
z2HklHK4b4nf.P.DWb6s35YTO.EbYwup0I...", created_at: nil, updated_at: nil>
irb(main):008:0> user.save
(1.0ms) BEGIN
User Exists (1.0ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = 'user#example.com' LIMIT 1
SQL (46.0ms) INSERT INTO `users` (`created_at`, `password_digest`, `updated_at`) VALUES ('2015-07-20 20:54:35', '$2a$10$jxwd/oriTz2HklHK4b4nf.P.DWb6s35YTO.Eb
Ywup0I.gMTOLSNKa', '2015-07-20 20:54:35')
(89.1ms) COMMIT
=> true
USERS CONTROLLER
class UsersController < ApplicationController
def new
end
private
def user_params
params.require(:user).permit(:email)
end
end
Looking at the log the email parameter was not inserted into; please, any help with this is appreciated.
Try the following in your User model:
before_save {self.email = email.downcase } # no "!"
downcase! edits the variable on which it is called and does not return the downcased string unless there is something to downcase (i.e., if you use it on an all lower-case email, it returns nil and based on your console output, this is what is happening). downcase alone should be fine.
downcase! on the API
I'm currently working on a system where the email is only required if the user is not a student and username is required if the user is a student.
So here is what I did in my model :
class User < ActiveRecord::Base
validates :email, presence: true, unless: :student?
validates :username, presence: true, if: :student?
end
This works fine on username attributes, but for the email, I'm still getting Email cannot be blank error. I guess Devise has it's own email validation rule.
How can I make this works, I mean overriding Devise validate presence rule on email?
Thanks
Devise has an email_required? method that can be overrided with some custom logic.
class User < ActiveRecord::Base
validates :username, presence: true, if: :student?
protected
def email_required?
true unless student?
end
end
I think Devise uses email as a key in its model.
add_index :users, :email, unique: true
If you used the generated devise migrations you could make the user_name the key with a migration.
add_index :users, :user_name, unique: true
remove_index(users, column: users)
change_column :users, :email, :string, :null => true