Rails3 gem 'nested_form' - how to make optional - ruby-on-rails

I'm adding the gem 'nested_form' to a Rails3 app.
It works fine if you want to add the associated record and you enter data.
But, I want the associated record to be optional = no data typed in.
When I save the form, I get errors saying the associated record is missing required fields.
Client Model:
accepts_nested_attributes_for :locations
Client Form:
<%= simple_nested_form_for #client, :html => {:class => 'form-horizontal'} do |f| %>
...
<h4>Primary Location (optional) ===========</h4>
<%= f.fields_for :locations do |l| %>
<%= l.input :name, :label => 'Name' %>
<%= l.input :address1 %>
<%= l.input :address2 %>
<%= l.input :city %>
<%= l.input :state %>
<%= l.input :zipcode %>
<% end %>
...
Client Controller:
def new
#client = Client.new
#client.locations.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #client }
end
end

You can use :reject_if parameter to make sure the record with blank required fields gets rejected. For example:
accepts_nested_attributes_for :locations, reject_if: proc { |l| l['name'].blank? && l['address1'].blank? }
Check out the documentation for more details: http://api.rubyonrails.org/v3.2.19/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for

Related

Rails - How to validate Form with Nested Attributes?

I am creating a nested form with attributes from different models. I expect all the required attributes to be valid, before a new object is saved.
<%= form for #product do |f| %>
<%= f.fields_for #customer do |g| %>
<%= g.label :name %>
<%= g.text_field :name %>
<%= g.label :email %>
<%= g.text_field :email %>
<%= g.label :city %>
<%= g.text_field :city %>
<%= g.label :state %>
<%= g.text_field :state %>
<%= g.label :zipcode %>
<%= g.text_field :zipcode %>
<% end %>
<%= f.label :product %>
<%= f.text_field :product %>
<%= f.label :quantity %>
<%= number_field(:quantity, in 1..10) %>
<% end %>
Here are my models
class Product < ActiveRecord::Base
belongs_to :customer
validates_associated :customer
validates :product, :presence => "true"
end
class Customer < ActiveRecord::Base
has_one :product
validates :name, :email, presence: true
validates :email, format: { with: /[A-Za-z\d+][#][A-Za-z\d+][.][A-Za-z]{2,20}\z/ }
validates :city, presence: true
validates :zipcode, format: { with: /\A\d{5}\z/ }
end
I added validates_associated to my Product Model, so my form_for #product should require all the customer validations to pass. That means name, email, city and zipcode have to be there and have to be formatted properly.
I fiddled around, and submitted the form without filling in the Customer required fields, and the form was considered valid.
I don't understand where my mistake is.
EDIT
Alright, so by adding validates :customer, the customer attributes are now required. But they aren't actually saved to the database. I think this has to do with my params
def product_params
params.require(:product).permit(:product, :quantity)
end
Do I need to add my Customer Params to my permitted params list?
The validates_associated method only validates the associated object if the object exists, so if you leave the form fields blank, the Product you are creating/editing will validate, because there is no associated Customer.
Instead, assuming you're using Rails 4+, you want to use accepts_nested_attributes_for :customer, along with validates :customer, presence: true in order to required the customer fields in your product form.
If you're using Rails 3, then accepts_nested_attributes_for will not work for a belongs_to association. Instead, your Customer class will need to use accepts_nested_attributes_for :product, and you will need to alter your form view accordingly.
UPDATE
You also need to allow your controller action to accept parameters for the :customer association:
def product_params
params.require(:product).permit(:product, :quantity, :customer_attributes => [:name, :email, :city, :state, :zipcode])
end
It's worth noting that because there is no :id field in your customer form fields, and no :customer_id field in your product form fields, you will create a new customer every time you successfully submit the product form.
try this out:
In Controller create an instance of a product and associated customer as follows:
#product = Product.new
#customer = #product.build_customer
in use this code for form
<%= form for #product do |f| %>
<%= f.fields_for :customer do |g| %>
<%= g.label :name %>
<%= g.text_field :name %>
<%= g.label :email %>
<%= g.text_field :email %>
<%= g.label :city %>
<%= g.text_field :city %>
<%= g.label :state %>
<%= g.text_field :state %>
<%= g.label :zipcode %>
<%= g.text_field :zipcode %>
<% end %>
<%= f.label :product %>
<%= f.text_field :product %>
<%= f.label :quantity %>
<%= number_field(:quantity, in 1..10) %>
<% end %>
i.e use :customer symbol instead of #customer instance variable.
and use accepts_nested_attributes_for helper method in Product model as #Charles said
Complementing the other answers, I control what I receive in the controller, avoiding further action and noticing if a value is not the one I want.
def update
if params[:customer][:product_attributes]["0"][:name] == ""
redirect_to customer_path(#incident), alert: 'You need to add a name'
else
respond_to do |format|
if #customer.update(customer_params)
format.html { redirect_to customer_path(#customer), notice: 'Succesfully updated' }
format.json { render :show, status: :ok, location: #customer }
else
format.html { render :edit }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
end

simple_form select-field turns into input-field after validation

I'm using simple_form combined with twitter boostrap for my rails application. And I've one problem that stops me.
<%= f.input :title %>
<%= f.input :url %>
<%= f.input :tag_list, :label => 'Tags' %>
<%= f.input :type_id, :collection => #types, :label_method => :type_name, :value_method => :id, :include_blank => false %>
<%= f.input :description %>
That creates me nice form. But when the validation fails every input-field shows their error.
But not the select field, it only changes from a select-field into a normal input-field filled with the id and I don't know why.
Any ideas?
For some reasons, it seems Rails + Bootstrap + Simple_form keep you select field after validation when the collection is placed into the field itself instead of your controller's action. This should work :
<%= f.input :title %>
<%= f.input :url %>
<%= f.input :tag_list, :label => 'Tags' %>
<%= f.input :type_id, :collection => Type.all.order('type_name'), :label_method => :type_name, :value_method => :id, :include_blank => false %>
<%= f.input :description %>
Hope this helps.
Well I would need more information to help you out.
Could you post your models? or explain what #types is and what it is for.
This is a basic simple for association:
models/type.rb
belongs_to :post
models/post.rb
has_many :types
views/post/_form.html.haml
= f.association :types
Make sure you have a column named "title" or "name" for types.
You need to set #types in methods create or update before render action new or edit respectively
For example
def new
#your_object = YourObject.new
#types = Type.all
....
end
def create
#your_object = YourObject.new(your_object_params)
respond_to do |format|
if #your_object.save
format.html { redirect_to ... }
format.json { render action: 'show' ... }
else
### ! init data for the form ! ###
#types = Type.all
format.html { render action: 'new' }
format.json { render json: ... }
end
end
end

Nested Form: Resource adds dynamically but doesnt get created?

I am using the nested form gem and i add products dynamically to the form. When i do click "add", another product resource appears but on creation it ERASES the former ones from being created entirely. This is how the scenario goes:
Fill in Location
Choose Date
Fill in Product ( one is already on form)
Add 5 more products (Products 2, 3, 4, 5)
Fill in All Products
"click" Create
Created Product 5
This is how my nested form looks:
<%= nested_form_for #location, :url => products_path(#product) do |f| %>
<%= f.label :business %>
<%= f.text_field :business %>
<%= f.label :address %>
<%= f.text_field :address %>
<%= f.fields_for :product_dates, :url => products_path(#product) do |d| %>
<%= d.label :date %>
<%= d.date_select :date %>
<%= d.fields_for :products, :url => products_path(#product) do |p| %>
<%= p.text_field :name %>
<%= p.text_field :price %>
<%= p.text_field :tag_list %>
<%= p.link_to_remove "Remove Product" %>
<% end %>
<%= d.link_to_add "Add", :products %>
<% end %>
<%= f.submit "Finish" %>
<% end %>
Controller:
class ProductsController < ApplicationController
def new
#location = Location.new
#product = Product.new
product_date = #location.product_dates.build
product_date.products.build
end
def create
#location = Location.create(params[:location])
if #location.save
flash[:notice] = "Products Created."
redirect_to :action => 'index'
else
render :action => 'new'
end
end
Models:
class User < ActiveRecord::Base
devise
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :products, :dependent => :destroy
end
class Location < ActiveRecord::Base
attr_accessible :address, :business, :product_dates_attributes
has_many :products
has_many :product_dates
accepts_nested_attributes_for :product_dates
end
class ProductDate < ActiveRecord::Base
attr_accessible :date, :products_attributes
belongs_to :location
belongs_to :user
has_many :products
accepts_nested_attributes_for :products
end
class Product < ActiveRecord::Base
attr_accessible :name, :price, :tag_list
belongs_to :user
belongs_to :location
belongs_to :product_date
end
Any Suggestions?
First of all remove the url_for declarations on the fields_for declarations so you get
<%= nested_form_for #location, :url => products_path(#product) do |f| %>
<%= f.label :business %>
<%= f.text_field :business %>
<%= f.label :address %>
<%= f.text_field :address %>
<%= f.fields_for :product_dates do |d| %>
<%= d.label :date %>
<%= d.date_select :date %>
<%= d.fields_for :products do |p| %>
<%= p.text_field :name %>
<%= p.text_field :price %>
<%= p.text_field :tag_list %>
<%= p.link_to_remove "Remove Product" %>
<% end %>
<%= d.link_to_add "Add", :products %>
<% end %>
<%= f.submit "Finish" %>
<% end %>
What is really confusing is your whole routing and params approach. It's just not right. You have a form_for #location with a :url products_path(#product) This will right royally cause issues with the params that are sent back and there in lies the problem.
Stick with routing to location controller not the products controller by removing the products_path(#product) form your nested_form_for declaration and you will find that you will have all the necessary records saved but you will most likely need to change the redirect_to declaration in the locations_controller create action and the same for the update_action.
But why use the products controller at all when you are dealing with a location? Again this just isn't natural or intuitive.
One last thing. Your remove links won't work as you have not added the necessary :dependent => :destroy declaration to the has_many declarations and you are also missing the :reject_if procs and the :allow_destroy => true declarations on the accepts_nested_attributes declarations.
Can I strongly suggest that you
1) Use either the locations controller or the products controller not both
I mean link to get to this form link_to the locations controller and set everything up there or use form_for #product rather than #location and handle everything in the products controller
2) watch the railscasts that this gem is based on very closely
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
3) Spend some time learning about how rails form view helpers arrange for the params hash to be organised in the controllers actions. In your case, have a close look at your log file for the parameters that come into the create action as things currently stand.
You will most likely see that the params are not nested as you would exect them to be which is why the nested attributes declaration is not behaving as expected

Using Formtastic's semantic_fields_for with a has_many association

I am trying to create a nested form using formtastic. I've included my code below but am running into some problems that I've also listed below. Any suggestions? Thanks.
# Home model
class Home < ActiveRecord::Base
has_many :home_members
accepts_nested_attributes_for :home_members, :reject_if => :all_blank, :update_only => true, :allow_destroy => true
end
# Form builder in members/new.html.erb
<%= semantic_form_for #home, :url => home_members_path(#home), :html => { :method => :post }, :remote => true do |f| %>
<%= f.inputs do %>
<%= f.semantic_fields_for :home_members do |h| %>
<%= h.input :name %>
<%= h.input :email %>
<%= h.input :birthday, :as => :string %>
<% end %>
<% end %>
# members_controller's new method; #home is set in a before filter
def new
2.times{ #home.home_members.build }
end
A default user is created when a Home is saved. How do I have the form display only the newly created records and not the existing one?
If #1 isn't possible, how do I make the existing record update? I have update_only set on the accepts_nested_attributes_for, but a new record is still created.
I am doing 2.times{ #home.home_members.build } in the controller action. When I print the size of #home.home_members I get 3 (one already exists) as expected. Why is the form only displaying 2 sets of inputs, one being populated with the existing home_member data?
well to answer question 1) show only the newly created objects
# Form builder in members/new.html.erb
<%= semantic_form_for #home, :url => home_members_path(#home), :html => { :method => :post }, :remote => true do |f| %>
<%= f.inputs do %>
<%= f.semantic_fields_for :home_members do |h| %>
<% if h.object.new_record? %>
<%= h.input :name %>
<%= h.input :email %>
<%= h.input :birthday, :as => :string %>
<% end %>
<% end %>
<% end %>
I've used cocoon with success in the past. https://github.com/nathanvda/formtastic-cocoon

How to use a nested form for multiple models in one form?

I'm struggling to come up with the proper way to design a form that will allow me to input data for two different models. The form is for an 'Incident', which has the following relationships:
belongs_to :customer
belongs_to :user
has_one :incident_status
has_many :incident_notes
accepts_nested_attributes_for :incident_notes, :allow_destroy => false
So an incident is assigned to a 'Customer' and a 'User', and the user is able to add 'Notes' to the incident. I'm having trouble with the notes part of the form. Here how the form is being submitted:
{"commit"=>"Create",
"authenticity_token"=>"ECH5Ziv7JAuzs53kt5m/njT9w39UJhfJEs2x0Ms2NA0=",
"customer_id"=>"4",
"incident"=>{"title"=>"Something bad",
"incident_status_id"=>"2",
"user_id"=>"2",
"other_id"=>"AAA01-042310-001",
"incident_note"=>{"note"=>"This is a note"}}}
It appears to be attempting to add the incident_note as a field under 'Incident', rather than creating a new entry in the incident_note table with an incident_id foreign key linking back to the incident.
Here is the 'IncidentNote' model:
belongs_to :incident
belongs_to :user
Here is the form for 'Incident':
<% form_for([#customer,#incident]) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :other_id, "ID" %><br />
<%= f.text_field :capc_id %>
</p>
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>
<p>
<%= label_tag 'user', 'Assign to user?' %>
<%= f.select :user_id, #users.collect {|u| [u.name, u.id]} %>
</p>
<p>
<%= f.label :incident_status, 'Status?' %>
<%= f.select :incident_status_id, #statuses.collect {|s| [s.name, s.id]} %>
</p>
<p>
<% f.fields_for :incident_note do |inote_form| %>
<%= inote_form.label :note, 'Add a Note' %>
<%= inote_form.text_area :note, :cols => 40, :rows => 20 %>
<% end %>
</p>
<p>
<%= f.submit "Create" %>
</p>
<% end %>
And finally, here are the incident_controller entries for New and Create.
New:
def new
#customer = current_user.customer
#incident = Incident.new
#users = #customer.users
#statuses = IncidentStatus.find(:all)
#incident_note = IncidentNote.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #incident }
end
end
Create:
def create
#users = #customer.users
#statuses = IncidentStatus.find(:all)
#incident = Incident.new(params[:incident])
#incident.customer = #customer
#incident_note = #incident.incident_note.build(params[:incident_note])
#incident_note.user = current_user
respond_to do |format|
if #incident.save
flash[:notice] = 'Incident was successfully created.'
format.html { redirect_to(#incident) }
format.xml { render :xml => #incident, :status => :created, :location => #incident }
else
format.html { render :action => "new" }
format.xml { render :xml => #incident.errors, :status => :unprocessable_entity }
end
end
end
I'm not really sure where to look at this point. I'm sure it's just a limitation of my current Rails skill (I don't know much). So if anyone can point me in the right direction I would be very appreciative. Please let me know if more information is needed!
Thanks!
Check api for fields_for method and scroll to one-to-many section.
Your model has many :incident_notes, not one incident_note, that is why it doesn't understand relationship and tries to find a field with this name.
So it should be:
<% f.fields_for :incident_notes do |inote_form| %>
<%= inote_form.label :note, 'Add a Note' %>
<%= inote_form.text_area :note, :cols => 40, :rows => 20 %>
<% end %>
It iterates through all incident_notes assigned to incident and produces fields for each of them.
You also have to build at least one note in you new action, otherwise there will be none:
def new
#incident = Incident.new
#incident.incident_notes.build
# ...
end

Resources