I’ve got a child model that has an end_date attribute and a validation which ensures only one record can leave this attribute blank (this is also enforced on the database level). When I have a form that allows updating all existing records as well as creating a new one this validation fails.
class Parent < ApplicationRecord
has_many :children, -> {order("end_date")}, dependent: :destroy
accepts_nested_attributes_for :children, reject_if: :all_blank, allow_destroy: true
end
class Child < ApplicationRecord
belongs_to :parent
validates :end_date, uniqueness: {scope: [:parent]}, unless: :end_date?
end
class Admin::ChildrenController < ApplicationController
before_action :select_parent
def edit
end
def update
@parent.update(parent_params)
unless @parent.errors.any?
redirect_to some_path(@parent), notice: t(".success")
else
flash.now[:alert] = @parent.errors.full_messages[0]
render :edit, status: :unprocessable_entity
end
end
private
def load_parent
@parent = Parents.find(params[:id])
end
def parent_params
params.expect(parent: [
children_attributes: [[:id, :start_date, :end_date, :_destroy]]
])
end
end
Processing by ChildrenController#update as HTML
Parameters: {"authenticity_token"=>"[FILTERED]", "parent"=>{
"children_attributes"=>{
"0"=>{"_destroy"=>"0", "start_date"=>"2024-03-06", "end_date"=>"2024-05-10", "id"=>"28213"},
"1"=>{"_destroy"=>"0", "start_date"=>"2024-05-10", "end_date"=>"2024-07-18", "id"=>"28214"},
"2"=>{"_destroy"=>"0", "start_date"=>"2024-07-18", "end_date"=>"2024-08-21", "id"=>"28215"},
"3"=>{"_destroy"=>"0", "start_date"=>"2024-08-21", "end_date"=>"2024-09-01", "id"=>"28216"},
"4"=>{"_destroy"=>"0", "start_date"=>"2024-09-01", "end_date"=>"2024-10-20", "id"=>"28217"},
"5"=>{"_destroy"=>"0", "start_date"=>"2024-10-20", "end_date"=>"2025-01-01", "id"=>"28218"},
"6"=>{"start_date"=>"2025-01-01"}
}
}, "commit"=>"Update", "id"=>"12345"}
@parent.valid?
> false
@parent.errors.full_messages
> ["Only one record without an end_date is allowed"]
If I do not try to create a new child the update is successful. It appears that Rails is trying to create the new record (for which end_date is blank) before updating the existing records (in which the previous one with a blank end_date should now have this set), triggering the “only one record without an end_date is allowed” validation error.
I’m not sure what the “Rails way” of dealing with such a Catch-22 could be?