2

I've got a simple form that allows managing the positions at a company. I'm using the accepts_nested_attributes API to achieve this. Users are able to add / remove positions using plus / minus buttons and select the user and position for each. The one validation I'd like to enforce is that a user can not have multiple positions for the same company. I enforced this like this:

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

However, I've noticed an error if a person is removed - then re-added to the same company without saving in between. This is sample illustrates the problem:

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!

Validation failed: Positions user duplicate position to company for user

Can I do anything to allow changes like these without causing the validations on assignments to fail (I also enforce at the database level - meaning these raise 5xx errors on the server)?

Stussa
  • 3,375
  • 3
  • 24
  • 35

1 Answers1

0

While it isn't pretty - you can potentially override positions_attributes= to re-map your data:

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
Kevin Sylvestre
  • 37,288
  • 33
  • 152
  • 232