58

I'm working with Rails and PostgreSQL and have a basic one-to-many relationship going on, one Auction has many Bids. However when I try and delete an auction (that has bids present) I get the following error:

ERROR: update or delete on table "auctions" violates foreign key constraint "fk_rails_43e9021cbf" on table "bids". DETAIL: Key(id)=(1) is still referenced from table "bids".

Deleting auctions with no bids gives no error.

The part that confuses me is that inside my Auction model, I have:

has_many :bids, dependent: :destroy

Error Screen Shot (better_error gem)

Since I have a dependent destroy clause, why am I still getting this error?

EDIT: I've tried dropping the whole DB, then recreating/re-migrating everything - still get the same error.

Matheus Lacerda
  • 5,983
  • 11
  • 29
  • 45
TABISH KHAN
  • 1,553
  • 1
  • 19
  • 32

9 Answers9

48

From Rails v4.2 you can do this:

Create a migration to update the foreign keys

20160321165946_update_foreign_key.rb

class UpdateForeignKey < ActiveRecord::Migration
  def change
    # remove the old foreign_key
    remove_foreign_key :posts, :users

    # add the new foreign_key
    add_foreign_key :posts, :users, on_delete: :cascade
  end
end
Mohamed Ziata
  • 1,186
  • 1
  • 11
  • 21
  • 2
    Thanks , i fixed that typo. – Mohamed Ziata May 02 '16 at 18:35
  • How would I do this in the initial reference creation migration? For example, do I do this: ```ActiveRecord::Migration[5.0] def change add_reference :job_queries, :user, foreign_key: true, on_delete: :cascade end end``` – Ka Mok Oct 09 '16 at 01:00
  • 2
    (From a Rails 5.1.4 perspective) I get the sense that foreign_key is a database-level setting whereas dependent: :delete_all is a model-level setting. If we have a foreign_key set in the database (check your schema.rb), dependent: :delete_all will not suffice, we need on_delete: :cascade at the database level too. I found this article helpful: https://spin.atomicobject.com/2016/10/04/rails-active-record-postgres-optimizing-deletions/. – MSC Aug 21 '18 at 07:25
  • This is a nice fix, and solves the issue for me... but... why do we have to resort getting the DB to clean up when we've told Rails to do this via `dependent: :destroy`? I've not seen this before on other code bases and yet on my current one I am having to use a ton of these! – Ian Vaughan Oct 22 '20 at 08:44
22

Are you using delete or destroy to remove the objects? I think you are using delete and you want to use destroy

See http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Delete+or+destroy-3F

John Naegle
  • 8,077
  • 3
  • 38
  • 47
17

My issue was that i am using @auction.delete (visible in the screenshot I posted) when trying to remove a record.

Delete will ignore any callbacks I have in place. So even though I have a dependent destroy clause, it is not being called - hence Rails is throwing an error. If/When I changed the code to read @auction.destroy, the call-back got invoked and it solved the problem.

Reference: Difference between Destroy and Delete

TABISH KHAN
  • 1,553
  • 1
  • 19
  • 32
12

Are you by chance using the paranoia gem or something like it?

If you are bids are paranoid and auctions are not, you may run into this error.

This would happen because when rails executes the dependent: destroy, it would soft-deletes the bids, but they still actually exist in the DB (they just have the deleted_at column set). Therefore, the foreign key constraint would fail.

steveklbnf
  • 451
  • 4
  • 7
4

Your error is from the database not rails. You need to delete the bids first in your app or change the foreign key constraint in the db to cascade the delete

Tony Hopkinson
  • 20,172
  • 3
  • 31
  • 39
  • 1
    Ive tried "rake db:drop" then re-created the database and re-ran the migration files... still get the same error! – TABISH KHAN Mar 11 '15 at 20:50
  • If there's a foreign key in there, all that would do is drop it and recreate it. You need to search the script(s) see if it in there, on examine the db itself. – Tony Hopkinson Mar 11 '15 at 22:41
  • I too have this problem, and see that its the DB raising the error, but why isn't rails deleting the dependant records first? The OP and I have the model constraint `dependent: :destroy`. – Ian Vaughan Oct 22 '20 at 08:42
2

Other answers are good, but don't mention that sometimes you want to leave the dependent record, but nullify the foreign key.

class Post < ActiveRecord::Base
  has_many :comments, dependent: :nullify
end

Note that this will require ensuring the foreign key column in the database table has null: true

I'm not positive, but you may also need to add optional: true to the belongs to association defined in the dependent model.

karns
  • 5,391
  • 8
  • 35
  • 57
1

Marc Busqué has a very good article about this problem that might can help.

"When ActiveRecord encounters a foreign key violation, it raises an ActiveRecord::InvalidForeignKey exception. Even if in its documentation it just says that it is raised when a record cannot be inserted or updated because it references a non-existent record, the fact is that it is also used in the case we are interested."

With that and a rescue_from we can just add to ApplicationController or to a controller concern:

rescue_from 'ActiveRecord::InvalidForeignKey' do
  # Flash and render, render API json error... whatever
end
0

Here explain how to fix it.

To solve this, you probably want to modify your has_many :comments association in your User model to have a dependent option. Some possibilities:

  • has_many :comments, dependent: :delete_all - just automatically delete them when the user is deleted
  • has_many :comments, dependent: :destroy - like above, but call #destroy on each comment instead of just deleting directly in the db
  • has_many :comments, dependent: :nullify - don't delete comments when the user is deleted, just null out their user_id column

See http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many for more information

antoniolulee
  • 990
  • 6
  • 6
-2

A really simple explanation: the associated table contains at least 1 record joined to the record in the table you're trying to destroy.

To fix this, add dependent: :destroy (assuming a User has_many Posts)

has_many :post, dependent: :destroy
stevec
  • 41,291
  • 27
  • 223
  • 311