Rails Validation Error: ActiveRecord::RecordInvalid - ruby-on-rails

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

Related

How is this validation error possible for this code?

The error is ActiveRecord::RecordInvalid: Validation failed: Route must exist.
This is the code:
new_route = Route.create!(new_route_data)
new_points.each_with_index do |point, index|
new_point_data = { route_id: new_route.id,
latitude: point[0],
longitude: point[1],
timestamp: point[2] }
new_point = Point.create!(new_point_data)
end
It is being reported for the new_point = Point.create!(new_point_data) line.
Related Details:
This code is running in a single Sidekiq worker as you see it above (so, the Route isn't being created in one worker, with the Points being created in another worker - this is all inline)
The routes table has almost 3M records
The points table has about 2.5B records
The Point model contains belongs_to :route, counter_cache: true
There are no validations on the Route model
In case it's relevant, the Route model does contain belongs_to :user, counter_cache: true
There are only about 5k records in the users table
Software versions:
Rails 5.1.5
Ruby 2.5.0
PostgreSQL 9.6.7
First of all, your code does not make sense. You are iterating new_point and assigning new_point to some value in the block. So I am assuming that you meant iteration over some collection called data_points
Try this.
In Route model
has_many :points
then:
new_route = ...
data_points.each do |point|
point_data = {latitude: point[0], ...} # do not include route_id
new_route.points.create(point_data) # idiomatic
end
`
You don't need the index, so don't use each_with_index.
It's tough to say what the issue is without seeing what type of validations you have in the Point model.
My guess is that your validation in point.rb is:
validates :route, presence: true
With ActiveRecord relations, you can use this shortcut to avoid explicitly assigning route_id:
new_point_data = { latitude: point[0],longitude: point[1], timestamp: point[2] }
new_route.points.create!(new_point_data)
where new_point data does not have route_id.
You should also rename the new_point that you are assigning in the block since you are writing over the array that you're iterating.

How to avoid duplicate data in my database in ruby on rails

I have scrap a data from another website and saved it in my database which is working fine. However, anytime I refresh my application the scrapped data duplicate itself in my database.Any help would be highly appreciated. Below are my code
require 'open-uri'
require 'nokogiri'
doc = Nokogiri::HTML(open("www.example.com"))
entries = doc.css('.block')
#entriesArray = []
entries.each do |row|
Scrap.create!(
title: title = row.css('h2>a').text,
link: link = row.css('a')[0]['href'],
day: days =row.css('time').text)
#entriesArray << Entry.new(title,link,days)
end
You can use model validation to raise error on create! if any validation fails.
class Scrap < ApplicationRecord
validates_uniqueness_of :title
end
And, you can also use first_or_create method to create a new record only if not exists in the database:
entries.each do |row|
title = row.css('h2>a').text
link = row.css('a')[0]['href']
day = row.css('time').text
Scrap.where(title: title).first_or_create(
title: title,
link: link,
day: day
)
#entriesArray << Entry.new(title,link,days)
end
You should add an uniq index on, for instance, the link column on your database (this is to optimize the find_by and to enforce you'll not have duplicates with the same links, it's not needed, although it makes sense), since they'll be unique (you could go with title too, but they could repeat themselves? - not sure, it depends on what you're fetching)
And then check to see if you already have that link on the database, like so:
entries.each do |row|
scrap = Scrap.create_with(title: row.css('h2>a').text, day: row.css('time').text).find_or_initialize_by(link: row.css('a')[0]['href'])
#entriesArray << Entry.new(title,link,days) if scrap.new_record? && (scrap.save! if scrap.new_record?)
end
(the last if is in case you just want to add the Entry if it's a new Entry, if you want to add it no matter what, just remove from if scrap.new_record? ... until end
You want to add a uniqueness validator to your Scrap model like this:
validates :title, uniqueness: true
validates :link, uniqueness: true
This will prevent the same record from being saved twice.

Active Record callbacks throw "undefined method" error in production with classes using STI

I have many instances in my application where I use single table inheritance and everything works fine in my development environment. But when I release to production (using passenger) I get the following error:
undefined method `before_save' for InventoryOrder:Class
(NoMethodError)
Why would this work in my dev environment and not work in production? Both are using Rails 4.2 and Ruby 2.1.5. Could this be a problem with passenger?
Here is the InventoryOrder class:
class InventoryOrder < Order
def self.model_name
Order.model_name
end
before_save :ensure_only_feed_types
def ensure_only_feed_types
order_products.each do |op|
if !ProductTypes::is_mix?(op.product_type.type)
raise Exceptions::FailedValidations, _("Can't have an inventory order for anything but mixes")
end
end
end
def self.check_if_replenishment_order_is_needed(product_type_id)
prod_type = ProductType.find(product_type_id)
return if prod_type.nil? || prod_type.min_system_should_have_on_hand.nil? || prod_type.min_system_should_have_on_hand == 0
amount_free = Inventory::inventory_free_for_type(product_type_id)
if prod_type.min_system_should_have_on_hand > amount_free
if prod_type.is_mix?
InventoryOrder::create_replenishment_order(product_type_id, prod_type.min_system_should_have_on_hand - amount_free)
else
OrderMoreNotification.create({subject: "Running low on #{prod_type.name}", body: "Should have #{prod_type.min_system_should_have_on_hand} of unreserved #{prod_type.name} but only #{amount_free} is left"})
end
end
end
def self.create_replenishment_order(product_type_id, amount)
# first check for current inventory orders
orders = InventoryOrder.joins(:order_products).where("order_products.product_type_id = ? and status <> ? and status <> ?", product_type_id, OrderStatuses::ready[:id], OrderStatuses::completed[:id])
amount_in_current_orders = orders.map {|o| o.order_products.map {|op| op.amount }.sum }.sum
amount_left_to_add = amount - amount_in_current_orders
if amount_left_to_add > 0
InventoryOrder.create({pickup_time: 3.days.from_now, location_id: Location::get_default_location.id, order_products: [OrderProduct.new({product_type_id: product_type_id, amount: amount_left_to_add})]})
end
end
def self.create_order_from_cancelled_order_product(order_product)
InventoryOrder.create({
pickup_time: DateTime.now.change({ min: 0, sec: 0 }) + 1.days,
location_id: Location::get_default_location.id,
order_products: [OrderProduct.new({
product_type_id: order_product.product_type_id,
feed_mill_job_id: order_product.feed_mill_job_id,
ration_id: order_product.ration_id,
amount: order_product.amount
})],
description: "Client Order for #{order_product.amount}kg of #{order_product.product_type.name} was cancelled after the feed mill job started."
})
end
end
And here is it's parent class:
class Order < ActiveRecord::Base
#active record concerns
include OrderProcessingInfo
belongs_to :client
belongs_to :location
has_many :order_products
before_destroy :clear_order_products
after_save :after_order_saved
before_save :on_before_save
accepts_nested_attributes_for :order_products, allow_destroy: true
after_initialize :init #used to set default values
validate :client_order_validations
def client_order_validations
if self.type == OrderTypes::client[:id] && self.client_id.nil?
errors.add(:client_id, _("choose a client"))
end
end
...
end
Thanks,
Eric
After doing some more digging and with the help of Roman's comment I was able to figure out that this issue was a result of me using an older convention for ActiveRecord::Concerns that works fine on windows but not on unix based systems.
According to this RailsCasts you can define your concerns like this:
In ../models/concerns/order/order_processing_info.rb
class Order
module OrderProcessingInfo
extend ActiveSupport::Concern
included do
end
...
end
But according to this the right way to define the concern would be to
1) Put it in ../models/concerns/[FILENAMEHERE] instead of ../models/concerns/[CLASSNAMEHERE]/[FILENAMEHERE]
2) Define the module without wrapping it in the class like this:
module OrderProcessingInfo
extend ActiveSupport::Concern
included do
end
end
Took some digging to get to the bottom of it but hopefully this might help someone else out there.

