Proposal: include existing record ID in uniqueness validation error details

Hello Rails community,

I’d like to propose a small enhancement to ActiveRecord’s uniqueness validation: when validation fails because a value is already taken, include the primary key (id) of the existing record in errors.details in addition to the conflicting value.

Rationale:

  • It makes error handling in APIs and UIs more precise — clients can reference the exact existing resource instead of only knowing the duplicate value.

  • It helps debugging (quickly point to the conflicting row).

Two example shapes for the extra metadata:

Option A — use id as an extra key inside the details hash:

record.valid? record.errors.details # => { field: [{ error: :taken, value: 'duplicate_value', id: 1 }] } 

Option B — explicitly name the primary key in the details:

record.valid? record.errors.details # => { field: [{ error: :taken, value: 'duplicate_value', primary_key: 1 }] } 

Implementation considerations:

  • It should not change the semantics of uniqueness validation itself — only the metadata attached to errors.details.

I believe this small change would be valuable for API authors and client apps. If the core team and community find this useful, I’m happy to prepare a prototype and open a pull request implementing the behavior (with tests and an opt-in flag).

Thanks for reading — curious to hear thoughts and potential pitfalls I might have missed.

2 Likes

Maybe un option to enable this…

I think this is a useful little enhancement, and it shouldn’t break any existing code.

I prefer the hash key name in Option B: existing_id.If the validation fails during an update, it wouldn’t be obvious whether id refers to the record that already includes the unique value or the record that was being updated.

1 Like

In terms of getting something like this upstream:

You’d need to include a consumer of the value in your change. I don’t imagine adding an additional value that is not immediately used/consumed immediately by anything else in Rails would be accepted.

I think making the PR would expose the challenges to this, which is possibly a de-optimization of the uniqueness query. But do it!

1 Like

Could you share your use case?

It sounds useful, but at the same time if you only need the ID to know what’s the record with that value, wouldn’t it be sufficient to query for this exact value as you know there’s only one record with it?

Ex: if you get { name: [{ error: :taken, value: "bar" }] } can you query for Model.find_by!(name: “bar”)?

That said, we should consider composite primary keys when implementing this too.

1 Like

In my case, I need to build an idempotent service, so just re-querying by value is not always sufficient.

With a simple validation like:

validates :name, uniqueness: true 

it would indeed be trivial to query Model.find_by!(name: "bar").

But when the uniqueness condition involves more complex rules, such as:

validates :name, uniqueness: { scope: :author_id, if: ->(record) { record.country_name == "Italy" } } validates :name, uniqueness: { scope: :author_id, conditions: -> { where.not(status: "archived") }, unless: ->(record) { record.country_name == "Italy" } } 

it’s no longer straightforward to determine which record is causing the conflict. In my case, I have to repeat the same validator logic just to figure out the existing record.

If the validation error could include something like:

{ name: [{ error: :taken, value: "bar", existing_id: 33 }] } 

then I could simply do:

taken_detail = my_model.errors.details[:name].find { |d| d[:error] == :taken } existing_model = MyModel.find_by(id: taken_detail[:existing_id]) if taken_detail 

This way, I don’t need to duplicate the uniqueness logic, and I can reliably fetch the conflicting record regardless of the rules used.

1 Like

That makes sense. TY for the context.

1 Like