To my User model I have added a new variable new_email. To this end I added:
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :new_email, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX }
In migration file:
t.string :new_email
I have not added anything in my seeds file regarding this new variable. On seeding I get the error: ActiveRecord::RecordInvalid: Validation failed: New email is invalid. If I remove the validates line from the model file it seeds succesfully. I have tried resetting my db: rake db:drop, rake db:create, rake db:migrate and rake db:seed, but with the same result. What could be causing this error?
The controller methods using params:
def create
#user = User.new(usernew_params)
if #user.save
#user.send_activation_email
flash[:success] = "A confirmation email has been sent to you"
redirect_to root_url
else
render 'new'
end
end
def update
#user = User.friendly.find(params[:id])
if #user.update_attributes(userupdate_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
private
def usernew_params
params.require(:user).permit(:email,
:username,
:password,
:password_confirmation)
end
def userupdate_params
params.require(:user).permit(:email,
:email_new,
:avatar,
:organization,
:activated,
:password,
:password_confirmation)
end
And my seeds file (left out other models without a relationship to this model):
99.times do |n|
username = "fakename#{n+1}"
email = "example-#{n+1}#railstutorial.org"
password = "password"
organization = Faker::Company.name
User.create!(username: username,
email: email,
password: password,
password_confirmation: password,
activated: true,
activated_at: Time.zone.now,
organization: organization)
end
The issue is that you are validating new_email on every request and not allowing it to be blank. Since it is not set in your seed file and you are validating based on a Regexp this will fail every time.
Based on your use case described I would recommend this
validates :new_email, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
allow_blank: true,
on: :update
This will do 2 things:
It will allow new_email to be blank "" or omitted completely nil without failing.
Also since you are not even accepting new_email on create through the controller this validation will only run on update.
While validations that allow_blank are fairly lightweight I still try to advise against running code when it is not needed that is why I added the on parameter.
Related
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
I have a simple Rails 4 application, and I'm trying to write some tests to check my user signup is working properly.
I have a problem that when I'm running testcases with my hands, everything works ok, but when I write some integration tests and run them it skips my model's validations, so I'm getting database exceptions like
Minitest::UnexpectedError: ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_users_on_username"
test 'unique username violated' do
params = user_params # it is just a hash with User model attributes
User.new(params).save # saving user first time to violate unique after
params[:email] = 'Test#Email2'
assert_no_difference 'User.count' do
post users_path, user: params
end
assert_template 'users/new'
end
Example of my validation is below:
validates :username, :email, \
uniqueness: {uniqueness: true, message: 'Username&email must be unique'}, \
length: {maximum: 50, message: 'Length must be <= 50'}
It's not only this validation issue, but all the rest are not working in tests too.
If I'll validate my user object with something like, inside of a test, it will be false, as it should be.
User.new(params).valid?
My users#create action is below:
def create
#user = User.new user_params
if #user.save
flash[:success] = 'Welcome to the Sample App!'
redirect_to #user
else
render 'new'
end
end
my users#user_params method:
def user_params
user_group = UserGroup.find_by_name 'administrator'
if user_group.nil?
user_group = UserGroup.new name: 'administrator'
user_group.save
end
{username: 'TestUsername', password: 'TestPassword', \
password_confirmation: 'TestPassword', email: 'Test#Email', \
firstname: 'TestFirstname', lastname: 'TestLastname',user_group: user_group}
end
Rails validations don't really work that way. When you use:
validates :username, :email, uniqueness: true
You are adding a similar but separate validations for :username and :email. Consider this case:
user = User.new
user.valid?
assert(user.errors.has_key?(:username)) # pass
assert(user.errors.has_key?(:email)) # pass
When you print out the errors with user.errors.full_messages() it will contain both Email has already been taken and Username has already been taken.
But if you override the message like so:
validates :username, :email, uniqueness: { message: 'Username&email must be unique' }
You will get Username&email must be unique twice instead, which is not so great.
If for some reason you really need to have only one error you would add a custom validation method:
validate :uniqueness_of_username_and_email
def uniqueness_of_username_and_email
if User.where("username = ? OR email = ?", username, email).any?
errors.add(:email_username, 'Username&email must be unique')
end
end
After coding users_signup_test.rb and expecting a GREEN response, it is still failing for some reason. The tutorial states I should be getting a GREEN response at this phase, but I am not. This is what I receive:
1) Error:
UsersSignupTest#test_invalid_signup_information:
ActiveModel::ForbiddenAttributesError: ActiveModel::ForbiddenAttributesError
app/controllers/users_controller.rb:12:in `create'
test/integration/users_signup_test.rb:8:in `block (2 levels) in <class:UsersSignupTest>'
test/integration/users_signup_test.rb:7:in `block in <class:UsersSignupTest>'
16 runs, 31 assertions, 0 failures, 1 errors, 0 skips
The only deviation I've had to do from the book that may affect this is one I had to make in the user.rb model and it is as follows:
validates( :email, presence: true, length: { maximum: 255}, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } )
as opposed to the book's:
validates( :email, presence: true, length: { maximum: 255}, format: { with: VALID_EMAIL_REGEX }, uniqueness: case_sensitive: false )
which would give me an error.
Here is my users_signup_test.rb file:
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, user: { name: "", email: "user#invalid", password: "foo", password_confirmation: "bar" }
end
assert_template 'users/new'
end
end
Let me know if any other code is needed to help diagnose this issue.
As an additional note, I also ran into issues that others have as well using sass-rails 5.0.1 and so I am using sass-rails 5.0.2.
Thank you everyone for your help.
Do you have well-defined the needed parameters (user_params function) when you create an user ? If you're following the tutorial you should end with something like this in your users_controller.rb:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
# more logic
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
Because it looks like you're passing an user parameter that is not permitted by the controller.
Found the answer!
Sorry for the unnecessary post. Researched the error and found it's related to Strong Params. Looking closely at the book, I missed this line:
#user = User.new(user_params)
which changed from
#user = User.new(params[:user])
So I wasn't passing the parameters to the controller properly. Sorry everyone!
email and new_email are two distinct columns. Every email should be unique, so if an email is added into either column it cannot already exist in either email or new_email columns.
Back story: I create the primary email for activated accounts and have a second new_email for when a user decides to change their email address but has not yet validated the new one via an email confirmation.
Most SO searches gave the scope solution:
I've tried validates :email, uniqueness: {scope: :new_email} and validates :new_email, uniqueness: {scope: :email} however I'm pretty sure this functionality acts to create a new key among the email1, email2 pair which is not the desired effect.
Currently I'm judging the validity of my code with the following two test cases (which are failing)
test "new_email should not match old email" do
#user.new_email = #user.email
assert_not #user.valid?
end
test "new_email addresses should be unique" do
duplicate_user = #user.dup
duplicate_user.new_email = #user.email.upcase
duplicate_user.email = #user.email + "z"
#user.save
assert_not duplicate_user.valid?
end
Scope's not going to cut it... it's really there to make sure that mail is unique amongst all records that might share the same new_mail and won't catch uniqueness for different new_email values, nor will it compare the values across the two columns.
Use standard 'uniqueness' to ensure there's no duplication within the column.
Then you'll need to create a custom validation for cross column...
validate :emails_unique
def emails_unique
found_user = User.find_by(new_email: email)
errors.add(:email, "email is someone's new email") if found_user
return unless new_email
found_user = User.find_by(email: new_email)
errors.add(:new_email, "new email is someone's regular email") if found_user
end
I think you really just want to write a custom validator, something like this:
validate :emails_are_unique
def emails_are_unique
if email == new_email
errors.add(:base, 'Your new email can not match your old email')
end
end
Which will do what you are looking for.
validates :email, uniqueness: {scope: :new_email} will ensure that email is unique among all records with the same value for new_email. This is not what you want.
You'll have to write a custom validation function, e.g.:
def validate_email_and_new_email_unique
if email && self.class.exists?("email = :email OR new_email = :email", email: email)
errors.add :email, "must be unique"
end
if new_email && self.class.exists?("email = :email OR new_email = :email", email: new_email)
errors.add :new_email, "must be unique"
end
end
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...