class User < ApplicationRecord
has_many :log_entries, dependent: :nullify
end
class LogEntry < ApplicationRecord
belongs_to :user, optional: true
end
This will cause the user_id of all related log entries to be set to NULL when a user is destroyed. This is better than just ignoring the error as it ensures your database maintains referential integrity.
Thanks for the suggestion! I already know about that solution, but I prefer not to use it in this specific case.
The “log” table is too large and deleting a user could produce literally millions of updates… It’s much better to consider logs as immutable records. They are deleted in any case after some time (e.g. 30 or 90 days).
For this reason I was wondering if there is something like belongs_to :user, ignore_not_found: true
In any case i understand that maybe my requirement is very specific.
For now, as a workaround, I simply use a user_id field directly, without the belongs_to
Isn’t belongs_to :user, optional: true all you need here? It depends how you define “ignore”, but it would have the user be nil if it’s not found which sounds like what you want.
For this reason I was wondering if there is something like belongs_to :user, ignore_not_found: true
Gotcha, for performance reasons you want to leave the dangling reference in the log_entries table but if someone does call the user method it won’t error trying to lookup that non-existent user. I actually think it does currently work like that. Below is a quick test script I did to verify:
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'activerecord', require: 'active_record'
gem 'sqlite3'
gem 'minitest'
gem 'minitest-bang', require: 'minitest/bang'
end
require 'minitest/spec'
require 'minitest/autorun'
describe 'belongs_to without referential integrity' do
let!(:user) { User.create! }
let!(:log_entry) { LogEntry.create! user: }
before { user.destroy }
it 'does not error with dangling reference' do
expect( log_entry.reload.user ).must_be_nil
end
end
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
ActiveRecord::Migration.verbose = false
ActiveRecord::Schema.define version: 1 do
create_table :users
create_table :log_entries do |t|
t.belongs_to :user
end
end
class User < ActiveRecord::Base
has_many :log_entries
end
class LogEntry < ActiveRecord::Base
belongs_to :user
end
In the above I did not put dependent: :nullify on the user model or the optional: true on the LogEntry model. I delete the user (so we have a dangling reference in the database) and then try to access the user via the log entry. The test passes not raising an error.
ActiveRecord will skip database queries if you set the id to null, which means it’ll be more performant to not have corrupted referential integrity.
But, is there any reason you can’t just anonymize the account and not actually delete the User record? Then you’ll know that user X did some things (which can be relevant for auditing / accounting purposes) but not know PII about that account anymore.