class AddUniqueIndexToApiKeys < ActiveRecord::Migration[6.1]
def change
add_index :api_keys, %i[member_id name], unique: true
end
end
class ApiKeys::FindOrCreate
def initialize(member:, name:)
@member = member
@name = name
end
def call
ApiKey.find_by(member: @member, name: @name) || create_with_retry
end
private
def create_with_retry
ApiKey.create!(member: @member, name: @name, token: SecureRandom.hex(24))
rescue ActiveRecord::RecordNotUnique
ApiKey.find_by!(member: @member, name: @name)
end
end
Race conditions happen. The correct “find or create” in production uses a unique constraint and a retry on conflict, not a naive check-then-insert. Let the database serialize the race.