Reputation: 3760
In my rails (4.1.6) app, I have a contact
model that has_one :address, :email
I construct a contact and related address and email in a single form using fields_for:
views/contacts/new.html.erb
<%= form_for @contact, ... %>
...
<%= fields_for :address do |address_fields| %>
<%= address_fields.text_field :street, ... %>
<%= address_fields.text_field :city, ... %>
...
<% end %>
<%= fields_for :email do |email_fields| %>
<%= email_fields.text_field :display_name, ... %>
<%= email_fields.text_field :mail_id, ... %>
<% end %>
...
<% end %>
I want email to be required, while address is optional. In other words, if email is not provided, none of the 3 models should be created, but if only email is provided, the email and contact must be created.
One way that does work is to validate the params manually in the contacts_controller#create before constructing anything, and flash[:error] and return without saving if email is not specified, or save it if all is well:
contacts_controller.rb
def create
@contact = Contact.new
if(params_email_valid? params)
@contact.save!
@email = Email.create(...)
@email.save!
...
else
flash[:error] = 'Email must be specified to save a contact'
redirect_to :root
end
end
private:
def params_email_valid? params
!(params[:email][:display_name].blank? || params[:email][:mail_id].blank?)
end
Another way that may work is to drop down to SQL and validate everything through direct SQL calls in a transaction.
However, both of these are not 'the rails way', since validations belong in the models. So, I am trying to use some combination of validates_presence_of, validates_associated and custom validators to validate this scenario. The problem here is that model level validation of associated models requires either self to be already saved in the database, or the associated model to be already saved in the database. Is there a way to validate all these models in a single transaction?
Upvotes: 0
Views: 228
Reputation: 2382
Considering you have appropriate validations in the models:
class Contact <
has_many :addresses
has_many :emails
#add
accepts_nested_attributes_for :addresses, :emails #you can add some validations here to like reject_all if blank? see the docs
end
class Address <
belongs_to :contact
end
class Email <
belongs_to :contact
end
In your CompaniesController
def new
@contact = Contact.new
@contact.addresses.new
@contact.emails.new
end
def create
@contact = Contact.new(contact_params)
if @contact.save
#redirect add flash
else
#add flash
#render action: new
end
protected
def contact_params
#permit(#contact_fields, address_attributes: [#address_fields], email_attributes: [#email_fields])
end
And you would like to modify your form like this
<%= form_for @contact, ... do|f| %>
...
<%= f.fields_for :address do |address_fields| %>
<%= address_fields.text_field :street, ... %>
<%= address_fields.text_field :city, ... %>
...
<% end %>
<%= f.fields_for :email do |email_fields| %>
<%= email_fields.text_field :display_name, ... %>
<%= email_fields.text_field :mail_id, ... %>
<% end %>
...
<% end %>
So accepts_nested_attributes
helps you validate the child as well as the parent and adds [child]_attributes
getters and setters, So normally in your form what was contact[email][display_name]
will become contact[email_attributes][display_name]
Upvotes: 1