mr_muscle
mr_muscle

Reputation: 2900

Rails model validation - create only one record with specific value

I've got Rating model to evaluate my features by user. When user click like or dislike button under one of my feature (e.g. data_chart) the new Rating record should be created. Example rating record: #<Rating:0x00007f8d839b2630 id: 7, customer_id: 1, actions: 'like', feature_uid: data_chart>. I want to prevent situation like below:

#<Rating:0x00007f8d839b2630 id: 7, customer_id: 1, actions: 'like', feature_uid: 'data_chart'>
#<Rating:0x00007f8d839b2630 id: 8, customer_id: 1, actions: 'dislike', feature_uid: 'data_chart'>

As you see there are two records for the same customer and for the same feature_uid. I want to prevent such scenarios since customers can evaluate each feature only once, without ability to change.

Code below:

  def change
    create_table :ratings do |t|
      t.references :customer, null: false, foreign_key: true
      t.string :action, null: false
      t.string :feature_uid, null: false

      t.timestamps
    end
  end

class Rating < ApplicationRecord
  belongs_to :customer

  validates :feature_uid, inclusion: { in: ['data_chart (...) some_other_features)'] }
  validates :action, inclusion: { in: %w[like dislike] }
end

Upvotes: 1

Views: 1118

Answers (1)

Marian13
Marian13

Reputation: 9228

You can prevent the creation of new records for already existing customer_id and feature_uid pairs using the uniqueness validator.

In your particular case it will look like the following:

class Rating < ApplicationRecord
  belongs_to :customer

  # ...

  validates :feature_uid, uniqueness: { scope: :customer_id }
end

Here is a link to the official docs.


It is also worth mentioning that the uniqueness validator works only on the model level, in other words, it does not create a uniqueness constraint in the database, so it may happen that two different simultaneous database connections create two records with the same values for columns that you intend to be unique.

In order to prevent such cases, you need to create a unique index on the pair of columns in your database. For instance, see the MySQL manual for more details about multiple column indexes or the PostgreSQL manual for examples of unique constraints that refer to a group of columns.


Here is a way how to create a unique index on multiple columns using Rails migrations.

  1. Create a migration.
$ rails generate migration add_unique_index_for_customer_id_and_feature_uid_to_ratings
  1. Modify it.
class AddUniqueIndexForCustomerIdAndFeatureUidToRatings < ActiveRecord::Migration[6.1]
  def change
    add_index(:ratings, [:customer_id, :feature_uid], unique: true)
  end
end
  1. Apply that migration.
$ rails db:migrate

Link to add_index docs.

Upvotes: 1

Related Questions