Upsert_all с ограничением индекса уникальности

Я столкнулся с дилеммой.

Я создал модель со столбцом wave_order с индексом базы данных уникальности с wavable. wavable является полиморфным (но на самом деле нас это не волнует).

# migration
create_table :invitation_waves do |t|
  t.string :name, null: false
  t.references :wavable, polymorphic: true, index: true
  t.integer :wave_order, null: false
  t.timestamps
end
add_index :invitation_waves, [:wavable_id, :wavable_type, :wave_order], unique: true
# This first time, it's working
InvitationWave.upsert_all([{id: 1, name: "Wave1", wave_order: 0, wavable_id: my_object.id, wavable_class: my_object.class.name}, {id: 2, name: "Wave2", wave_order: 1, wavable_id: my_object.id, wavable_class: my_object.class.name}], unique_by: :id, update_only: :wave_order, record_timestamps: true)

# The second time, it's not because of the db constraint. 
InvitationWave.upsert_all([{id: 1, name: "Wave1", wave_order: 1, wavable_id: my_object.id, wavable_class: my_object.class.name}, {id: 2, name: "Wave2", wave_order: 2, wavable_id: my_object.id, wavable_class: my_object.class.name}], unique_by: :id, update_only: :wave_order, record_timestamps: true)

# => ActiveRecord::RecordNotUnique:
       PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "idx_on_wavable_id_wavable_type_wave_order_39aa14a122"
       DETAIL:  Key (wavable_id, wavable_type, wave_order)=(1, MyObject, 1) already exists.

Есть ли другое решение, кроме этих 3:

  1. Удалить уникальность индекса БД
  2. Обновить вручную каждую строку
  3. Используйте дважды upsert_all (первый раз с неправильными, но доступными wave_orders, второй раз с правильными wave_orders).
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

А как насчет добавления DEFERRABLE INITIALLY IMMEDIATE для ограничения уникальности?

В этом случае PostgreSQL проверяет уникальность перед фиксацией.

def up
  create_table :invitation_waves do |t|
    t.string :name, null: false
    t.references :wavable, polymorphic: true, index: true
    t.integer :wave_order, null: false
    t.timestamps
  end

  execute <<~SQL.squish
    ALTER TABLE invitation_waves
    ADD CONSTRAINT idx_on_wavable_id_wavable_type_wave_order
    UNIQUE (wavable_id, wavable_type, wave_order)
    DEFERRABLE INITIALLY IMMEDIATE
  SQL
end

def down
  drop_table :invitation_waves
end

В новых версиях Rails вы можете применить метод миграции unique_constraint

create_table :invitation_waves do |t|
  t.string :name, null: false
  t.references :wavable, polymorphic: true, index: true   t.integer :wave_order, null: false
  t.timestamps
  t.unique_constraint %i[wavable_id wavable_type wave_order], defferable: :immediate
end

или add_unique_constraint

add_unique_constraint :invitation_waves, %i[wavable_id wavable_type wave_order], defferable: :immediate

Независимо от того, как вы добавите это ограничение, оно будет работать таким образом.

# First query (populate records)
InvitationWave.upsert_all(
  [
    {id: 1, name: "Wave1", wave_order: 0, wavable_id: 1, wavable_type: "MyObject"},
    {id: 2, name: "Wave2", wave_order: 1, wavable_id: 1, wavable_type: "MyObject"}
  ],
  unique_by: :id, update_only: :wave_order, record_timestamps: true
)

# Second query (update records) that failed without DEFERRABLE, but now is ok
InvitationWave.upsert_all(
  [
    {id: 1, name: "Wave1", wave_order: 1, wavable_id: 1, wavable_type: "MyObject"},
    {id: 2, name: "Wave2", wave_order: 2, wavable_id: 1, wavable_type: "MyObject"}
  ],
  unique_by: :id, update_only: :wave_order, record_timestamps: true
)

Оба запроса будут успешными, поскольку ограничение будет проверено в конце оператора (непосредственно перед фиксацией).

Но когда вы попытаетесь выполнить третий запрос,

# Will fail, because there is other record with same wave_order
InvitationWave.find(2).update!(wave_order: 1)

он потерпит неудачу из-за ограничения UNIQUE

Лучший ответ на свете. Вы решаете мою проблему с помощью deferrable: :immediate. Большое спасибо!

brcebn 25.06.2024 13:05

Просто помните, что вам нужно изменить формат дампа схемы с Ruby на SQL, иначе этот индекс просто потеряется при переводе.

max 27.06.2024 12:02

Другие вопросы по теме