'How do you persist an instantiated nested form object when creation fails?
I have models Software and Version. A Software has_many Version's and has_one :first_version
class Software < ApplicationRecord
has_many :versions
has_one :first_version, -> { order(created_at: :asc) },
class_name: "Version", dependent: :destroy
accepts_nested_attributes_for :versions
end
class Version < ApplicationRecord
belongs_to :software
end
I'm building the nested object in the new controller action.
class SoftwaresController < ApplicationController
def new
@software = current_account.softwares.build
@software.build_first_version
end
def create
@software = current_account.softwares.build(software_params)
if @software.save
redirect_to software_path(@software)
else
render :new
end
end
def software_params
params.require(:software).permit(
:name,
first_version_attributes: %i[id release_date],
)
end
end
form:
<%= simple_form_for :software do |f| %>
<%= f.input :name %>
<%= f.simple_fields_for :first_version do |v|%>
<%= v.input :release_date %>
<% end %>
<% end %>
With the above code, if something fails during creation, the nested object is persisted even though the object itself and it's parent do not have an id yet, and so errors are displayed under each field with invalid values.
At the same time, if I comment out the line where I build the nested object, the form does not break, just no nested fields are displayed. This is good.
Now, because the form is reused in the new and edit views and I don't want to let users edit the :first_version through this form nor rely on the view to render it conditionally if @software.new_record? I put the nested object in a global variable and point the nested form to that variable hoping that the same result will be achieved in the edit view because no global variable will exist.
def new
@software = current_account.softwares.build
@first_version = @software.build_first_version
end
form:
<%= simple_form_for :software do |f| %>
<%= f.input :name %>
<%= f.simple_fields_for @first_version do |v|%>
<%= v.input :release_date %>
<% end %>
<% end %>
Problem:
If something goes wrong during creation the object is no longer persisted and the view breaks due to @first_version being nil. So why is the nested object persisted when I do @parent.build_nested_object but not when @nested_object = @parent.build_nested_object ?
Solution 1:[1]
Solving the problem by creating more i_vars can lead to bugs. I think the best option is to disable the field based on a condition and change your view to the following.
<%= simple_form_for @software do |f| %>
<%= f.input :name %>
<%= f.simple_fields_for @software.first_version || @software.build_first_version do |v| %>
<%= v.input :release_date, disabled: (true if @software.first_version.id) %>
<% end %>
<% end %>
Using this view means that you can initialize only @software on your controller.
class SoftwaresController < ApplicationController
def new
@software = current_account.softwares.build
end
end
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Christos-Angelos Vasilopoulos |
