Search code examples
sqlruby-on-railsrubyruby-on-rails-7sqlite3-ruby

Can't delete User due to SQLite3::ConstraintException: FOREIGN KEY constraint failed (ActiveRecord::InvalidForeignKey) error


I'm a beginner working on a Ruby on Rails application where I want to ensure that deleting a user also deletes their associated messages, participants, and rooms. I have set up associations, but I am facing issues with the deletion process. It's a business directory that has a chat feature and after building it out (following some tutorial) I'm discovering I can't delete the User

I have the following models: User

class User < ApplicationRecord
    # Include default devise modules. Others available are:
    # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
    devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :validatable

    # definine the relationships
    has_many :services, dependent: :destroy
    has_many :reviews, dependent: :destroy
    has_many :favorites, dependent: :destroy
    has_many :messages,  dependent: :destroy
    has_many :participants, dependent: :destroy                   # Add this line
    has_many :rooms, through: :participants, dependent: :destroy  # added to test

    # used to display all users except the current user
    #change to (somehow) only show users that have started a chat with them
    scope :all_except, ->(user) { where.not(id: user) }

    def self.with_messages
      joins(:messages).where.not(messages: { body: nil }).distinct
    end


    # broadcast that a new user has been added
    after_create_commit { broadcast_append_to "users" }


    # roles that say what kind of user they are
    enum role: { customer: 0, businessperson: 1, admin: 2 }

    #validates that the username is unique
    validates :username, presence: true, uniqueness: true

    #makes sure the user inputs a phone number in an appropriate format 0XXXXXXXXX
    validates :phone_number, format: { with: /\A0\d{9}\z/, message: 'must start with 0 and be 10 digits' }, allow_blank: true

    # UNCOMMENT after you've seeded user in the schema with :profile picture
    #mount_uploader :profile_picture, ProfilePictureUploader # to upload profile picture

    
    def self.ransackable_attributes(auth_object = nil)
      ["created_at", "email", "encrypted_password", "firstname", "id", "lastname", "password_digest", "phone_number", "profession", "remember_created_at", "reset_password_sent_at", "reset_password_token", "role", "updated_at", "username"]
    end


    def average_rating
      services_with_reviews = services.includes(:reviews).where.not(reviews: { rating: nil })
      total_ratings = services_with_reviews.sum { |service| service.reviews.average(:rating).round(0) }
      
      if services_with_reviews.size.positive?
        total_ratings / services_with_reviews.size
      else
        0 # or any default value you prefer when there are no reviews available
      end
    end
    

    def total_num_ratings
      services_with_reviews = services.includes(:reviews).where.not(reviews: { rating: nil })
      services_with_reviews.size
    end


    def self.find_for_database_authentication(warden_conditions)
      conditions = warden_conditions.dup
      if login = conditions.delete(:login)
        where(conditions.to_h).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
      else
        find_by(conditions)
      end
    end
  

  end

Participants

class Participant < ApplicationRecord
  belongs_to :user
  belongs_to :room
end

Room

class Room < ApplicationRecord
    validates_uniqueness_of :name
    scope :public_rooms, -> { where(is_private: false) }
    after_create_commit { broadcast_if_public } #only broadcasting a channel/room if it's public
    
    has_many :messages, dependent: :destroy
    has_many :participants, dependent: :destroy
  
    # broadcast the adding of a room if its a public one
    def broadcast_if_public
      broadcast_append_to 'rooms' unless is_private
    end
  

    def self.create_private_room(users, room_name)
      single_room = Room.create(name: room_name, is_private: true)
      users.each do |user|
        Participant.create(user_id: user.id, room_id: single_room.id)
      end
      single_room
    end

    def participant?(room, user)
      room.participants.where(user: user).exists?
    end

  end

Message

class Message < ApplicationRecord
  belongs_to :user
  belongs_to :room
  after_create_commit { broadcast_append_to room }
  before_create :confirm_participant

  def confirm_participant
    return unless room.is_private

    is_participant = Participant.where(user_id: user.id, room_id: room.id).first
    throw :abort unless is_participant
  end
end

My Schema