Rails 3 - custom validation to check date type

I want to validate my date (which actually have DATE type) in model. So, i try to write for that simle method and run it via validation.
Teacher
class Teacher < ActiveRecord::Base
attr_accessible :teacher_birthday # DATE type!
belongs_to :user
validates :teacher_birthday, :presence => true,
:unless => :date_is_correct?
########
def date_is_correct?
parsed_data = Date._parse(:teacher_birthday)
input_day = parsed_data[:mday]
input_month = parsed_data[:mon]
input_year = parsed_data[:year]
correct_days = 1..31
correct_months = 1..12
correct_year = 1900..2000
if ( correct_days.member? input_day ) and ( correct_months.member? input_month) and
( correct_year.member? input_year)
true
else
errors.add(:teacher_birthday, 'date is invalid')
false
end
end
When i run rspec a lot of tests fail.
TypeError: can't convert Symbol into String
# ./app/models/teacher.rb:56:in `_parse'
# ./app/models/teacher.rb:56:in `date_is_correct?'
I suppose i do something wrong. Can someone tell me what is wrong?
This isn't necessary at all. If Date.parse(:teacher_birthday) returns a date and doesn't raise an exception, you have a valid date.
Date._parse expects a string-value containing a date and will in your code always try to parse 'teacher_birthday'. You need to get the value of the field first and pass the value to Date._parse. ActiveRecord creates methods with the same name as the field to get the value.
Any of the following will work:
Short way
parsed_data = Date._parse(teacher_birthday)
Identically to the first (the self. is added for you during parsing)
parsed_data = Date._parse(self.teacher_birthday)
Explicit way
parsed_data = Date._parse(self[:teacher_birthday])
A new gem has been created to help validate types in rails and an explanatory blog post exists to answer more of the "why" it was created in the first place.
With this library your code would simple be:
class Post < ActiveRecord::Base
validates_type :teacher_birthday, :date
end
This will throw an exception when anything except a a Date is assigned to :teacher_birthday.

