Rails Submitting Multiple Has_Many Attributes via Form - ruby-on-rails

I am running a Rails 5.1 app with the following information:
Models
class Company < ApplicationRecord
has_many :complaints
accepts_nested_attributes_for :complaints
validates :name, presence: true
end
class Complaint < ApplicationRecord
belongs_to :company
validates :username, :priority, presence: true
end
Controller
class ComplaintController < ApplicationController
def new
#company = Company.new
#company.complaints.build
end
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
format.html { redirect_to complaint_url }
else
format.html { render :new }
end
end
end
private
def company_params
params.require(:company).permit(:name, complaints_attributes: [:username, :priority])
end
Form in view
<%= form_for #company do |f| %>
<%= f.label :name, "Company" %>
<%= f.text_field :name, type: "text" %>
<%= f.fields_for :complaints do |complaint| %>
<%= complaint.label :username, "Username" %>
<%= complaint.text_field :username %>
<%= complaint.label :priority, "Priority" %>
<%= complaint.text_field :priority %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
If I have just one input field for the complaint_attributes part of the form (in other words just one field for username and one field for priority as shown above), this works just fine.
However, if I want to have multiple fields for username/priority in the form, so that I can submit multiple username/priority combinations in a single submission, I find that submitting the form will only save the last username/priority values from the form. Example of this view would be:
<%= form_for #company do |f| %>
<%= f.label :name, "Company" %>
<%= f.text_field :name, type: "text" %>
<%= f.fields_for :complaints do |complaint| %>
<div>
<%= complaint.label :username, "Username" %>
<%= complaint.text_field :username %>
<%= complaint.label :priority, "Priority" %>
<%= complaint.text_field :priority %>
</div>
<div>
<%= complaint.label :username, "Username" %>
<%= complaint.text_field :username %>
<%= complaint.label :priority, "Priority" %>
<%= complaint.text_field :priority %>
</div>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
I noticed that when submitting the form, I get a hash like this (for submitting single complaint):
{"utf8"=>"✓", "authenticity_token"=>"...", "company"=>{"name"=>"Test", "complaints_attributes"=>{"0"=>{"username"=>"test_person", "priority"=>"1"}}}, "commit"=>"Submit"}
Is there any way to modify the params to make it similar to this and have it saved to the DB?:
{"utf8"=>"✓", "authenticity_token"=>"...", "company"=>{"name"=>"Test", "complaints_attributes"=>{"0"=>{"username"=>"test_person", "priority"=>"1"}"1"=>{"username"=>"test_person", "priority"=>"2"}}}, "commit"=>"Submit"}
Or if not the above, what would be the best way to have the username/priority values saved if using multiple fields for them in a single form?
EDIT: I should point out that I can dynamically add the username/priority field groups as needed, so I don't want to be restricted to a set number.