ctiveRecord::Schema[7.0].define(version: 2023_11_17_214308) do
  create_table "active_admin_comments", force: :cascade do |t|
    t.string "namespace"
    t.text "body"
    t.string "resource_type"
    t.integer "resource_id"
    t.string "author_type"
    t.integer "author_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["author_type", "author_id"], name: "index_active_admin_comments_on_author"
    t.index ["namespace"], name: "index_active_admin_comments_on_namespace"
    t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource"
  end

  create_table "admin_users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_admin_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true
  end

  create_table "messages", force: :cascade do |t|
    t.integer "user_id", null: false
    t.integer "room_id", null: false
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["room_id"], name: "index_messages_on_room_id"
    t.index ["user_id"], name: "index_messages_on_user_id"
  end

  create_table "participants", force: :cascade do |t|
    t.integer "user_id", null: false
    t.integer "room_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["room_id"], name: "index_participants_on_room_id"
    t.index ["user_id"], name: "index_participants_on_user_id"
  end

  create_table "rooms", force: :cascade do |t|
    t.string "name"
    t.boolean "is_private", default: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", force: :cascade do |t|
    t.string "username"
    t.string "email", default: "", null: false
    t.string "password_digest"
    t.string "profession"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "role", default: 0
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.string "firstname"
    t.string "lastname"
    t.string "phone_number"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  add_foreign_key "messages", "rooms", on_delete: :cascade
  add_foreign_key "messages", "users", on_delete: :cascade
  add_foreign_key "participants", "rooms"
  add_foreign_key "participants", "users"
  add_foreign_key "services", "locations"
end

The error message plus something a commenter asked me to do I though i might as well add the results of his suggestion (unless i got it wrong somewhere)

irb(main):013:0> User.last.destroy
  User Load (21.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
  TRANSACTION (1.6ms)  begin transaction
  Service Load (2.9ms)  SELECT "services".* FROM "services" WHERE "services"."user_id" = ?  [["user_id", 8]]
  Review Load (1.1ms)  SELECT "reviews".* FROM "reviews" WHERE "reviews"."user_id" = ?  [["user_id", 8]]
  Favorite Load (0.8ms)  SELECT "favorites".* FROM "favorites" WHERE "favorites"."user_id" = ?  [["user_id", 8]]
  Message Load (1.4ms)  SELECT "messages".* FROM "messages" WHERE "messages"."user_id" = ?  [["user_id", 8]]
  User Destroy (23.9ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 8]]
  TRANSACTION (1.6ms)  rollback transaction
C:/Users/XW3R/.local/share/gem/ruby/3.2.0/gems/sqlite3-1.6.5-x64-mingw-ucrt/lib/sqlite3/statement.rb:108:in `step': SQLite3::ConstraintException: FOREIGN KEY constraint failed (ActiveRecord::InvalidForeignKey)
C:/Users/XW3R/.local/share/gem/ruby/3.2.0/gems/sqlite3-1.6.5-x64-mingw-ucrt/lib/sqlite3/statement.rb:108:in `step': FOREIGN KEY constraint failed (SQLite3::ConstraintException)
irb(main):014:0> ActiveRecord::Base.connection.execute("PRAGMA foreign_keys = OFF")
   (0.4ms)  PRAGMA foreign_keys = OFF
=> []
irb(main):015:0> ActiveRecord::Base.connection.execute("PRAGMA foreign_key_check")
   (1.9ms)  PRAGMA foreign_key_check
=> []
irb(main):016:0> ActiveRecord::Base.connection.execute("PRAGMA foreign_keys = ON")
   (0.1ms)  PRAGMA foreign_keys = ON
=> []
irb(main):017:0>

Checking what's violating the contraint

irb(main):017:0> ActiveRecord::Base.connection.execute("PRAGMA foreign_keys = OFF")
   (2.2ms)  PRAGMA foreign_keys = OFF
=> []
irb(main):018:0> User.last.destroy
  User Load (1.5ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
  TRANSACTION (0.2ms)  begin transaction
  Service Load (0.6ms)  SELECT "services".* FROM "services" WHERE "services"."user_id" = ?  [["user_id", 8]]
  Review Load (0.3ms)  SELECT "reviews".* FROM "reviews" WHERE "reviews"."user_id" = ?  [["user_id", 8]]
  Favorite Load (0.4ms)  SELECT "favorites".* FROM "favorites" WHERE "favorites"."user_id" = ?  [["user_id", 8]]
  Message Load (1.7ms)  SELECT "messages".* FROM "messages" WHERE "messages"."user_id" = ?  [["user_id", 8]]
  User Destroy (2.6ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 8]]
  TRANSACTION (10.9ms)  commit transaction