Rails 3 - adding an index failsafe to ensure the uniqueness of would-be duplicates

I have a rails question that I have been unable to find an answer for on my own. I apologize if it's very simple or obvious, I'm a complete beginner here.
So I have a column in my db called :client_code, which is defined in the model as the down-cased concatenation of the first character of :first_name, and :last_name. So for instance, if I pull up a new form and enter 'John' for :first_name, and 'Doe' for :last_name, :client_code is automatically given the value of 'jdoe'. Here's the relevant portion of the model code:
class Client < ActiveRecord::Base
before_validation :client_code_default_format
validates_presence_of :first_name, :last_name, :email
validates_uniqueness_of :client_code
...
def client_code_default_format
self.client_code = "#{first_name[0]}#{last_name}".downcase
end
end
I would like to add something to this code so that, in the event that someone enters a different client with the same exact name, it does't fail the uniqueness validation but instead creates a slightly modified :client_code ('jdoe2', for example). I could probably figure out how to add an index to all of them, but I would prefer to only include numbers as a failsafe in the case of duplicates. Can anyone point me in the right direction?
Calculating the number of current matching Client objects with the same client_code should work
def client_code_default_format
preferred_client_code = "#{first_name[0]}#{last_name}".downcase
count = Client.count(:conditions => "client_code = #{preferred_client_code}")
self.client_code = count == 0 ? preferred_client_code : preferred_client_code + count
end
Many thanks to #Dieseltime for his response. I took his suggestion and was able to get the functionality I wanted with some minor changes:
before_validation :format_client_code
validates_presence_of :first_name, :last_name, :email, :company_id
validates_uniqueness_of :client_code
...
def format_client_code
unless self.client_code != nil
default_client_code = "#{first_name[0]}#{last_name}".downcase
count = Client.count(:conditions => "client_code = '#{default_client_code}'")
if count == 0
self.client_code = default_client_code
else
self.client_code = default_client_code + (count + 1).to_s
end
end
end

Resources