the second block will override the first fields... you should instead build many complaints in the controller:
def new
#company = Company.new
3.times { #company.complaints.build }
end
and then with the following form it should generate to inputs according to the number of complaints you have built:
<%= form_for #company do |f| %>
<%= f.label :name, "Company" %>
<%= f.text_field :name, type: "text" %>
<%= f.fields_for :complaints do |complaint| %>
<%= complaint.label :username, "Username" %>
<%= complaint.text_field :username %>
<%= complaint.label :priority, "Priority" %>
<%= complaint.text_field :priority %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>

Related

ActionController::InvalidAuthenticityToken When Submitting

I have a program that allows users to enter their song for a certain event. You must enter in the partycode for that event in order for it to submit. Here is a screenshot of what it looks like:
When I submit it gives me this error:
Here is what my SongsController looks like:
class SongsController < ApplicationController
def new
#song = Song.new
end
def create
current_event = Event.find(song_params[:event_id])
#song = current_event.songs.build(song_params)
if #song.save
flash[:success] = "Success"
redirect_to event_path(#song.event)
else
flash[:error] = "Failed"
end
end
def destroy
end
private
def song_params
params.require(:song).permit(:event_id, :artist, :title, :genre)
end
end
event model
class Event < ApplicationRecord
belongs_to :user
validates :name, presence: true
validates :partycode, presence: true, length: {minimum: 5}
has_many :songs, dependent: :destroy
end
song model
class Song < ApplicationRecord
belongs_to :event
validates :artist, presence: true
validates :title, presence: true
end
new.html.erb(song)
<br>
<br>
<h1> Member page </h1>
<div class ="container">
<div class="jumbotron">
<h2> Select an event to add songs to: </h2>
<%= form_for Song.new do |f| %>
<%= f.collection_select(:event_id, Event.all, :id, :name) %>
<h3> Enter your song </h3>
<%= form_for Song.new do |f| %>
<%= f.text_field :artist, placeholder: "Artist" %>
<%= f.text_field :title, placeholder: "Title" %>
<%= f.text_field :genre, placeholder: "Genre" %>
<h2> Enter the partycode for that event: </h2>
<%= form_for Event.new do |f| %>
<%= f.text_field :partycode %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
<% end %>
<% end %>
</div>
</div>
What can I do to make this functionality of my website work? Any help is greatly appreciated thanks
I see many form_for nesting on your views. It's impossible. Only one submit for a form.
I think you may want to change your _form.html.erb
<div class ="container">
<div class="jumbotron">
<h2> Select an event to add songs to: </h2>
<%= form_for #song do |f| %>
<%= f.collection_select(:event_id, Event.all, :id, :name) %>
<h3> Enter your song </h3>
<%= f.text_field :artist, placeholder: "Artist" %>
<%= f.text_field :title, placeholder: "Title" %>
<%= f.text_field :genre, placeholder: "Genre" %>
<h2> Enter the partycode for that event: </h2>
<%= f.text_field :partycode %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
You have completely messed up your form. Ideally you should have one form but here you have just kept one form within another form using form_for.
I would recommend you to have a look at the form_for documentation .

Not able to save data into Model

I can't save the data into my model. Every time when the code run it will ran into the else statement which failed to save the data in the CREATE action. Any idea?
This is my invoices_controller.rb
class InvoicesController < ApplicationController
def new
#permits = Permit.find(params[:permit_id])
#invoice = Invoice.new
end
def create
#permit = Permit.find(params[:permit_id])
#invoice = #permit.build_invoice(invoice_params)
if #invoice.save
redirect_to payment_path
else
redirect_to root_path
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_invoice
#invoice = Invoice.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def invoice_params
params.require(:invoice).permit(:vehicle_type, :name, :department, :carplate, :duration, :permitstart, :permitend, :price, :time)
end
end
Invoices/new.html.erb ( This is the data I wanted to save)
<% provide(:title, 'Invoice') %>
<h1>Invoice</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3" id="datashow">
<%= form_for(#invoice) do |f| %>
<h2>Time : <%=#permits.created_at%></h2></br>
<h2>Invoice ID : <%=#permits.id%></h2></br>
<%= f.label :"Vehicle" %>
<%= f.text_field :vehicle_type, :value => #permits.vehicle_type, readonly: true %>
<%= f.label :"License Plate" %>
<%= f.text_field :carplate, :value => #permits.carplate, readonly: true %>
<%= f.label :"Student ID" %>
<%= f.text_field :studentid, :value => #permits.studentid, readonly: true %>
<%= f.label :name %>
<%= f.text_field :name, :value => #permits.name, readonly: true %>
<%= f.label :"Department of applicant" %>
<%= f.text_field :department, :value => #permits.department, readonly: true %>
<%= f.label :permit_start %>
<%= f.text_field :permitstart, :value => #permits.permitstart, readonly: true %>
<%= f.label :permit_end %>
<%= f.text_field :permitend, :value => #permits.permitend, readonly: true %>
<%= f.label :"Price" %>
<%= (f.text_field :price, :value => '$AUD 50' , readonly: true) %>
<%= hidden_field_tag(:permit_id, #permits.id) %>
<%= f.submit "Make Payment", class: "btn btn-primary" %>
<% end %>
</div>
</div>
Invoice.rb
class Invoice < ApplicationRecord
belongs_to :user
has_one :receipt
belongs_to :permit
end
Permit.rb
class Permit < ApplicationRecord
belongs_to :user
has_one :invoice
end
If you are unsure why your object is not created, you have multiple options.
First you can use #invoice.save! instead of #invoice.save during debugging. This will raise an exception and give you some clues, what's going wrong.
Or you can use a debugger and inspect #invoice.errors.full_messages.
Further more you can output #invoice.errors.full_messages via Rails.logger.error #invoice.errors.full_messages.to_sentence.
Or you can use the error message as a flash message flash[:error] = #item.errors.full_messages.to_sentence
This should help you find the error.
from: build method on ruby on rails
build won't "create" a record in database, just create a new object in memory so that the view can take this object and display something, especially for a form.
So build isn't working because you aren't creating (create and saving) a record. build doesn't save a record.
Try:
def create
#permit = Permit.find(params[:permit_id])
#invoice = #permit.invoices.create(invoice_params)
if #invoice.save
redirect_to payment_path
else
redirect_to root_path
end
end

Is it possible to create two objects from two forms in 1 view and have them related?

I know the title sounded a bit wonky, but this is what I am trying to do.
I have two models - Job and Company. Someone can create a Job listing, but before the listing is saved, it should be associated with a company. This company can be newly created, or should be populated from companies the current_user has previously created.
A job belongs_to :company, and a company has_many :jobs.
I know I could do two different views, and just send the user to two different actions on two different controllers, but I would like to simplify it and just have everything done in one view.
When they go to /jobs/new, they should see the normal jobs/_form.html.erb partial, but I would love to be able to show either a Company dropdown for existing companies or new company creation fields that the user fills out and that new company gets associated with this new job that is being created.
This is what my jobs/_form.html.erb partial looks like:
<%= simple_form_for(#job) do |f| %>
<%= f.input_field :title %>
<%= f.input_field :salary %>
<%= f.input_field :description, as: :text %>
<%= f.input_field :apply_description, as: :text %>
<%= f.input_field :language %>
<%= f.input :premium, as: :boolean, inline_label: true, label: "Make this listing stand out", class: "form-control" %>
<%= f.button :submit, class: "btn btn-lg btn-primary pull-right" %>
<% end %>
This is what my companies/_form.html.erb looks like:
<%= simple_form_for(#company) do |f| %>
<%= f.input :name %>
<%= f.input :logo %>
<%= f.input :description %>
<%= f.input :city %>
<%= f.input :state %>
<%= f.input :country %>
<%= f.input :email %>
<%= f.button :submit %>
<% end %>
How can I combine them into 1 form, or some other unified workflow within 1 view where it works seamlessly to the user?
Edit 1
Based on Jay-Ar's answer, this is what is happening now.
When I select New Company in the Company Dropdown, it doesn't show the fields in the <fieldset>. I believe that's the case because there is no value=0 in the select tags rendered in the HTML, as can be seen in the screenshot below.
Edit 2
After attempting the latest update from Jay-Ar, the JS still doesn't work and the form is no longer hidden.
This is what it looks like on first load, and always:
Ideally I would like for this form not to show up until they have chosen "New Company" from the dropdown.
This is what the HTML looks like now:
The JS does appear in the source, so I know it is being loaded in the asset pipeline correctly.
UPDATED & TESTED WORKING
Now supports Turbolinks
views/jobs/_form.html.erb
<%= simple_form_for(#job) do |f| %>
<%# IMPORTANT: use `include_blank` below instead of `prompt` because prompt does not seem to work when updating, but only works when creating %>
<%= f.association :company, collection: [['New Company', nil]] + Company.pluck(:name, :id), include_blank: 'Please Select Company', input_html: { id: 'company-select' } %>
<fieldset id='job-fields'>
<%= f.simple_fields_for :company, #job.build_company do |ff| %>
<%= ff.input :name %>
<%= ff.input :logo %>
<%= ff.input :description %>
<%= ff.input :city %>
<%= ff.input :state %>
<%= ff.input :country %>
<%= ff.input :email %>
<% end %>
</fieldset>
<%= f.input_field :title %>
<%= f.input_field :salary %>
<%= f.input_field :description, as: :text %>
<%= f.input_field :apply_description, as: :text %>
<%= f.input_field :language %>
<%= f.input :premium, as: :boolean, inline_label: true, label: "Make this listing stand out", class: "form-control" %>
<%= f.button :submit, class: "btn btn-lg btn-primary pull-right" %>
<% end %>
JS
// for Rails 5, use turbolinks:load instead of page:change below
$(document).on('page:change', function(){
var companySelect = $('#company-select');
var jobFields = $('#job-fields');
companySelect.change(function(){
// if selected option is the second option
if ($(this).find('option:selected').index() == 1)
jobFields.show().find(':input').prop('disabled', false);
else
jobFields.hide().find(':input').prop('disabled', true);
})
// call change immediately so this still works when already updating and not just creating.
companySelect.change();
})
controllers/jobs_controller.rb
class JobsController < ApplicationController
...
def create
#job = Job.new(job_params)
...
end
def update
#job = Job.find(params[:id]) # not needed if using before_action #set_job
if #job.update(job_params)
...
end
private
def job_params
params.require(:job).permit(:id, :title, :salary, :description, :apply_description, :language, :premium, :company_id, company_attributes: [:name, :logo, :description, :city, :state, :country, :email]
end
end
models/job.rb
class Job < ActiveRecord::Base
accepts_nested_attributes_for :company
validates :company, presence: true
...
end
You should get something like the following:
On fresh load:
After selecting 'New Company' option':
You should use nested model forms, I suggest you watch this video it has a very detailed explanation of the steps to do it for a question / answer models but its the same thing.

Rails: File to Upload does not get passed from Form to the controller

This is the Form. All of the fields get passed (and saved) except the one containing the File.
I have checked that using the
render plain: params[:article].inspect method
giving out this (I have entered the value "n" for all fields):
{"country"=>"n", "region"=>"n", "town"=>"n", "street"=>"n", "company"=>"n", "title"=>"n", "content"=>"n"}
I am leaving out superfluous fields here to make the Form shorter:
<%= form_for(#article, html: { multipart: true }) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :country %>
<%= f.text_field :country, :required => true,
placeholder: "enter country" %>
</div>
<%= f.label :content %>
<%= f.text_field :content, :required => true, placeholder: "town..." %>
</div>
</div>
</div>
</div>
<span class="picture">
<%= form_for #article, html: { multipart: true } do |f| %>
<%= f.text_field :title %>
<%= fields_for :pictures do |ff| %>
<%= ff.file_field :picture %>
<% end %>
<% end %>
</div>
I have also tried the slight variation here, but no change
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for
The create method at the Controller is like this:
def create
#article = current_user.articles.build(article_params)
if #article.save
flash[:success] = "Article created!"
redirect_to root_url
else
render 'articles/new'
end
end
and yes, the new method in the Articles controller, is like I was indicated by peers here:
def new
#article = current_user.articles.build
#article.pictures.build
end
The Article Model
class Article < ActiveRecord::Base
belongs_to :user
has_many :pictures
accepts_nested_attributes_for :pictures, allow_destroy: true
And the pictures Model
class Picture < ActiveRecord::Base
belongs_to :article
mount_uploader :picture, PictureUploader
end
Change your <%= fields_for :pictures do |ff| %> to <%= f.fields_for :pictures do |ff| %>

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

Resources