(Object doesn't support #inspect)
=>
irb(main):019:0> ActiveRecord::Base.connection.execute("PRAGMA foreign_key_check")
   (6.9ms)  PRAGMA foreign_key_check
=> [{"table"=>"participants", "rowid"=>13, "parent"=>"users", "fkid"=>1}]
irb(main):020:0> ActiveRecord::Base.connection.execute("PRAGMA foreign_keys = ON")
   (0.1ms)  PRAGMA foreign_keys = ON
=> []
irb(main):021:0>

After adding belongs_to :participant to Message model and restarting the console

PS C:\Users\XW3R\Documents\GitHub\WindhoekTradespersonPlatform> rails console
Loading development environment (Rails 7.0.8)
irb(main):001:0> User.last.destroy
  User Load (0.5ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
  TRANSACTION (0.1ms)  begin transaction
  Service Load (0.2ms)  SELECT "services".* FROM "services" WHERE "services"."user_id" = ?  [["user_id", 7]]
  Review Load (0.1ms)  SELECT "reviews".* FROM "reviews" WHERE "reviews"."service_id" = ?  [["service_id", 36]]
  Image Load (0.2ms)  SELECT "images".* FROM "images" WHERE "images"."service_id" = ?  [["service_id", 36]]
  Favorite Load (0.2ms)  SELECT "favorites".* FROM "favorites" WHERE "favorites"."service_id" = ?  [["service_id", 36]]
  Service Destroy (1.1ms)  DELETE FROM "services" WHERE "services"."id" = ?  [["id", 36]]
  Review Load (0.2ms)  SELECT "reviews".* FROM "reviews" WHERE "reviews"."user_id" = ?  [["user_id", 7]]
  Favorite Load (0.2ms)  SELECT "favorites".* FROM "favorites" WHERE "favorites"."user_id" = ?  [["user_id", 7]]
  Participant Load (0.2ms)  SELECT "participants".* FROM "participants" WHERE "participants"."user_id" = ?  [["user_id", 7]]
  Participant Destroy (0.4ms)  DELETE FROM "participants" WHERE "participants"."id" = ?  [["id", 3]]
  Room Load (0.2ms)  SELECT "rooms".* FROM "rooms" WHERE "rooms"."id" = ? LIMIT ?  [["id", 22], ["LIMIT", 1]]
  Message Load (0.3ms)  SELECT "messages".* FROM "messages" WHERE "messages"."room_id" = ?  [["room_id", 22]]
  Message Destroy (0.4ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 11]]
  Message Destroy (0.2ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 12]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 13]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 14]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 15]]
  Message Destroy (0.2ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 16]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 17]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 18]]
  Message Destroy (0.9ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 19]]
  Message Destroy (0.2ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 20]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 21]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 22]]
  Message Destroy (0.2ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 23]]
  Message Destroy (0.3ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 24]]
  Message Destroy (0.2ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 25]]
  Message Destroy (0.2ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 26]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 27]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 28]]
  Message Destroy (0.6ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 29]]
  Message Destroy (0.2ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 30]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 31]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 32]]
  Message Destroy (0.1ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 33]]
  Message Destroy (0.2ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 35]]
  Message Destroy (0.2ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 36]]
  Message Destroy (0.2ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 41]]
  Message Destroy (0.4ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 42]]
  Participant Load (0.2ms)  SELECT "participants".* FROM "participants" WHERE "participants"."room_id" = ?  [["room_id", 22]]
  Participant Destroy (0.4ms)  DELETE FROM "participants" WHERE "participants"."id" = ?  [["id", 4]]
  TRANSACTION (2.5ms)  rollback transaction
=> false
irb(main):002:0>

EDIT I've tried messing around with the relations, adding stuff and deleting stuff so much so that I'm worried I may have made things worse.

I've added the rest of the code for the models maybe you'll see something. And thank you for the comments so far even though I hadn't given you everything.

