validates_uniqueness_of with conditions is not working as expected - ruby-on-rails

sorry for my english,
I have this validation on my model
validates_uniqueness_of :price, scope: [:brand, :establishment, :presentation, :user], conditions: -> { where(created_at: Date.today.beginning_of_day..Date.today.end_of_day) }
And this is my factory and test for it.
FactoryGirl.define do
factory :price do
association :establishment
association :presentation
association :brand
association :user
price 9.99
end
end
it "is invalid on duplicated by date" do
price = create(:price)
expect(build(:price, price.attributes)).to have(1).errors
end
And I am getting...
1) Price is invalid on duplicated by date
Failure/Error: expect(build(:price, price.attributes)).to have(1).errors
expected 1 errors, got 0
Why?

validation errors get added to new objects(unsaved) only when you call valid? on the object
it "is invalid on duplicated by date" do
price = create(:price)
new_price = build(:price, price.attributes)
new_price.valid?
expect(new_price).to have(1).errors
end

I solved it, my error was on a database conception, I had a float type for price field, I changed it to decimal type to fix the error.

Related

Rails Validation Error: ActiveRecord::RecordInvalid

I'm kinda struggling with validations in my Rails application.
I have the following Setup:
class MealDay < ApplicationRecord
belongs_to :meal
belongs_to :day
has_many :meal_day_canteens
has_many :canteens,
through: :meal_day_canteens
validates :meal_id, uniqueness: {scope: :day_id}
end
#MainController
helping_hash.each { |days, meal|
dayRef = Day.where(date: days).first
mealRef = Meal.where(name: meal)
dayRef.meals << mealRef #This line is obviously throwing the error because a record exists already
}
The Error is: "Validation failed: Meal has already been taken"
But I'm not sure on how to handle the error. I just want it, so that Rails is not inserting it into the database and skips it. If you need more information just tell me.
Thanks.
Edit: Some more code which I can't get to work now.
Edit2: Forgot to add validation to that model. Works fine now
helping_hash.each { |days, meal|
dayRef = Day.where(date: days).first
mealRef = Meal.where(name: meal)
meal_day_ref = MealDay.where(day: dayRef, meal: mealRef)
#canteenNameRef.meal_days << meal_day_ref rescue next
}
How about rescueing the error?
helping_hash.each do |days, meal|
dayRef = Day.where(date: days).first
mealRef = Meal.where(name: meal)
dayRef.meals << mealRef rescue next
end
Rails uniqueness constraint is basically throwing a query under the hood to look for a record in the DB. More of a side comment but this is already not safe concurrently so I recommend you adding a constraint at the database level.
You basically need to do the manual work of skipping the ones that already exist.
Basically something like:
helping_hash.each do |day, meal|
day_ref = Day.find_by!(date: day)
meal_ref = Meal.find_by!(name: meal)
unless day_ref.meal_ids.include?(meal_ref.id)
# Depending on the number of records you expect
# using day_ref.meals.exists?(id: meal_ref.id) may be a better choice
day_ref.meals << meal_ref
end
end

Rails faker gem produces same product name

I'm trying to use rails Faker gem to produce unique product names to make sample Item models in the database. I've used Faker multiple times but for some reason I can't produce new product names. I've made the nameMaker function to avoid possible early repeats, but I get a record invalidation just after one insert. Does anyone know how I could fix this?
seed.rb:
98.times do |n|
name = Item.nameMaker
description = Faker::Lorem.sentence(1)
price = Item.priceMaker
item = Item.create!(
name: name,
description: description,
price: price)
end
item.rb:
class Item < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 100 }
validates :description, presence: true,
length: { maximum: 1000 }
VALID_PRICE_REGEX = /\A\d+(?:\.\d{0,3})?\z/
validates :price, presence: true,
:format => { with: VALID_PRICE_REGEX },
:numericality => {:greater_than => 0}
validates_uniqueness_of :name
def Item.nameMaker
loop do
name = Item.newName
break if Item.find_by(name: name).nil?
end
return name
end
def Item.newName
Faker::Commerce.product_name
end
end
To get a unique name, enclose the faker in brackets. Eg
name { Faker::Commerce.product_name }
To achieve this, you could also make use of factory girl and when you want to create 98 different Items, you could have something like
factories/item.rb
FactoryGirl.define do
factory :item do
name { Faker::Commerce.product_name }
description { Faker::Lorem.sentence(1) }
price Faker::Commerce.price
end
end
in your spec file
let(:item) { create_list(:item, 98) }
You can add validates_uniqueness_of :name in your model. When you run seed method if there is already exists same name, it will throw error and skip to the next.
There is possibility that you will not have exactly 98 Items. You can increase number of times or edit Faker itself.
I figured it out after some experimentation, apparently the loop in some ways acts as like a function in terms of scoping. If you initialize a local variable in a loop, the function outside of the loop will not see it. In this case name always returning the string Item from the Item.nameMaker function. Thus the first attempt would always succeed and the second one would obtain the validation restriction.
def Item.nameMaker
loop do
name = Faker::Commerce.product_name # 'Random Product Name'
puts "Name: #{name}" # "Name: Random Product Name"
item = Item.find_by(name: name)
if item.nil?
puts "#{name} not found" # "Random Product Name not found"
break
else
end
end
puts "Returning Name #{name}" # "Returning Name Item"
return name
end
I managed to fix this by initializing the local variable outside of the loop. By doing this the entire function now has visibility to the same local variable for some reason.
def Item.nameMaker
name = "" #initializing
loop do
name = Faker::Commerce.product_name # 'Random Product Name'
puts "Name: #{name}" # "Name: Random Product Name"
item = Item.find_by(name: name)
if item.nil?
puts "#{name} not found" # "Random Product Name not found"
break
else
end
end
puts "Returning Name #{name}" # "Returning Random Product Name"
return name
end

