Rails: unique validation fails when updating? - ruby-on-rails

I am searching or initializing and object and then updating it:
booking = Booking.where(deal_id: params["id"]).first_or_initialize
if booking.update!(
names: "some names",
deal_id: params["id"]
)
some other stuff
is failing with the message
Validation failed: deal_id has already been taken
If I am calling first or initialize and then update, how is this error possible? What needs to be fixed? Is it because I am adding deal_id again on the update line?
Thanks

Related

Rails: "has already been taken" error on update

The app finds or initializes by an attribute:
booking = Booking.where(deal_id: params["id"]).first_or_initialize
Then updates some additional attributes
if booking.update!(
guests: guests,
names: names,
time: time)
This code gives me the error
Validation failed: Deal has already been taken
The validation code is
validates_uniqueness_of :deal_id, allow_blank: true, scope: [:experience_id, :time], unless: -> {deal_id.zero? }
Why is it giving a "has already been taken" error when first it does first_or_initialize?
Thanks
It means you already have Booking with same deal_id in scope of experience_id, time
This could happen if you already had records in the database at the time when you added this uniqueness validation to model
But this is not necessary at all. Your first_or_initialize with just deal_id doesn't prevent of combination deal_id, experience_id and time
Besides, it doesn't protect you from race condition
You can check such records count with debug before updating:
Booking.where(
deal_id: booking.deal_id,
experience_id: booking.experience_id,
time: time,
).count
try to run without update method without ! to see where validation has failed
result = booking.update(
guests: guests,
names: names,
time: time)
result.errors # <= see related errors here
# This implies the first record
booking = Booking.find_by(deal_id: params["id"])
if booking.nil?
booking = Booking.new(
#... your attributes
)
else
# Update the attributes you want
booking.anything = anything
...
end
# This will create / update the record in the database
booking.save!

find_or_create by, if found, does it update?

https://apidock.com/rails/v4.0.2/ActiveRecord/Relation/find_or_create_by
After reading the docs, it does say: "find the first user named "Penélope" or create a new one." and "We already have one so the existing record will be returned."
But I do want to be 100% clear about this .
If I do:
User.find_or_create_by(first_name: 'Scarlett') do |user|
user.last_name = 'Johansson'
end
and User does exist with both first_name: 'Scarlett' and `last_name: 'Johansson'``, will it update it or completely ignore it?
In my case, I would like to completely ignore it if it exists at all and wondering if find_or_create is the way to go. Because I don't want to bother updating records with the same information. I am not trying to return anything either.
Should I be using find_or_create, or just use exists?
Also, if find_or_create does act as a way to check if it exists and would ignore if it does, would I be able to use it that way?
For example:
User.find_or_create_by(first_name: 'Scarlett') do |user|
user.last_name = 'Johansson'
puts "Hello" #if it doesn't exist
end
Would "Hello" puts if it doesn't exist and not puts if it does?
In the example, if you have one or more User records with the first name 'Scarlett', then find_or_create_by will return one of those records (using a LIMIT 1 query). Your code, as provided, will set - but not save - the last_name of that record to 'Johansson'.
If you do not have one or more records with the first name 'Scarlett', then a record will be created and the field first_name will have the value 'Scarlett'. Again, the last_name field will be set to 'Johansson', but will not be saved (in the code you provide; you might save it elsewhere).
In this code:
User.find_or_create_by(first_name: 'Scarlett') do |user|
user.last_name = 'Johansson'
puts "Hello" #if it doesn't exist
end
...you will always see "Hello" because find_or_create_by will always return a record (either a found one or a created one).

How to catch records that fail when trying to create in Rails

I am creating new records, and want to be able to catch the ones that error and add them to my activity log so I can look into them and why they may have failed.
What would be the best way to do this?
Currently, I am creating categories bu passing over an array of categories.
Category.create(categories)
I have validation on the name of the category to make sure its unique.
I would like to see which ones have failed and also have a total count of which ones were created vs failed. For example...
xyz failed to be created.
Created 99/100 categories, 1 failed.
I am using a simple activity log which I have made myself. And would like to have these details stored in the "log_msg"
ActivityLog.create(:act_type => "Insert", :updated_by => "System", :activity => log_msg, :act_tstamp => Time.now)
Any suggestions would be much appreciated.
You can adjust this code to your needs:
invalid_categories = []
categories.each do |category|
begin
Category.create!(category)
rescue ActiveRecord::RecordInvalid => exception
invalid_categories << category
end
end
log_msg = invalid_categories.map { |category| "#{category} failed to be created" }
log_msg << "Created #{categories.size - invalid_categories.size}/#{categories.size}, #{invalid_categories.size} failed"
log_msg = log_msg.join("\n")
puts log_msg # log somehow the message
Also take a look at this article because of:
Rails does many things, but data integrity validations are not one of
them. Your relational database is designed to enforce data integrity

Displaying from my database

I'm new to rails and I'm working on a project where I'm having an issue. I'm trying to display all the gyms that have the same zipcode. When I tried the code below, it only displays 1 and not the other ones. How can display all the gym that have the same zip code?
controller
def gym
#fitness = Fitness.find_by(zip_code: params[:zip_code])
end
gym.html.erb
<%= #fitness.name %>
You're doing this to yourself. By definition, #find_by only returns a single record, or nil. You probably want #where instead:
Fitness.where(zip_code: params[:zip_code])
If that still doesn't work, check both your table data and the content of your params hash to make sure you're creating a valid query.
def gyms
#fitness = Fitness.where("zip_code = ?", params[:zip_code])
end

Simple Mongoid Validation for create! - how to display error messages

I'm using Rails 3 with mongoid 2 and have a simple question regarding mongoid validation.
if #forum.topics.create!(name: params[:topic][:name])
# success, do something
else
#should handle errors but doesn't
render 'new'
end
If I use the .create! method, it runs validations on a mongoid model class correctly, but it is not getting to the else block to display the error. Instead it returns a rails error page saying...
Mongoid::Errors::Validations in TopicsController#create
Validation failed - Name can't be blank.
That's good, but how do I display that in a view instead of getting an ugly rails error message page?
Try this way:
new_topic = #forum.topics.new(name: params[:topic][:name])
if new_topic.save
# success, do something
else
render 'new', errors: new_topic.errors.full_messages
end
with this way you will have the local variable errors which is a Hash formated like following:
new_topic.errors.full_messages # => ["\"Name\" can't be blank"]
you can rescue the Mongoid::Errors::Validations and use it's instance method to get the errors
new_topic = #forum.topics.new(name: params[:topic][:name])
new_topic.create!
rescue Mongoid::Errors::Validations => e
summary = e.summary
problem = e.problem
res = e.resolution
using the above error messages you can display the error
Documentaion link
https://docs.mongodb.com/mongoid/6.2/api/Mongoid/Errors/Validations.html

Resources