Rails: Validate unique combination of 3 columns

Ruby on-RailsRuby on-Rails-3

Ruby on-Rails Problem Overview


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}

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

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]

Solution 2 - Ruby on-Rails

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.

Solution 3 - Ruby on-Rails

To Rails 4 the correct code with new hash pattern

validates :column_name, uniqueness: {scope: [:brand_id, :fuel_type_id]}

Solution 4 - Ruby on-Rails

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.

Solution 5 - Ruby on-Rails

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.

Solution 6 - Ruby on-Rails

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] }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNiels KristianView Question on Stackoverflow
Solution 1 - Ruby on-RailsalupView Answer on Stackoverflow
Solution 2 - Ruby on-RailsAlexanderView Answer on Stackoverflow
Solution 3 - Ruby on-RailsRuby Junior DevView Answer on Stackoverflow
Solution 4 - Ruby on-RailsMBOView Answer on Stackoverflow
Solution 5 - Ruby on-RailsTenzin ChemiView Answer on Stackoverflow
Solution 6 - Ruby on-RailsPaul LassiterView Answer on Stackoverflow