I wanted to do a simple regex-based email validation:
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([\S]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || 'is not an email')
end
end
end
Which is mentioned in rails documentation.
I have checked it in rubular and everythings works fine, but in rails, emails with dot in local address fails validation for some reason (john.doe#example.com). What's weird is that it accepts any other non-whitespace characters (e.g &^%#!*() excluding # of course).
class User < ApplicationRecord
validates :email, presence: true, length: { maximum: 64 },
uniqueness: { case_sensitive: false }, email: true
validates :password, presence: true, length: { in: 8..64 }
validates :first_name, length: { in: 2..16 }, allow_blank: true
validates :last_name, length: { in: 2..32 }, allow_blank: true
validates :student_id, length: { in: 5..6 }, allow_blank: true,
numericality: true
end
And failing test:
test 'should validate email with special characters in local address' do
#user.email = 'john.doe#domain.com'
assert #user.valid?
end
Whole user_test.rb
class UserTest < ActiveSupport::TestCase
def setup
#user = User.new(email: 'johndoe#domain.com', password: 'password12',
first_name: 'John', last_name: 'Doe',
student_id: '112233')
end
# Presence validation
test 'user validate user' do
assert #user.valid?
end
test 'should not validate empty email' do
#user.email = ''
assert_not #user.valid?
end
test 'should not validate blank password' do
#user.password = ' '
assert_not #user.valid?
end
test 'should validate blank first name' do
#user.first_name = ''
assert #user.valid?
end
test 'should validate blank last name' do
#user.last_name = ''
assert #user.valid?
end
test 'should validate blank student id' do
#user.student_id = ''
assert #user.valid?
end
# Length validation
test 'should not validate too long email' do
#user.password = 'a' * 65
assert_not #user.valid?
end
test 'should not validate too long password' do
#user.password = 'a' * 65
assert_not #user.valid?
end
test 'should not validate too short password' do
#user.password = 'a' * 7
assert_not #user.valid?
end
test 'should not validate too long first name' do
#user.first_name = 'a' * 17
assert_not #user.valid?
end
test 'should not validate too short first name' do
#user.first_name = 'a'
assert_not #user.valid?
end
test 'should not validate too long last name' do
#user.last_name = 'a' * 33
assert_not #user.valid?
end
test 'should not validate too short last name' do
#user.last_name = 'a'
assert_not #user.valid?
end
test 'should not validate too long student id' do
#user.student_id = '1234567'
assert_not #user.valid?
end
test 'should not validate too short student id' do
#user.student_id = '1234'
assert_not #user.valid?
end
# Email format validation
test 'should not validate email with wrong format' do
#user.email = 'john.doe#domain'
assert_not #user.valid?
end
test 'should validate email with special characters in local address' do
#user.email = 'john.doe#domain.com'
assert #user.valid?
end
test 'should not validate email with special characters in domain' do
#user.email = 'john.doe#dom_ain.com'
assert_not #user.valid?
end
# Numeric-only validation
test 'should not validate student id with non-numeric characters' do
#user.student_id = 'ab123c'
assert_not #user.valid?
end
# Uniqueness validation
test 'should not validate duplicated email' do
#copycat_user = #user.dup
#user.save
assert_not #copycat_user.valid?
end
end
Any idea what could be the reason for that weird behaviour?
I've got the resolution.
I had fixture with same parameters as
#user = User.new(email: 'johndoe#domain.com', password: 'password12',
first_name: 'John', last_name: 'Doe',
student_id: '112233')
Since email must be unique, it gave me an error that email has already been taken.
Related
My tests had been running green up until I hit 6.17. Can anyone help me see what is wrong?
Here is the error and fail:
ERROR["test_layout_links", SiteLayoutTest, 0.011797307059168816]
test_layout_links#SiteLayoutTest (0.01s)
ArgumentError: ArgumentError: Range unspecified. Specify the :in,
:within, :maximum, :minimum, or :is option.
app/models/user.rb:3:in `<class:User>'
app/models/user.rb:1:in `<top (required)>'
FAIL["test_email_should_not_be_too_long", UserTest, 0.04466394800692797]
test_email_should_not_be_too_long#UserTest (0.04s)
Expected true to be nil or false
test/models/user_test.rb:29:in `block in <class:UserTest>'
12/12: [=============================================================] 100%
Time: 00:00:00, Time: 00:00:00
Finished in 0.62805s
12 tests, 15 assertions, 1 failures, 1 errors, 0 skips
Here is my user.rb file:
class User < ApplicationRecord
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, length: { maxmium: 255 }
end
Here is my user_test.rb file:
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com")
end
test "should be valid" do
assert #user.valid?
end
test "name should be present" do
#user.name = " "
assert_not #user.valid?
end
test "email should be present" do
#user.email = " "
assert_not #user.valid?
end
test "name should not be too long" do
#user.name = "a" * 51
assert_not #user.valid?
end
test "email should not be too long" do
#user.email = "a" * 244 + "#example.com"
assert_not #user.valid?
end
end
And here is my site_layout_test.rb file since it is somehow involved:
require 'test_helper'
class SiteLayoutTest < ActionDispatch::IntegrationTest
test "layout links" do
get root_path
assert_template 'static_pages/home'
assert_select "a[href=?]", root_path, count: 2
assert_select "a[href=?]", help_path
assert_select "a[href=?]", about_path
assert_select "a[href=?]", contact_path
get contact_path
assert_select "title", full_title("Contact")
get signup_path
assert_select "title", full_title("Sign Up")
end
end
I've combed these files for typos and can't seem to find what is going on. Thanks for any help you can provide.
Have a spell check on maximum the user.rb file.
You have mispelled maximum for maxmium
The code should be like
class User < ApplicationRecord
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, length: { maximum: 255 }
end
I have custom validator for password that takes a updating_password field from the controller
attr_accessor :updating_password
validates :password, presence: true, if: :should_validate_password?
validates :password, length: { minimum: 6 }, if: :should_validate_password?
def should_validate_password?
updating_password || new_record?
end
I want to stub out the updating_password field in my User model RSpec test, something like
before(:each) do
#user_valid = FactoryGirl.create(:user)
end
it "validates for password when updating_password is true" do
old_password = #user_valid.password
subject { #user_valid }
allow(subject).to receive(:updating_password).and_return(true)
#user_valid.update(password: "short", password_confirmation: "short")
expect(#user_valid.password).to eql(old_password)
end
The password should not be updated in this case because it is too short but the test is failing. Any help would be appreciated
I'd recommend not stubbing out your model validations. Instead you might test what you're trying to do like this
# spec/models/user_spec.rb
describe User do
describe 'validations' do
context 'while updating password' do
let(:user){ FactoryGirl.create(:user, updating_password: true) }
it 'requires password to be at least 6 characters long' do
expect {user.update!(password: 'short')}.to raise_error(ActiveRecord:RecordInvalid)
end
it 'requires password to be present' do
expect {user.update!(password: nil))}.to raise_error(ActiveRecord:RecordInvalid)
end
end
end
end
Doing the Ruby on Rails Tutorial and I'm getting strange error message when running the test.
My user_test.rb:
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com", password: "wordpass", password_confirmation: "wordpass")
end
test "should be valid" do
assert #user.valid?
end
test "name should be present" do
#user.name=" "
assert_not #user.valid?
end
test "email should be present" do
#user.email= " "
assert_not #user.valid?
end
test "name should not be too long" do
#user.name= "a" * 51
assert_not #user.valid?
end
test "email should not be too long" do
#user.email= "a" * 256
assert_not #user.valid?
end
test "email validation should accept valid addresses" do
valid_addresses = %w[user#example.com USER#foo.COM A_US-ER#foo.bar.org
first.last#foo.jp alice+bob#baz.cn]
valid_addresses.each do |valid_address|
#user.email = valid_address
assert #user.valid? , "#{valid_address.inspect} should be valid"
end
end
test "email validation should reject invalid addresses" do
invalid_addresses = %w[user#example,com user_at_foo.org user.name#example.
foo#bar_baz.com foo#bar+baz.com]
invalid_addresses.each do |invalid_address|
#user.email = invalid_address
assert_not #user.valid?, "#{invalid_address.inspect} should be invalid"
end
end
test "email addresses should be unique" do
duplicate_user = #user.dup
duplicate_user.email = #user.email.upcase
#user.save
assert_not duplicate_user.valid?
end
test "password should be a minimum length" do
#user.password = #user.password_confirmation = "a" * 5
assert_not #user.valid?
end
end
The user.rb :
class User < ActiveRecord::Base
attr_accessor :name, :email , :password, :password_confirmation
before_save { self.email = email.downcase }
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 }
end
One of the errors seems to be from the user definition, but I cannot find the source of error. Is it something due related to "validates" in user.rb file ? Any help will be much appreciated !!
FAIL["test_email_validation_should_accept_valid_addresses", UserTest, 0.061796445]
test_email_validation_should_accept_valid_addresses#UserTest (0.06s)
"user#example.com" should be valid
test/models/user_test.rb:34:in `block (2 levels) in <class:UserTest>'
test/models/user_test.rb:32:in `each'
test/models/user_test.rb:32:in `block in <class:UserTest>'
FAIL["test_should_be_valid", UserTest, 0.080466737]
test_should_be_valid#UserTest (0.08s)
Failed assertion, no message given.
test/models/user_test.rb:8:in `block in <class:UserTest>'
Thanks in advance for your help !
You are right, one of the errors is due to #user being invalid in the first test. Do the following in rails console, this should print out errors on user model:
#user = User.new(name: "Example User", email: "user#example.com", password: "wordpass", password_confirmation: "wordpass")
#user.valid?
pp #user.errors
Try different regex in user.rb
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
well everything looks fine. just run the command
rake db:test:prepare
and if this doesnt work either then
$rake db:delete
rake db:create
rake db:migrate
first one worked for me
I'm learning TDD with Rails 4 and rspec. I've made some test cases for my user model to check the password lengths. I have two tests so far that checks whether a user input a password that was too short and one where the password is between 6 - 10 characters.
So far, the "password is too short" test passes:
it "validation says password too short if password is less than 6 characters" do
short_password = User.create(email: "tester#gmail.com", password: "12345")
expect(short_password).not_to be_valid
end
However, on the test where I do have a valid password, it fails:
it "validation allows passwords larger than 6 and less than 10" do
good_password = User.create(email: "tester2#gmail.com", password: "blahblah")
expect(good_password).to be_valid
end
And I get this error:
Failure/Error: expect(good_password).to be_valid
expected #<User id: 1, email: "tester2#gmail.com",
created_at: "2014-06-21 02:43:42", updated_at: "2014-06-21 02:43:42",
password_digest: nil, password: nil, password_hash: "$2a$10$7u0xdDEcc6KJcAi32LBW7uzV9n7xYbfOhZWdcOnU5Cdm...",
password_salt: "$2a$10$7u0xdDEcc6KJcAi32LBW7u"> to be valid,
but got errors: Password can't be blank, Password is too short (minimum is 6 characters)
# ./spec/models/user_spec.rb:12:in `block (3 levels) in <top (required)>'
Here's my model code:
class User < ActiveRecord::Base
has_many :pets, dependent: :destroy
accepts_nested_attributes_for :pets, :allow_destroy => true
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, presence: true, :length => 6..10, :confirmation => true
#callbacks
before_save :encrypt_password
after_save :clear_password
#method to authenticate the user and password
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
#method to encrypt password
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
#clears password
def clear_password
self.password = nil
end
end
I'm confused on why the password is nil when I create the test object.
You have a password presence requirement on your model, but then you have an after_save hook that nilifies the password and puts the record into an invalid state. The first test passes because your records are always being put into an invalid state by the after_save hook. You need to rethink how you're handling password storage; once you resolve that, here are some code samples to help give you some ways to test this:
# Set up a :user factory in spec/factories.rb; it should look something like:
FactoryBot.define do
factory :user do
sequence(:email) { |n| "tester+#{n}#gmail.com" }
password { SecureRandom.hex(6) }
end
end
# In your spec:
let(:user) { create :user, password: password }
context 'password' do
context 'length < 6' do
let(:password) { '12345' }
it { expect(user).not_to be_valid }
it { user.errors.message[:password]).to include('something') }
end
context 'length >= 6' do
context 'length < 10' do
let(:password) { 'blahblah' }
it { expect(user).to be_valid }
end
context 'length >= 10' do
let(:password) { 'blahblahblah' }
it { expect(user).not_to be_valid }
end
end
end
You can also use shoulda matchers:
it { should_not allow_value('12345').for(:password) }
it { should allow_value('12345blah').for(:password) }
The most likely problem is the password field is not mass assignable. That is why password is nil in the output message.
Try this instead:
it "validation allows passwords larger than 6 and less than 10" do
good_password = User.create(email: "tester2#gmail.com")
good_password.password = "blahblah"
expect(good_password).to be_valid
end
Note that your first test is passing accidentally - it has the same problem as the second test (password isn't being assigned). This means you aren't actually testing that the password is rejected when less than 6 characters atm.
See this article on mass assignment for more details.
EDIT: Leo Correa's comment may suggest this may not be the case for you. Posting your model code would help...
Well I've searched all over the place and it seems that nobody else has the issue with this 'encrypt' method causing their tests to fail, though it seems plenty others have had some difficulty with chapter 7. Without further adieu,
Here's the link to Hartl's Chapter 7
My code in the user model file, and the corresponding spec file, appear to be completely exact to what he has written and I still cannot get the tests to pass. The errors?
Failures:
1) User should create a new instance given valid attributes
Failure/Error: User.Create!(#attr)
NoMethodError: undefined method 'encrypt' for #<User:asdf>
#./app/models/user.rb:22:in 'has_password?'
#./app/models/user.rb:28:in 'encrypt_password'
#./spec/models/user_spec.rb:15:in 'block (2 levels) in <top (required)>'
2) User should not allow duplicate email addresses
Failure/Error: User.Create!(#attr)
NoMethodError: undefined method 'encrypt' for #<User:asdf>
#./app/models/user.rb:22:in 'has_password?'
#./app/models/user.rb:28:in 'encrypt_password'
#./spec/models/user_spec.rb:15:in 'block (2 levels) in <top (required)>'
3) User should reject email addresses identical up to case
Failure/Error: User.Create!(#attr)
NoMethodError: undefined method 'encrypt' for #<User:asdf>
#./app/models/user.rb:22:in 'has_password?'
#./app/models/user.rb:28:in 'encrypt_password'
#./spec/models/user_spec.rb:15:in 'block (2 levels) in <top (required)>'
...
7) User has_password? method should be false if passwords do not match
Failure/Error: User.Create!(#attr)
NoMethodError: undefined method 'encrypt' for #<User:asdf>
#./app/models/user.rb:22:in 'has_password?'
#./app/models/user.rb:28:in 'encrypt_password'
#./spec/models/user_spec.rb:15:in 'block (3 levels) in <top (required)>'
so i'm getting the same error messages for each test and I am going nuts trying to find out why!
Here's my user.rb:
require 'digest'
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 }
#automatically create the virtual attribute for 'password_confirmation'
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
#returns true if the users password matches the submitted one
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
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
and my user_spec.rb file:
require 'spec_helper'
require 'digest'
describe User do
before(:each) do
#attr = {
:name => "User Name",
:email => "example#email.com",
:password => "password",
:password_confirmation => "password"
}
end
it "should create a new instance given valid attributes" do
User.create!(#attr)
end
it "should require a name" do
no_name_user = User.new(#attr.merge(:name => ""))
no_name_user.should_not be_valid
end
it "should require an email" do
no_email_user = User.new(#attr.merge(:email => ""))
no_email_user.should_not be_valid
end
it "should reject names that are too long" do
long_name = "a" * 51
long_name_user = User.new(#attr.merge(:name => long_name))
long_name_user.should_not be_valid
end
it "should accept valid email addresses" do
addresses = %w[user#foo.com THE_USER#foo.bar.org first.last#foo.jp]
addresses.each do |address|
valid_email_user = User.new(#attr.merge(:email => address))
valid_email_user.should be_valid
end
end
it "should reject invalid email addresses" do
addresses = %w[user#foo,com user_at_foo.org example.user#foo.]
addresses.each do |address|
invalid_email_user = User.new(#attr.merge(:email => address))
invalid_email_user.should_not be_valid
end
end
it "should not allow duplicate email addresses" do
User.create!(#attr)
user_with_duplicate_email = User.new(#attr)
user_with_duplicate_email.should_not be_valid
end
it "should reject email addresses identical up to case" do
upcased_email = #attr[:email].upcase
User.create!(#attr.merge(:email => upcased_email))
user_with_duplicate_email = User.new(#attr)
user_with_duplicate_email.should_not be_valid
end
describe "password validations" do
it "should require a password" do
User.new(#attr.merge(:password => "", :password_confirmation => ""))
should_not be_valid
end
it "should require password to match the password confirmation" do
User.new(#attr.merge(:password_confirmation => "invalid"))
should_not be_valid
end
it "should reject short passwords" do
short = "a" * 5
hash = #attr.merge(:password => short, :password_confirmation => short)
User.new(hash).should_not be_valid
end
it "should reject long passwords" do
long = "a" * 41
hash = #attr.merge(:password => long, :password_confirmation => long)
User.new(hash).should_not be_valid
end
end
describe "password encryption" do
before(:each) do
#user = User.create!(#attr)
end
it "should have an encrypted password attribute" do
#user.should respond_to(:encrypted_password)
end
it "should not allow a blank encrypted password" do
#user.encrypted_password.should_not be_blank
end
end
describe "has_password? method" do
before(:each) do
#attr = User.create!(#attr)
end
it "should be true if the passwords match" do
#user.has_password?(#attr[:password]).should be_true
end
it "should be false if the passwords don't match" do
#user.has_password?("invalid").should be_false
end
end
end
Any help would be greatly appreciated. I've poured over other's problems, my code, and changed various aspects to try and get the tests to work, all to no avail. I hope it's not something really stupid I'm still not seeing.
Your error is here:
def encrypt_string
secure_hash("#{salt}--#{string}")
end
You are calling encrypt in the following encrypt_password method but your method above is named encrypt_string:
def encrypt_password
self.salt = make_salt unless has_password?(password)
self.encrypted_password = encrypt(password)
end
Just change encrypt_string to encrypt in the method definition and you should be good to go.