rails model validation in the database - ruby-on-rails

I have a table and have the validation for uniqueness setup in the table. eg.
create table posts (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY UNIQUE,
title varchar(255) unique,
content text
);
Here title is unique. Do also need to inform the model class about this uniqueness? If not when i insert a duplicate title, it gives me error. How do I catch that. Currently rails shows me the backtrace and i could not put my own error messages
def create
#f = Post.new(params[:post])
if #f.save
redirect_to posts_path
else
#flash['message'] = "Duplicated title"
render :action=>'new'
end
end
I am not being redirected to the new and instead show a big backtrace.

Use the validates_uniqueness_of validation. "When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified attribute (that maps to a column)"

You will have to add all of the validations to your models. There is a gem called schema_validations, which will inspect your db for validations and create them in your models for you. https://github.com/lomba/schema_validations

Yes you do as noted in other answers, the answer is validate_uniqueness_of - http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000086. Note, even though you have a validation in your model a race condition does exist where Rails may try and do two inserts unaware of there being a unique record already in the table
When the record is created, a check is performed to make sure that no
record exists in the database with the given value for the specified
attribute (that maps to a column). When the record is updated, the
same check is made but disregarding the record itself.
Because this check is performed outside the database there is still a
chance that duplicate values will be inserted in two parallel
transactions. To guarantee against this you should create a unique
index on the field. See add_index for more information.
So what you have done, by creating a unique index on the database is right, though you may get database driver exceptions in your exception log. There are workarounds for this, such as detecting when inserts happen (through a double click).

The Michael Hartl Rails Tutorial covers uniqueness validation (re. the "email" field) here. It appears the full uniqueness solution is:
Add the :uniqueness validation to the model.
Use a migration to add the unique index to the DB.
Trap the DB error in the controller. Michael's example is the Insoshi people_controller--search for the rescue ActiveRecord::StatementInvalid statement.
Re. #3, it looks like Michael just redirects to the home page on any DB statement exception, so it's not as complex (nor as accurate) as the parsing suggested by #Ransom Briggs, but maybe it's good enough if, as #Omar Qureshi says, the uniqueness constraint covers 99% of the cases.

Related

Delete record with nil id in rails console heroku ruby postgres

I used destroy to delete a record remotely in heroku rails console and now it does not show up if I write
MyModel.find_by(email: 'some#email.com')
but it does show up if I write
MyModel.find_by_or_create_by(email: 'some#email.com')
...except the id is nil. I can't figure out how to get rid of this record. I am using postgres and rails 4
When I try to create a new record with the same email via the web ui, it triggers the uniqueness validation for this ghost record...yet I can't remove the ghost record.
When find_or_create_by returns a record with a nil id, that suggests the find part is failing, and then the create part fails too with validation errors. What do you get from MyModel.find_or_create_by(email: 'some#email.com').errors.full_messages? I'm guessing you see the same uniqueness validation error as you're seeing in the web console.
Is your app using a soft-delete approach, e.g. with a gem like acts_as_paranoid or permanent_records? Those gems change the behavior of destroy so that it does not issue a SQL DELETE command but instead sets a deleted_at column. They also hide soft-deleted records, so that may be why find_by isn't giving you anything. If this is what you're doing, you should make sure your uniqueness validation knows to ignore soft-deleted records. How to do that depends on your soft-delete implementation, but you might find some tips here.
You might want to try straight SQL to see what's really in your database, e.g. using the Heroku psql prompt or this Ruby code: MyModel.unscoped.where(email: "some#email.com")
find_or_create_by will look for the record by the given parameters and create it if it can't be found. If there isn't an id field that means it isn't being saved and your problem is already solved.

Is there a way to generate the friendly_id only if the validations pass?

Currently, the friendly_id is generated before saving. So, if there are any errors in the form it will generate the friendly ID and reload. The problem is I use javascript to edit the form fields in bulk. I pick what fields to edit by going through the ids. For example,to look for category, I generate the script to look for photos_61081719_category The midle part is the ID. If the form reload with errors, this ID turns to the friendly ID thereby breaking the script. Is there a way to fix this?
As #JustMichael has stated, using the before_save callback should do exactly what you want. Here is the the API callback documentation. Technically, you didn't say you were explicitly using that callback, so that might be the problem.
class Category
before_save :generate_friendly_id # this code is only called if there are no errors
private
def generate_friendly_id
# ...
end
end
If the friendly id is still being generated, it may mean your validations are passing. If you are actually changing your model's primary key (meaning you are using friendly_id's value for the Category table's id, you must ensure that this will be a unique value, otherwise the model will pass validation but fail to be created when the underlying database hits a pk not unique constraint.
The built-in uniqueness validation won't work for this because you are generating the friendly_id after the validations.

