Rails 5 validation on update fires on create too - ruby-on-rails

I am using factory bot gem to build my factory objects for rspec test. And I have a validation like this in my model :
validates :reason_for_change, presence: true, on: :update
The spec test fails because the object itself cannot be created :
Failure/Error: let(:user) { create(:super_user) }
ActiveRecord::RecordInvalid:
Validation failed: Profile reason for change can't be blank
I have also tried this:
validates :reason_for_change, presence: true, unless: :new_record?
Same error. This as well :
validates :reason_for_change, presence: true, if: :my_custom_method
def my_custom_method
!new_record?
end
Same result. However when I used the last validation and put a breakpoint inside my custom method like this :
def my_custom_method
binding.pry
end
And was manually first checking is this a new_record?, whenever it would evaluate to true, I would return false (manually typing). And for every false evaluated, I'd return true (manually typing).
In this case all tests pass. What is going on here? I am using rails 5.0.1
Update 1:
Here is my super user factory :
factory :super_user do
transient do
email { nil }
end
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
gender { 'male' }
age { Random.rand(18..40) }
ethnicity { '' }
avatar { Rack::Test::UploadedFile.new(Rails.root.join('spec/files/1.png')) }
end
Also I don't think I should modify factory, just to make this work. This should work.

A copy-paste of my comment:
Please check the logs. I believe, you will see there something like this:
INSERT INTO super_users () VALUES(...)
UPDATE super_users SET filed = val
If so - please check another callbacks that may be run when record is saved. My guess is that you have another callback that updates same record in, for example, after_save callback.
You should disable that validation first before trying my suggestion.

Related

Rails 4 - Custom validation method not being called

I have a validation method that should validate whether an user is part of a team. If it is not part of the team it should add an error and thus failing to save the record.
This is the current method that is inside the model:
def assignee_must_be_part_of_team
unless assignee_id.blank?
team = Team.find(self.team_id)
errors.add(:team, 'Equipe não existe') unless team
user = team.users.find(self.assignee_id)
errors.add(:assignee_id, 'Responsável não faz parte da equipe') unless user
end
end
And I am registering it in my model with this:
validate :assignee_must_be_part_of_team, on: :save
However, this method is not being even called when I save a new record! I even tried to add some logs to it but nothing happens and the record is being saved anyway.
Am I missing anything here?
Use create or update as the value of :on option.
Change this:
validate :assignee_must_be_part_of_team, on: :save
To:
validate :assignee_must_be_part_of_team, on: :create
or:
validate :assignee_must_be_part_of_team, on: :update
If you want your validation to run for both create and update actions, then you don't even need to specify the :on option at all, because that's the default behaviour. So, just this should work:
validate :assignee_must_be_part_of_team
See the documentation here for more information.
You are adding two errors in one validation. maybe you can split this into separate validations for easy debugging:
validates :team_id, presence: :true
validate :belong_to_team, :assignee_part_of_team
private
def belong_to_team
errors[:team] << 'Equipe não existe' unless self.team
end
def assignee_part_of_team
errors[:assignee] << 'Responsável não faz parte da equipe' unless self.team and self.team.users.include?(self.assignee)
end
Then you can know which is causing the fault here.

Disable password validation

If it's seen that a user is trying to log in with Facebook, I'd like to not require a password when logging in. However, the Authlogic (obviously) checks for a password when logging in. How can I disable password validation in this case?
Below is a short code snippet of the code that I'm working with. login_params changes depending on the method by which the user is logging in -- either by form fields or by OmniAuth.
login_params = if facebook_login?
if homeowner = get_facebook_user
{
email: homeowner.email,
password: homeowner.password
}
end
else
user_session_params
end
You could use a lambda in the validation to run it conditionally. For example:
validates :password, presence: true, if: lambda { !isNotFacebookLogin? }

Rails & Shoulda Spec Matcher: validate_presence_of (:first_name) blowing up

describe User do
it { should belong_to(:shop) }
it { should respond_to(:shop) }
it { should respond_to (:first_name)}
it { should respond_to (:last_name)}
it { should respond_to (:email)}
Works fine. But as soon as I add:
it { should validate_presence_of (:first_name)}
it { should validate_presence_of (:last_name)}
It breaks, specifically in this callback method:
def get_url
source = "http://www.randomsite.com/"+ first_name + "+" + last_name
end
no implicit conversion of nil into String for +
How do I fix this?
Looks like your first_name and last_name are nil for the test.
Try to stub the values in a before block like this:
context 'first_name and last_name validation' do
before do
allow(subject).to receive(:first_name).and_return('bob')
allow(subject).to receive(:last_name).and_return('tom')
end
it { should validate_presence_of (:first_name)}
it { should validate_presence_of (:last_name)}
end
Well, this is how validate_presence_of(:first_name) works:
It assigns nil to your model's first_name attribute and calls .valid? on your model.
If .valid? returns true - the test fails, since if you have had correct validation in place, that wouldn't happen.
I assume that get_url method is set up to run on before_validation callback, so what is going on in your code:
first_name gets assigned to nil.
.valid? is called.
before_validation callback fires your get_url method
Everything blows up, because get_url doesn't get into account that first_name can be nil.

