Rails принимает проверки уникальности вложенных атрибутов

У меня есть простая форма, которая позволяет управлять позициями в компании. Я использую accepts_nested_attributes API для достижения этой цели. Пользователи могут добавлять / удалять позиции с помощью кнопок плюс / минус и выбирать пользователя и позицию для каждого. Единственная проверка, которую я хотел бы применить, заключается в том, что пользователь не может иметь несколько позиций для одной и той же компании. Я применил это так:

class User < ActiveRecord::Base
  has_many :positions
end

class Position < ActiveRecord::Base
  belongs_to :user
  belongs_to :company
  validates :title, presence: true
  validates :user, presence: true
  validates :company, presence: :true
  validates :user, uniqueness: { scope: :company }
end

class Company < ActiveRecord::Base
  has_many :positions
  accepts_nested_attributes_for :positions, allow_destroy: true
end

Тем не менее, я заметил ошибку, если человека удаляют, а затем снова добавляют в ту же компанию без сохранения между ними. Это пример иллюстрирует проблему:

company = Company.create(name: "Widgets")
mark = User.create(name: "Mark")
luke = User.create(name: "Luke")
mark_position = Position.create(company: company, user: mark, title: "CTO")
luke_position = Position.create(company: company, user: luke, title: "CFO")

company.positions_attributes = [
  { id: mark_position.id, _destroy: true },
  { id: john_position.id, _destroy: true },
  { user_id: mark.id, title: "CPO" },
  { user_id: john.id, title: "CMO" },
]

company.save!

Проверка не удалась: позиционирует дубликат пользователя в компании для пользователя

Могу ли я что-либо сделать, чтобы изменения, подобные этим, не приводили к сбою проверок назначений (я также применяю принудительно на уровне базы данных - это означает, что на сервере возникают ошибки 5xx)?

1 ответ

Хотя это не красиво - вы можете переопределить positions_attributes= чтобы переназначить ваши данные:

def positions_attributes=(attributes)
  existing = Array(positions)

  attributes.each do |iteration|
    position = existing.find { |position| position.user_id.eql?(iteration[:user_id]) }
    if position
      attributes.select{ |iteration| iteration[:id].eql?(position.id) }.each{ |iteration| iteration.delete(:id) }
      iteration[:id] = position.id
    end
  end

  super attributes
end
Другие вопросы по тегам