can find_or_create_by_name update?

when using find_or_create_by_name in rails, if the table is found and you pass in parameters for other attributes, will it update the table? for example:
College.find_or_create_by_name(name: 'University of Pittsburgh', calendar: 'semester')
Say the University of Pittsburgh table has already been created, but the calendar attribute is nil. Will this code update the calendar attribute to make it 'semester'?
Some context... I'm making a website with a bunch of pages for different colleges. Part of the website involves listing a bunch of data for the college. Right now I'm writing my seed file, but I anticipate having to change it. I'd like to have hundreds of schools on the website, and for each school, there are going to be hundreds of different pieces of data. I was thinking that having the seed file and using find_or_create_by_name would be a good way to do this, but if you have a better way, please let me know.
That code won't update the record if it already exists. I would suggest:
#college = College.find_or_initialize_by_name("Robot House!!")
#college.attributes = {
reputation: "partyhouse",
occupants: "robots"
}
#college.save!
You could wrap this in a method if you needed to.
find_or_create_by_ does exactly what it says: it either creates a new item (create saves to database) or finds the existing one, meaning reads it from the database.
It returns false on validation errors when creating an object
So to save changes you use the normal update methods:
if #college= College.find_or_create_by_name(given_attributes) &&
#college.update_attributes(given_attributes)
else
# handle validation errors
end
It won't hit the database twice, because update_attributes does'n apply any changes to newly created objects (but possible changes to existing ones)
To write it more explicit:
#college= College.find_or_create_by_name(given_attributes)
if #college.present?
if #college.update_attributes(given_attributes)
# do your success stuff
else
# handle update validation errors
end
else
# handle find_or_create errors
end

Active Record object loaded from database is invalid

I have an ActiveRecord object that I load from database.
When I call valid? on this object it returns false due to a rails unique constraint not met, at least so the validation says.
I checked the database schema and the unique field also has an index defined, so the uniqueness is also ensured on the database level.
What is going on here and how is this even possible in the first place?
You should check #object.errors.inspect for inspection of what's going on and then fix accordingly.
Also it does matter that when are you checking the validity of an object i.e. before save or after save.
The more elegant way is to use #object.save!
Ruby should tell you what went wrong during the attempt to save the object.
If you do not have unique indexes defined on your database tables, this is what happens!
To be a bit more elaborate: I thought the database had a unique index on the column, but that turned out to be a 'regular' index.
The problem occurred, because at some point in the application, the model got saved without validating it first. Which led to non unique entries in the database. By calling valid? triggers the rails internal routine that checks for uniqueness (however that is implemented) , which returned false, correctly.
Lesson learned: Always make sure to add a unique index at the database level.

mongoid atomic updates in rails

i am having an issue with updating existing documents composite key fields in rails. the associated mongo query for my update_attributes statement seems to be correct, however the object cannot be found afterwards.
for example with an existing object with first_name "Jane" and last_name "Doe"... with my :key being :first_name, :last_name
i hit my update method with:
{"artist"=>{"last_name"=>"Doe", "first_name"=>"John"}, "commit"=>"Update Artist", "id"=>"jane-doe"}
def update
#artist = Artist.find(params[:id])
if #artist.update_attributes(params[:artist])
redirect_to #artist
end
end
which generates the mongo query: MONGODB app_database['artists'].update({"_id"=>"jane-doe"}, {"$set"=>{"_id"=>"john-doe", "first_name"=>"John"}})
which seems correct to me... but when i am redirected to that new artist, it complains about Document not found for class Artist with id(s) john-doe.
and in fact looking at my db from the mongo console, i still see Jane Doe in there. It is something to do with them being composite key fields, since i can update non-key fields just fine.
what am i missing here?
I tried this in an app of my own, and it looks like MongoDB simply doesn't currently allow you to modify the _id field as part of a $set operation (which is what Mongoid uses to perform updates). This is a strange restriction - I've been using Mongo for a year and a half now and I've never run into it.
So, a few options:
Stop using anything for your _id that might need to be changed later. I'd do this - it's a best practice anyway.
Whenever you need to make one of these _id changes, instead create a new record with the new _id attribute and delete the old one. This might get messy though, especially if you have other models that refer your Artist models by id.
File an issue with 10gen asking for this restriction to be lifted. They're very good about responding to users' concerns, but even if they agree, it'll probably take a while to be done.
File an issue with Mongoid to request support for these types of changes (Mongoid could conceivably handle the create + delete mechanism for you), but honestly, it's the kind of edge case that's probably not worth the extra time and code for them to support. It'd be nice if Mongoid raised an error when you tried to do an update like this, at the very least.

Resources