Has_one + accepts_nested_attributes_for - po błędnej walidacji jest zawsze tworzony nowy obiekt

W moim projekcie korzystam z Rails 5.1.4 i simple_form, mam takie dwa modele:

class Reservation < ApplicationRecord
  has_one :stretch, dependent: :destroy

  has_many :reservation_additional_services
  has_many :additional_services, through: :reservation_additional_services

  belongs_to :user

  enum status: [:submitted, :cancelled, :confirmed, :altered, :finished]

  accepts_nested_attributes_for :stretch, reject_if: proc { |attributes| attributes.all? { |key, value| value.blank? }}
  # validates :stretch, presence: true
  validates :additional_service_ids, presence: true
end

class Stretch < ApplicationRecord
  belongs_to :reservation
  validates :start_date, :end_date, presence: true
end

kontroller jeżeli chodzi o stretch_attributes to próbowałem już różnych rzeczy i dodałem też id bo myślałem, że może jego brak jest problemem:

class ReservationsController < ApplicationController

  def index
    @reservations = current_user.reservations
  end

  def new
    @reservation = Reservation.new
    @reservation.build_stretch
    @reservation.additional_services.build
  end

  def create
    @reservation = Reservation.new(reservation_prams)
    @reservation.user = current_user

    if @reservation.save
      redirect_to reservations_url
    else
      @reservation.build_stretch unless @reservation.stretch.present?
      @reservation.additional_services.build unless @reservation.additional_services.present?
      render :new, status: 	:not_acceptable
    end
  end

  private
    def reservation_prams
      params.require(:reservation).permit(
        stretch_attributes: [:id, :start_date, :end_date],
        additional_service: [:id, :name, :price]
      )
    end

    def stay_params
      
    end
end

i partial formularza:

= simple_form_for(@reservation) do |f|
  .row
    = f.simple_fields_for :stretch do |sf|
      = sf.hidden_field :id
      = sf.input :start_date, as: :string
      = sf.input :end_date, as: :string
  .row
    = f.submit

Walidacja działa tzn formularz nie przechodzi i rezerwacja nie jest tworzona, ale przy nie wypełnieniu pól, nie wyświetlają się błędy walidacji, a po wyświetleniu @reservation.stretch w widoku widać, że za każdym razem jest tworzony nowy obiekt.

EDIT
Błędy walidacji pojawiają się jak jedno z pól jest wypełnione:

Mógłbyś podać jakie wartości w create zwraca params, a jakie reservation_prams?

*********************PARAMS*****************************
{"utf8"=>"✓","authenticity_token"=>"oWpQrG6oSO9md9DK1zXVElxiaRkMTM1+Hwo+hm02twE6qsNbFDkJmkCfR6fnkBh/sKkselncSCALR033LPXinA==", "reservation"=>{"stretch_attributes"=>{"id"=>"", "start_date"=>"", "end_date"=>""}}, "commit"=>"Create Reservation", "controller"=>"reservations", "action"=>"create"}
*********************RESERVATION PARAMS*****************************
{"stretch_attributes"=><ActionController::Parameters {"id"=>"", "start_date"=>"", "end_date"=>""} permitted: true>}

W modelu Reservation masz
accepts_nested_attributes_for :stretch, reject_if: proc { |attributes| attributes.all? { |key, value| value.blank? }}

więc jeśli stretch składa się jedynie z id, start_date i end_date i wszystkie te atrybuty są puste wtedy usunie taki obiekt - nie w tym problem?

nie ma to jak ENTER misclick w trakcie edycji :wink:

nowy obiekt jest tworzony gdy żadne pole nie jest wypełnione

ale dopiero po nieudanym zapisie

wyrzucił bym ten reject i pozwolił zwalidować modelowi Stretch istnienie jego atrybutów

accepts_nested_attributes_for :stretch # , reject_if: proc { |attributes| attributes.all? { |key, value| value.blank? }}

Proponuje budować stretch przed @reservation.save gdy wykrywasz że sparsowane parametry nie zawierają obiektu zależnego

szczegóły w komentarzach kodu :slight_smile:

  def create
    @reservation = Reservation.new(reservation_prams)
    @reservation.user = current_user
     # dodane
    @reservation.build_stretch unless @reservation.stretch.present?
    @reservation.additional_services.build unless @reservation.additional_services.present?
    # przed save
    if @reservation.save
      redirect_to reservations_url
    else
      # żadne pole nie jest wypełnione
      # warunek  @reservation.stretch.present? == false
      # nie ma "starego" obiektu stretch jest budowany nowy pusty
      # nie było dla niego walidacji bo jesteśmy już po walidacji
       ###przeniesione wyżej### @reservation.additional_services.build unless 
      # ugly hack
      # @reservation.valid?
      # dorzuci błędy
      # lepszym rozwiązaniem jest przeniesienie @reservation.build_stretch unless @reservation.stretch.present?
      # powyżej if @reservation.save
      # wtedy save dorzuci walidację stretch
      # pod warunkiem że nie będziesz robił reject-a
      # tak samo adttitional_services.build wstawił bym przed instrukcją if @reservation.save
      ### przeniesione_wyzej ### @reservation.additional_services.build unless @reservation.additional_services.present?
      render :new, status: 	:not_acceptable
    end
  end

Wszystko śmiga dzięki, nie widziałem, że reject odrzuci mi wszystko i nie powoli wyświetlić błędów.