RSpec test won't pass. Validating for uniqueness with domain name

I'm using ruby 1.9.2 and rails 3.2.2.
I have a 'domain' model (domain.rb):
class Domain < ActiveRecord::Base
attr_accessible :url
belongs_to :user
VALID_DOMAIN_REGEX = /^[a-z0-9\-\.]+\.[a-z]{2,}$/i
validates :url, presence:true,
format: { with: VALID_DOMAIN_REGEX },
uniqueness: { case_sensitive: false }
end
And a test asserting that a duplicate domain should not be valid:
require 'spec_helper'
describe Domain do
before do
#domain = FactoryGirl.create(:domain)
end
subject { #domain }
describe "when domain url is already taken" do
before do
domain_with_same_url = #domain.dup
domain_with_same_url.url = #domain.url.upcase
domain_with_same_url.save
end
it { should_not be_valid }
end
end
The test keeps failing:
1) Domain when domain url is already taken
Failure/Error: it { should_not be_valid }
expected valid? to return false, got true
# ./spec/models/domain_spec.rb:31:in `block (3 levels) in '
#domain is already created, validated and saved.
The domain_with_same_url is the new record, and it should be invalid. But you are not checking it.
Try
domain_with_same_url = FactoryGirl.create(:domain, :url => #domain.url.upcase)
domain_with_same_url.should_not be_valid
Your two before blocks are run in outer-to inner order. Thus, when running your testsuite, first your #domain object gets created and saved, then the inner before block gets executed. Hiwever, your domain_with_same_url probably gets never actually saved because it's validation fails which probably leats to domain_with_same_url.save to return false.
As a workaround, you could check the validity of domain_with_same_url instead of #domain.
Seems like your subject of your test case is #domain, which is a valid object. Whether use new subject for #domain_with_same_url (don't forget to make it an instance variable), or explicitly say domain_with_same_url.should ... (just like The Who indicated in his answer).
I just had the same problem in Rails 4 Model column uniqueness not working. It provides two other solutions without a second call to FactoryGirl, just FYI. The one I am using is:
before(:each) do
#feed_with_same_url = #feed.dup
#feed_with_same_url.feed_url = #feed.feed_url
end
it { #feed_with_same_url.should_not be_valid }
The save is causing some problem, unexpected to me. And, you need to reference an object to should_not_be_valid as a local variable.

How can i save errors in a container inside a model and display it in the controller

i am currently trying to display my errors that i added to user object inside the controller simultaneously with my validation inside the model.
like if maybe i have an error in my controller it display it immediately then return and without displaying the errors inside the model, i know that it has to return if my errors are finished counting and display all of them not one by one.
i even wrote a validation method that should check the validations inside the model and save them to my object then the display error method can display all the errors including the one's that was found in the model.
my controllers methods are like this
def info
if #user.firstname != "" && #user.lastname != "" && #user.id_number != "" && #user.email != ""
#user.errors.add_to_base("Password can't be blank")
end
end
def validations()
#errors = User.check_validations
end
def display(template_to_render)
if #user.errors.count >= 1
render :action => template_to_render
end
end
Then my method in the model is as follows
def self.check_validations
validates_presence_of :firstname, :lastname, :date_of_birth, :if => Proc.new { |o| o.force_update? || o.profile_confirmed? }
end
then i want to add all the errors of validations method to the #user.errors.to_base errors
and display them all.
so i was wondering if there is any method maybe that i can use to check the method inside the model and add all those errors to the #user object before it can display on the view.
A couple things.
All validations should go in the model, you shouldn't call errors.add in the controller.
The validates_presence_of is a class method which defines the validations which should take place. The validation does not happen at that exact point. Therefore you can not use this on a per-request basis.
If you want to do just one validation and then validate the rest of the model at a later time, try this.
class User < ActiveRecord::Base
validate :check_password
validates_presence_of :firstname, :lastname, :date_of_birth, :if => Proc.new { |o| o.force_update? || o.profile_confirmed? }
def check_password
if firstname != "" && lastname != "" && id_number != "" && email != ""
errors.add_to_base("Password can't be blank")
end
end
end
You can then call check_password directly to validate only that when needed.
def info
#user.check_password
end
def validations
#user.valid? # triggers validations
#errors = #user.errors
end
def display(template_to_render)
if #user.errors.count >= 1
render :action => template_to_render
end
end
I'm assuming those methods are all called in a single request and they aren't each separate controller actions.
What you are doing in "self.check_validations" is adding a validation every time your controller runs. After 1000 requests you will have 1000 validations added to the model and a crashed app (probably).
Look up "conditional validations" in Rails docs, this will explain how to achieve what you want.
You can also investigate .save! and .create! methods where you get exceptions if the model is invalid - this allows you to alter the control flow in a more explicit way

Resources