Also from what i understand the reason for the participant's class is to have something to check if a person was a participant of that room/conversation. Or to add them as a participant.

To be honest, now that I think about it using users instead of participants would have been better. I regret following that but I'm here now.

EDIT 2 Added the error Code

EDIT 3 Added @Alex's suggestion to see what was violating the constraint

EDIT 4 addedbelongs_to :participant to Message model and restarted the console. Tried User.last.destroy and got false

EDIT 5 Not sure if I messed something up so I'm reposting my models. Below that is an error that I'm getting. It worked but now it's not working so I think there's something I did.

User

class User < ApplicationRecord
    # Include default devise modules. Others available are:
    # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
    devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :validatable

    # definine the relationships
    has_many :services, dependent: :destroy
    has_many :reviews, dependent: :destroy
    has_many :favorites, dependent: :destroy
    has_many :messages, dependent: :destroy
    has_many :participants, dependent: :destroy                   # Add this line
    has_many :rooms, through: :participants                       # added to test # deleted "dependent: :destroy "

    # used to display all users except the current user
    #change to (somehow) only show users that have started a chat with them
    scope :all_except, ->(user) { where.not(id: user) }

    def self.with_messages
      joins(:messages).where.not(messages: { body: nil }).distinct
    end


    # broadcast that a new user has been added
    after_create_commit { broadcast_append_to "users" }


    # roles that say what kind of user they are
    enum role: { customer: 0, businessperson: 1, admin: 2 }

    #validates that the username is unique
    validates :username, presence: true, uniqueness: true

    #makes sure the user inputs a phone number in an appropriate format 0XXXXXXXXX
    validates :phone_number, format: { with: /\A0\d{9}\z/, message: 'must start with 0 and be 10 digits' }, allow_blank: true

    # UNCOMMENT after you've seeded user in the schema with :profile picture
    #mount_uploader :profile_picture, ProfilePictureUploader # to upload profile picture

    
    def self.ransackable_attributes(auth_object = nil)
      ["created_at", "email", "encrypted_password", "firstname", "id", "lastname", "password_digest", "phone_number", "profession", "remember_created_at", "reset_password_sent_at", "reset_password_token", "role", "updated_at", "username"]
    end


    def average_rating
      services_with_reviews = services.includes(:reviews).where.not(reviews: { rating: nil })
      total_ratings = services_with_reviews.sum { |service| service.reviews.average(:rating).round(0) }
      
      if services_with_reviews.size.positive?
        total_ratings / services_with_reviews.size
      else
        0 # or any default value you prefer when there are no reviews available
      end
    end
    

    def total_num_ratings
      services_with_reviews = services.includes(:reviews).where.not(reviews: { rating: nil })
      services_with_reviews.size
    end


    def self.find_for_database_authentication(warden_conditions)
      conditions = warden_conditions.dup
      if login = conditions.delete(:login)
        where(conditions.to_h).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
      else
        find_by(conditions)
      end
    end
  

  end

Messages

class Message < ApplicationRecord
  belongs_to :user
  belongs_to :room
  belongs_to :participant
  
  after_create_commit { broadcast_append_to room }
  before_create :confirm_participant

  def confirm_participant
    return unless room.is_private

    is_participant = Participant.where(user_id: user.id, room_id: room.id).first
    throw :abort unless is_participant
  end
end

Participants

class Participant < ApplicationRecord
  belongs_to :user
  belongs_to :room # dependent: :destroy removed recentely
end

Review

class Review < ApplicationRecord
    belongs_to :user, dependent: :destroy
    belongs_to :service, dependent: :destroy

    # only have one review UNCOMMENT LATER
    #validates :service_id, uniqueness: { scope: :user_id, message: "You have already reviewed this service" }
  end

The error

PS C:\Users\XW3R\Documents\GitHub\WindhoekTradespersonPlatform> rails console
Loading development environment (Rails 7.0.8)
irb(main):001:0>  ActiveRecord::Base.connection.execute("PRAGMA foreign_keys = OFF")
   (0.1ms)  PRAGMA foreign_keys = OFF
