I have a parent that accepts_nested_attributes_for
a child. So, when I have a form for the parent, I need to build
the child so I can display form fields for it as well. What I want to know is: where should I build
the child? In the Model, View, or Controller?
You may be shaking your head and thinking I'm a madman for asking a question like this, but here's the line of thinking that got me here.
I have a Customer
model that accepts_nested_attributes_for
a billing_address
, like so:
class Customer
belongs_to :billing_address, class_name: 'Address'
accepts_nested_attributes_for :billing_address
end
When I present a form for a new Customer
to the user, I want to make sure there is a blank billing_address
, so that the user actually sees fields for the billing_address
. So I have something like this in my controller:
def new
@customer = Customer.new
@customer.build_billing_address
end
However, if the user doesn't fill out any of the billing_address
fields, but tries to submit an invalid form, they will be presented with a form that no longer has fields for the billing_address
, unless I put something like this in the create
action of my controller:
def create
@customer = Customer.new(params[:customer])
@customer.build_billing_address if @customer.billing_address.nil?
end
There is another issue, which is that if a user tries to edit a Customer
, but that Customer
doesn't have an associated billing_address
already, they won't see fields for the billing_address
. So I have to add somethign like this to the controller:
def edit
@customer = Customer.find(params[:id])
@customer.build_billing_address if @customer.billing_address.nil?
end
And something similar needs to happen in the controller's update
method.
Anyway, this is highly repetitive, so I thought about doing something in the model. My initial thinking was to add a callback to the model's after_initialize
event, like so:
class CustomerModel
after_initialize :build_billing_address, if: 'billing_address.nil?'
end
But my spidey sense started tingling. Who's to say I won't instantiate a Customer
in some other part of my code in the future and have this wreak havoc in some unexpected ways.
So my current thinking is that the best place to do this is in the form view itself, since what I'm trying to accomplish is to have a blank billing_address
for the form and the form itself is the only place in the code where I know for sure that I'm about to show a form for the billing_address
.
But, you know, I'm just some guy on the Internet. Where should I build_billing_address
?
Though this advice by Xavier Shay is from 2011, he suggests putting it in the view, "since this is a view problem (do we display fields or not?)":
app/helpers/form_helper.rb:
module FormHelper
def setup_user(user)
user.address ||= Address.new
user
end
end
app/views/users/_form.html.erb:
<%= form_for setup_user(@user) do |f| %>
Note that I had to change the helper method to the following:
def setup_user(user)
user.addresses.build if user.addresses.empty?
user
end
The controller remains completely unchanged.
If you know your model should always have a billing address, you can override the getter for this attribute in your model class as described in the docs:
def billing_address
super || build_billing_address
end
Optionally pass in any attributes to build_billing_address
as required by your particular needs.
You would use build if you want to build up something and save it later. I would say, use it in nested routes.
def create
@address = @customer.billing_addresses.build(params[:billing_address])
if @address.save
redirect_to @customer.billing_addresses
else
render "create"
end
end
Something like that. I also use the build when I'm in the console.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With