Use a factory's sequence to generate unique phone numbers

I'm new to TDD, RSpec and factories, and trying to understand how to test that each User's phone number attribute is unique. To do so, I'm trying to use a sequence in my User factory. I'm not having much luck with the following:
FactoryGirl.define do
factory :user do
number = 123456789
sequence(:phone_number) {|n| (number + n).to_s }
end
end
Any thoughts on the best way to accomplish this? Also, what kind of test would make sense for something like this where ultimately I would want to add the following validation to the user model to make such a test pass?
validates :phone_number, :uniqueness => true
Thanks!
Try using a lambda with a random 10 digit number:
phone_number { rand(10**9..10**10) }
Try this:
FactoryGirl.define do
sequence :phone_number do |n|
"123456789#{n}"
end
factory :user do
phone_number
end
end
and in order to test your validation use this in your user_spec
it { should validate_uniqueness_of(:phone_number) }
To complete #westonplatter answer, in order to start at 0 000 000 000, you can use String#rjust:
FactoryGirl.define do
factory :user do
sequence(:phone_number) {|n| n.to_s.rjust(10, '0') }
end
end
Example:
> 10.times { |n| puts n.to_s.rjust(10, '0') }
0000000000
0000000001
0000000002
0000000003
0000000004
0000000005
0000000006
0000000007
0000000008
0000000009
While the random solution works, you have a small chance of not getting a unique number. I think you should leverage the FactoryGirl sequence.
We can start at, 1,000,000,000 (100-000-000) and increment up. Note: This only gives you 98,999,999,999 unqiue phone numbers, which should be sufficient. If not, you have other issues.
FactoryGirl.define do
sequence :phone_number do |n|
num = 1*(10**8) + n
num.to_s
end
factory :user do
phone_number
end
end

Rails 3 validates_uniqueness_of works in an unexpected way

I'm new to RoR. I'm facing a problem when using validates_uniqueness_of. I've a table with 3 columns:
name || father_name || dob
Vimal Raj || Selvam || 1985-08-30
I've a code in my model like this:
class Candidate < ActiveRecord::Base
attr_accessible :dob, :father_name, :name
validates_uniqueness_of :name, scope: [:father_name, :dob], case_sensitive: false,
message: ": %{value} already present in the database!!!"
before_save :capitalize_name, :capitalize_father_name
private
def capitalize_name
self.name.capitalize!
end
def capitalize_father_name
self.father_name.capitalize!
end
end
It throws error as expected when I insert => "vimal raj, Selvam, 1985-08-30"
But it is accepting the following data => "Vimal Raj, selvam, 1985-08-30" . I was expecting it will throw an error, but unexpectedly it accepts the record and inserts into the db as a new record.
Please help me on how to solve this.
If you want a one-liner solution, please try this :
before_validation lambda {self.name.capitalize!; self.father_name.capitalize!}
Hope, it will help.
I think the case_sensitivity is only matching on name, not on father_name. I would try changing before_save to before_validation so that both name and father_name are consistently the same capitalization when your validation is evaluated.

Comparing dates: "comparison of Date with nil failed"

I have a Client model which has many projects. In the project model I want to validate that the project start date is always before or on the same day as the project end date. This is my project model:
class Project < ActiveRecord::Base
attr_accessible :end_on, :start_on, :title
validates_presence_of :client_id, :end_on, :start_on, :title
validate :start_has_to_be_before_end
belongs_to :clients
def start_has_to_be_before_end
if start_on > end_on
errors[:start_on] << " must not be after end date."
errors[:end_on] << " must not be before start date."
end
end
end
My application works as expected and gives me the specified errors in case the validation fails.
However, in my unit test for the projects, I am trying to cover this scenario, deliberately setting the start date after the end date:
test "project must have a start date thats either on the same day or before the end date" do
project = Project.new(client_id: 1, start_on: "2012-01-02", end_on: "2012-01-01", title: "Project title")
assert !project.save, "Project could be saved although its start date was after its end date"
assert !project.errors[:start_on].empty?
assert !project.errors[:end_on].empty?
end
Strangely, running this test gives me three errors, all referring to this line if start_on > end_on in my validation method, saying undefined method '>' for nil:NilClass twice and comparison of Date with nil failed once.
What can I do to make the tests pass?
You are creating a Project that has string values for :start_on and :end_on. That's unlikely to work. Rails might try to be smart and parse those, I'm not sure.. I wouldn't count on it. Odds are some coercion is going on and the values are getting set to nil.
I would do this:
project = Project.new(client_id: 1,
start_on: 2.days.from_now.to_date,
end_on: Time.now.to_date,
title: "Project title")

Resources