class Person < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }
end
When a model has above definition, what exactly is happening behind the scenes?
My guess is there exists some validates method and a parameter is passed with symbol name. What is second parameter? a hash with a value that is hash?
First validation :name lets know that Person is not valid without a name attribute.
Second validation uniqueness
This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index on that column in your database.
Third { case_sensitive: false }
There is also a case_sensitive option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to true
Finally validates :name, uniqueness: { case_sensitive: false }
It means in Person model name attribute must be present with uniqueness not be case sensitive.
Related
I am working in my ruby on rails assignment that will let users input their first name, middle name and last name. Now I want to validate each of them using uniqueness. Of course, if the first, middle and last name already exist, it cannot be created. But if the first name is the only one that has and existing match in the database and the middle and last name is different, it should be created. Another example is that if only the middle name has an existing match in the database and the other two are different. The problem is that if I use, validates :first_name, :middle_name, :last_name, uniqueness: true, it will not cover the said conditions. Please help me
What you're looking for is :scope keyword
Try
validates :first_name, uniqueness: { scope: [:last_name, :middle_name] }
And refer to documentation
I have this following validation in Playlist model with attributes title and is_deleted. The point of the 'is_deleted' column(bool type column with default value 'false') is cause I have to archive
the playlists not delete them.
validates :title, presence: true, uniqueness: true, unless: :is_deleted?
Now if I have a record for example with title 'Playlist 1' and is_deleted false and try to create a record with is_deleted: true it won't validate him, which is good. If I try to update the is_deleted column from the first record to true it won't validate, which is good as well.
But here is the thing now I have two playlists with title: "Playlist 1" and is_deleted: true. If I try to update any of them to is_deleted: false, the validation won't let me. It gives a "Title has already been taken" error. I really don't understand why, cause I don't have any other record that is is_deleted: false with the title: "Playlist 1".
This is because your validation is checking for the uniqueness of :title only. When it validates your model after changing is_deleted to false it is checking that the title is unique. It'll do a query something like:
Playlist.where(title: 'Playlist 1').where.not(id: self.id).exists?
and obviously there is a playlist with that title already.
The key thing is that it doesn't check the is_deleted flag on other playlists. It is only using it on the current playlist to decide whether it should validate or not.
You probably need to us validates_uniqueness_of rather than just validates which means you can add conditions to the validation:
validates_uniqueness_of :title,
unless: :is_deleted?,
conditions: -> { where(is_deleted: false) }
This will ensure that your uniqueness check only checks against other playlists that are also not deleted.
You need to keep your validates :title, presence: true, unless: :is_deleted? to validate that the title is present (or you may have some success with the allow_nil or allow_blank options on validates_uniqueness_of).
This unless runs in ruby-land. And it does what it says on the tin: "validate uniqueness of title if post is not deleted". Note that it doesn't say "validate uniqueness of title among only non-deleted records".
You should be using database tools to enforce this kind of restrictions. Postgres has partial unique indexes that can do this.
In my Rails 4 app I have an email field. My model ensures that no duplicate values are stored using the following:
validates :email, presence: true, uniqueness: { case_sensitive: false }
This has served me well while I wasn't bothered about confirming email addresses but now I want to confirm them before they are able to login. This means that I only want to confirm uniqueness if another field confirmed is true.
Is there an in-built way to tackle this at all or will it be my own validation rule that's required?
You can pass options such as a record set to a uniqueness validator like so:
validates_uniqueness_of :email, conditions: -> { where(confirmed: true) }
Then it will only enforce uniqueness against confirmed records.
I believe this is what you want:
http://edgeguides.rubyonrails.org/active_record_validations.html#using-a-symbol-with-if-and-unless
I have a model where there are two fields that can technically be null. The field names are :is_activated and :activated_at. :activated_at is only required if :is_activated is set to true. It does not need to be present if :is_activated is false. What's the appropriate way in Rails to set this validation directly into ActiveRecord?
You can use a Proc on the :activated_at validator.
validates :activated_at, :presence, if: Proc.new { |a| a.is_activated? }
Recommended Reading:
http://guides.rubyonrails.org/active_record_validations.html#using-a-proc-with-if-and-unless
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
Finally, you should consider renaming :is_activated to simply :activated. This is considered better practice in Rails. The is_ prefix is used in other languages for boolean attributes because their method names don't support a ? character. Ruby does, and Rails generates ___? methods on boolean attributes. Because of this it's better to just call the attribute :activated and check it with activated?.
Non-Boolean Attributes.
If you're not using a Boolean attribute, like is_activated, and want to ensure that one attribute is present when another, non-Boolean attribute is present, you can simplify it:
validates :activated_at, presence: true, if: :activated_by?
activated_by? will return false when null and true otherwise.
You could do something like this:
validates :activated_at, presence: true, if: :is_activated?
def is_activated?
self.is_activated
end
This will only validate :activated_at if the method is_activated? returns true.
Hi I wan't to validate the unique combination of 3 columns in my table.
Let's say I have a table called cars with the values :brand, :model_name and :fuel_type.
What I then want is to validate if a record is unique based on the combination of those 3. An example:
brand model_name fuel_type
Audi A4 Gas
Audi A4 Diesel
Audi A6 Gas
Should all be valid. But another record with 'Audi, A6, Gas' should NOT be valid.
I know of this validation, but I doubt that it actually does what I want.
validates_uniqueness_of :brand, :scope => {:model_name, :fuel_type}
There is a syntax error in your code snippet. The correct validation is :
validates_uniqueness_of :car_model_name, :scope => [:brand_id, :fuel_type_id]
or even shorter in ruby 1.9.x:
validates_uniqueness_of :car_model_name, scope: [:brand_id, :fuel_type_id]
with rails 4 you can use:
validates :car_model_name, uniqueness: { scope: [:brand_id, :fuel_type_id] }
with rails 5 you can use
validates_uniqueness_of :car_model_name, scope: %i[brand_id fuel_type_id]
Depends on your needs you could also to add a constraint (as a part of table creation migration or as a separate one) instead of model validation:
add_index :the_table_name, [:brand, :model_name, :fuel_type], :unique => true
Adding the unique constraint on the database level makes sense, in case multiple database connections are performing write operations at the same time.
To Rails 4 the correct code with new hash pattern
validates :column_name, uniqueness: {scope: [:brand_id, :fuel_type_id]}
I would make it this way:
validates_uniqueness_of :model_name, :scope => {:brand_id, :fuel_type_id}
because it makes more sense for me:
there should not be duplicated "model names" for combination of "brand" and "fuel type", vs
there should not be duplicated "brands" for combination of "model name" and "fuel type"
but it's subjective opinion.
Of course if brand and fuel_type are relationships to other models (if not, then just drop "_id" part). With uniqueness validation you can't check non-db columns, so you have to validate foreign keys in model.
You need to define which attribute is validated - you don't validate all at once, if you want, you need to create separate validation for every attribute, so when user make mistake and tries to create duplicated record, then you show him errors in form near invalid field.
Using this validation method in conjunction with ActiveRecord::Validations#save does not guarantee the absence of duplicate record insertions, because uniqueness checks on the application level are inherently prone to race conditions.
This could even happen if you use transactions with the 'serializable' isolation level. The best way to work around this problem is to add a unique index to the database table using ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the rare case that a race condition occurs, the database will guarantee the field's uniqueness.
Piecing together the other answers and trying it myself, this is the syntax you're looking for:
validates :brand, uniqueness: { scope: [:model_name, :fuel_type] }
I'm not sure why the other answers are adding _id to the fields in the scope. That would only be needed if these fields are representing other models, but I didn't see an indication of that in the question. Additionally, these fields can be in any order. This will accomplish the same thing, only the error will be on the :model_name attribute instead of :brand:
validates :model_name, uniqueness: { scope: [:fuel_type, :brand] }