=> []
irb(main):002:0> User.last.destroy
  User Load (16.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
  TRANSACTION (0.1ms)  begin transaction
  Service Load (0.7ms)  SELECT "services".* FROM "services" WHERE "services"."user_id" = ?  [["user_id", 6]]
  Review Load (0.9ms)  SELECT "reviews".* FROM "reviews" WHERE "reviews"."service_id" = ?  [["service_id", 1]]
  Review Destroy (10.4ms)  DELETE FROM "reviews" WHERE "reviews"."id" = ?  [["id", 8]]
  User Load (0.6ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Service Load (0.3ms)  SELECT "services".* FROM "services" WHERE "services"."user_id" = ?  [["user_id", 2]]
  Review Load (0.1ms)  SELECT "reviews".* FROM "reviews" WHERE "reviews"."service_id" = ?  [["service_id", 38]]
  Image Load (0.6ms)  SELECT "images".* FROM "images" WHERE "images"."service_id" = ?  [["service_id", 38]]
  Favorite Load (0.4ms)  SELECT "favorites".* FROM "favorites" WHERE "favorites"."service_id" = ?  [["service_id", 38]]
  Service Destroy (1.4ms)  DELETE FROM "services" WHERE "services"."id" = ?  [["id", 38]]
  Review Load (11.1ms)  SELECT "reviews".* FROM "reviews" WHERE "reviews"."service_id" = ?  [["service_id", 39]]
  Image Load (0.4ms)  SELECT "images".* FROM "images" WHERE "images"."service_id" = ?  [["service_id", 39]]
  Favorite Load (0.1ms)  SELECT "favorites".* FROM "favorites" WHERE "favorites"."service_id" = ?  [["service_id", 39]]
  Service Destroy (0.2ms)  DELETE FROM "services" WHERE "services"."id" = ?  [["id", 39]]
  Review Load (0.6ms)  SELECT "reviews".* FROM "reviews" WHERE "reviews"."service_id" = ?  [["service_id", 40]]
  Image Load (0.1ms)  SELECT "images".* FROM "images" WHERE "images"."service_id" = ?  [["service_id", 40]]
  Favorite Load (0.2ms)  SELECT "favorites".* FROM "favorites" WHERE "favorites"."service_id" = ?  [["service_id", 40]]
  Service Destroy (0.2ms)  DELETE FROM "services" WHERE "services"."id" = ?  [["id", 40]]
  Review Load (0.7ms)  SELECT "reviews".* FROM "reviews" WHERE "reviews"."user_id" = ?  [["user_id", 2]]
  Review Destroy (1.2ms)  DELETE FROM "reviews" WHERE "reviews"."id" = ?  [["id", 5]]
  TRANSACTION (12.2ms)  rollback transaction
=> false
irb(main):003:0> ActiveRecord::Base.connection.execute("PRAGMA foreign_key_check")
   (2.4ms)  PRAGMA foreign_key_check
=> [{"table"=>"participants", "rowid"=>13, "parent"=>"users", "fkid"=>1}]
irb(main):004:0> exit

Solution

  • First, a few notes:

    has_many :participants, dependent: :destroy
    has_many :rooms, through: :participants, dependent: :destroy
    # this `dependent: :destroy` is redundant ^
    
    # so is this one    v
    has_many :messages, dependent: :destroy
    # because you have sqlite deleting it  v
    add_foreign_key "messages", "users", on_delete: :cascade
    
    # messages should probably belong to Participant in this set up
    class Message < ApplicationRecord
      belongs_to :participant
    end
    

    The code that you show works, I assume you're not showing everything. This is the only way I know how to debug this:

    User.last.destroy
    #=> FOREIGN KEY constraint failed (SQLite3::ConstraintException) ¯\_(ツ)_/¯
    
    # disable foreign key constraints
    ActiveRecord::Base.connection.execute("PRAGMA foreign_keys = OFF")
    
    # this should work now (you have to run this again)
    User.last.destroy
    
    # check foreign keys manually to get a helpful error
    ActiveRecord::Base.connection.execute("PRAGMA foreign_key_check")
    #=> [{"table"=>"payments", "rowid"=>1, "parent"=>"users", "fkid"=>0}]
    # now you know where ^
    
    # turn it back on
    ActiveRecord::Base.connection.execute("PRAGMA foreign_keys = ON")