How to loop through two alternating resources on a form? - ruby-on-rails

I'm trying to make a dynamic form of questions and answers, like so:
Question _______
Answer _______
Question _______
Answer _______
I can't figure out how to loop through the two resources as alternating pairs. I have tried this:
<%= semantic_fields_for [#question, #answer] do |h, i| %>
<%= f.inputs :for => #question do |h|%>
<%= h.input :question %>
<% end %>
<%= f.inputs :for => #answer do |i|%>
<%= i.input :answer %>
<% end %>
<% end %>
But it gives me the error "Undefined method `model_name' for Array:Class."
My controller:
def new
#post = Post.new
#question = #post.questions.new
#answer = #question.build_answer
respond_to do |format|
format.html
end
end
And my models:
class Post < ActiveRecord::Base
has_many :questions
has_many :answers
end
class Question < ActiveRecord::Base
belongs_to :post
has_one :answer
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :post
end

So I don't personally use formtastic but I understand it follows similar lines to simple_form. Your error is coming from trying to pass an Array to semantic_fields_for which only takes a single object:
<%= semantic_form_for #questions do |q| %>
<%= q.input :question %>
<%= q.semantic_fields_for #answer do |a| %>
<%= a.inputs :answer %>
<% end %>
<%= q.actions %>
<% end %>
Don’t forget your models need to be setup correctly with accepts_nested_attributes_for
class Question < ActiveRecord::Base
belongs_to :post
has_one :answer
accepts_nested_attributes_for :answers
end
You'll want to check out the formtastic docs at https://github.com/justinfrench/formtastic
That should get your form showing correctly in the view but you'll need to add some more to your questions controller to make sure it saves the answers (someone correct me if I'm mistaken).
Also just so it's clear do your Questions and Answers tables really have a question and answer column? If the columns are actually something like :body you'll want to replace the relevant symbols in the above code.

I think what you need is exactly what is described in these railcasts:
Nested Model Form Part 1
Nested Model Form Part 2
I think you should also refactor a bit, Posts should not have questions. You might notice a little difference from the railcasts but that's because you have only one answer per question whereas in the railcasts a question has many answers. In the part 2 it shows how to add AJAX calls to add/remove questions and answers (probably you won't need this if you only have one answer).
Mandatory reading so you have a better understanding of associations and how nested attributes work:
A Guide to Active Record Associations
Active Record Nested Attributes
And this is an example that will probably work, with some minimum tweaking. I haven't used semantic fields, just the standard form builder.
class Post < ActiveRecord::Base
has_many :questions
accepts_nested_attributes_for :questions, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
class Question < ActiveRecord::Base
belongs_to :post
has_one :answer, :dependent => :destroy
accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
class Answer < ActiveRecord::Base
belongs_to :question
end
# posts_controller.rb
def new
#post = Post.new
# lets add 2 questions
2.times do
question = #post.questions.build
question.build_answer
respond_to do |format|
format.html
end
end
# views/posts/_form.html.erb
<%= form_for #post do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.fields_for :questions do |builder| %>
<%= render "question_fields", :f => builder %>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
# views/posts/_question_fields.html.erb
<p>
<%= f.label :content, "Question" %><br />
<%= f.text_area :content, :rows => 3 %><br />
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "Remove Question" %>
</p>
<%= f.fields_for :answers do |builder| %>
<%= render 'answer_fields', :f => builder %>
<% end %>
# views/posts/_answer_fields.html.erb
<p>
<%= f.label :content, "Answer" %>
<%= f.text_field :content %>
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "Remove" %>
</p>

Related

Create multiple new records from checkboxes in form using nested attributes in Rails

I'm trying to come up with a contact form that creates a contact record and potentially multiple location records, if multiple locations are checked in a list of checkboxes. I thought of having all location records created and then destroyed, if they aren't checked. I don't think that's optimal though.
I'm using many to many relationships in the models.
This is what they look like at the moment:
contact.rb
class Contact < ApplicationRecord
has_many :contact_locations, dependent: :destroy
has_many :locations, through: :contact_locations
accepts_nested_attributes_for :contact_locations, allow_destroy: true, reject_if: :empty_location?
private
def empty_location?(att)
att['location_id'].blank?
end
end
location.rb
class Location < ApplicationRecord
has_many :locations, dependent: :destroy
has_many :contacts, :through => :contact_locations
has_many :contact_locations
end
contact_location.rb
class ContactLocation < ApplicationRecord
belongs_to :location
belongs_to :contact
end
contacts_controller.rb
def new
#contact = Contact.new
#locations = Location.all
4.times {#contact.contact_locations.new}
end
private
def contact_params
params.require(:contact).permit(:name, :phone, ..., contact_locations_attributes: [:location_ids])
end
new.html.rb
<%= form_with model: #contact do |f| %>
...
<%= #locations.each do |location| %>
<%= f.fields_for :contact_locations do |l| %>
<%= l.check_box :location_id, {}, location.id, nil %><%= l.label location.name %>
<% end %>
<% end %>
...
<% end %>
Does anyone how to make it work properly?
I'm working on Ruby 2.5.1 and Rails 5.2.1.
Thanks a lot.
I think your solution is the form objects pattern.
You can have something like this:
<%= form_for #user do |f| %>
<%= f.email_field :email %>
<%= f.fields_for #user.build_location do |g| %>
<%= g.text_field :country %>
<% end %>
<% end%>
And convert it in something more readable that permits you to instance the locations inside the registration object, checking the value of the checkboxes.
<%= form_for #registration do |f| %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.input :password %>
<%= f.text_field :password %>
<%= f.input :country %>
<%= f.text_field :country %>
<%= f.input :city %>
<%= f.text_field :city %>
<%= f.button :submit, 'Create account' %>
<% end %>
Here you will find how to apply the pattern: https://revs.runtime-revolution.com/saving-multiple-models-with-form-objects-and-transactions-2c26f37f7b9a
I ended up making it work with Kirti's suggestion on the following question:
Rails Nested attributes with check_box loop
It turns out I needed to make a small adjustment in my form's fields_for tag.
Thanks a lot the help!

Ruby on Rails creation of record with nested forms and join table

I am trying to implement the creation of a record with a many-to-many relationship (between surveys and questions) captured in a join table. I want the user to be able to create a Survey an it's contained questions in a single form, but the questions do not appear.
[EDIT: After the changes by RAJ, only one question displays, and they are not saved into the database.]
[EDIT: Rails version 4.2.0, ruby version 2.2.1p85, for what it's worth]
[EDIT: Added "=" in form partial as suggested by RAJ ]
The question model:
class Question < ActiveRecord::Base
has_many :survey_questions
has_many :surveys, :through => :survey_questions
end
The Survey model:
class Survey < ActiveRecord::Base
has_many :survey_questions
has_many :questions, :through => :survey_questions
accepts_nested_attributes_for :survey_questions
end
The SurveyQuestion join table:
class SurveyQuestion < ActiveRecord::Base
belongs_to :survey
belongs_to :question
accepts_nested_attributes_for :question
end
The /surveys/_form.html.erb partial:
<%= form_for #survey do |f|%>
<h3>The Survey Itself</h3>
<%= f.label :title %>
<%= f.text_field :title %> <br/>
<h3>Questions:</h3>
<%= f.fields_for :questions do |builder| %>
<p>
<%= builder.label :question_text, "Question Text" %><br />
<%= builder.text_field :question_text %>
</p>
<% end %>
<%= f.submit "Submit" %>
<% end %>
The /surveys/new.html.erb view:
<h1>New Survey</h1>
<%= render :partial => "form" %>
And, finally, the SurveysController:
class SurveysController < ApplicationController
# I have a few other actions here, like list, edit, delete and so forth
def new
#survey = Survey.new
3.times do
question = #survey.questions.build
survey_question_combo = #survey.survey_questions.build
end
def create
#survey = Survey.new(params[:survey].permit(:title))
if #survey.save
redirect_to :action => "show", :id => #survey
else
render :action => "new"
end
end
end
The problem is that when I create a new Survey, the HTML displays up to the h3 "Questions", but nothing below that. How can I rectify this?
In your _form partial, you need to add = before f.fields_for so that it shows on the page.
<%= f.fields_for :questions do |builder| %>
<p>
<%= builder.label :question_text, "Question Text" %><br />
<%= builder.text_field :question_text %>
</p>
<% end %>
Additionally, you can update your new.html.erb as:
<h1>New Survey</h1>
<%= render :partial => "form" %>
While I am still unsure of what the problem is above, I found a nice tutorial here. It uses a gem called cocoon.

Simplest way for generating multiple sets of fields for nested associations?

Models: Jobs, Applications, Questions, Answers
Jobs has_many questions
has many Applications
Applications has_many answers
belongs_to :job
Questions
has_one :answer
belongs_to :job
In the Application view, I would like to create a view that lists all the questions a particular job has, and gives the corresponding field for the answers.
Right now, I'm able to do that in a bit of a hacked way.
I think the best way to do it is to run 2 simultaneous loops, one of which is an array of questions, #questions, and the other one, which is fields_for :answers
Then, in the fields_for tag, I could provide the question.id, as well as the question.content
Is there a way to do this?
This is my current strategy
<%= form_for [#job, #application] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<% #job.questions.each do |question| %>
<%= f.fields_for :answers, question do |a| %> #This is the hacked part
<%= a.label :content, question.content %>
<%= a.text_area :content, value: "" %>
<%= a.hidden_field :question_id, value: question.id %>
<% end %>
<% end %>
<%= f.submit "Submit the application", class: "button" %>
<% end %>
What I'm doing there is passing the question object as the object for the fields_for tag.
This is my controller ->
def new
job = params[:job_id]
#application = Application.build(job)
redirect_to jobs_path, :notice => "You've already applied to this job! Check out some more" if has_job(current_user,#job)
end
And Application Model ->
class Application < ActiveRecord::Base
belongs_to :job
belongs_to :user
validates :job_id, presence: true
validates :user_id, presence: true
has_many :questions, through: :job
has_many :answers, dependent: :destroy
accepts_nested_attributes_for :answers, allow_destroy: true
def self.build(job_id)
application = self.new
job = Job.find(job_id)
job.questions.count.times do
application.answers.build
end
application
end
end
I found this --> http://rosettacode.org/wiki/Loop_over_multiple_arrays_simultaneously#Ruby ,
But I'm not sure if that can be implemented with a fields_for
Looks like there's a problem with your associations. It seems you want a Job to be able to have many applications and its own set of questions. Each application will then have a set of answers with each answer corresponding to one question in the Job posting. Reflecting this, you would have the ff associations:
Job has_many :questions
has many :applications
Application has_many :answers
belongs_to :job
Question has_many :answer
belongs_to :job
Answer belongs_to :application
belongs_to :question
Then we do this in the model:
def build_answers
job.questions.each do |question|
application.answers.build(question_id: question.id)
end
end
And in the controller:
def new
#application = Application.new(job_id: params[:job_id])
#application.build_answers
redirect_to jobs_path, :notice => "You've already applied to this job! Check out some more" if has_job(current_user,#job)
end
That way, your view would look simpler:
<%= form_for #application do |f| %>
<%= f.hidden_field :job_id, value: f.object.job_id %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.fields_for :answers do |a| %>
<% question = a.object.question %>
<%= a.label :content, question.content %>
<%= a.text_area :content %>
<%= a.hidden_field :question_id, value: question.id %>
<% end %>
<%= f.submit "Submit the application", class: "button" %>
<% end %>
Hope that helps!

has_many nested form with a has_one nested form within it

I am currently trying to make a form for a model, which has a dynamic number of nested models. I'm using Nested Forms (as described in RailsCasts 197). To make things even more complicated, each of my nested models has a has_one association with a third model, which I would also like to be added to the form.
For any who are wondering about over normalization or an improper approach, this example is a simplified version of the problem I'm facing. In reality, things are slightly more complex, and this is the approach we've decided to take.
Some example code to illustrate the problem below:
#MODELS
class Test
attr_accessible :test_name, :test_description, :questions_attributes
has_many :questions
accepts_nested_attributes_for :questions
end
class Question
attr_accessible :question, :answer_attributes
belongs_to :test
has_one :answer
accepts_nested_attributes_for :answer
end
class Answer
attr_accessible :answer
belongs_to :question
end
#CONTROLLER
class TestsController < ApplicationController
#GET /tests/new
def new
#test = Test.new
#questions = #test.questions.build
#answers = #questions.build_answer
end
end
#VIEW
<%= form_for #test do |f| %>
<%= f.label :test_name %>
<%= f.text_box :test_name %>
<%= f.label :test_description %>
<%= f.text_area :test_description %>
<%= f.fields_for :questions do |questions_builder| %>
<%= questions_builder.label :question %>
<%= questions_builder.text_box :question %>
<%= questions_builder.fields_for :answer do |answers_builder| %>
<%= answers_builder.label :answer %>
<%= answers_builder.text_box :answer %>
<% end %>
<% end %>
<%= link_to_add_fields 'New', f, :questions %>
<% end %>
This code example works fully for the first instance of Question. The issue occurs when another question is dynamically added to be created; the answer fields are not displayed. I believe this is because they are only built for the first question in the controller. Is there a way to achieve this using nested_attributes?
I solved my own issue here. What I did was, instead of building the answer model in the controller (which is impossible when you do not know how many questions are going to be made in the view), I built it when calling fields_for:
#CONTROLLER
class TestsController < ApplicationController
#GET /tests/new
def new
#test = Test.new
#questions = #test.questions.build
end
end
#VIEW
<%= form_for #test do |f| %>
<%= f.label :test_name %>
<%= f.text_box :test_name %>
<%= f.label :test_description %>
<%= f.text_area :test_description %>
<%= f.fields_for :questions do |questions_builder| %>
<%= questions_builder.label :question %>
<%= questions_builder.text_box :question %>
<%= questions_builder.fields_for :answer, #questions.build_answer do |answers_builder| %>
<%= answers_builder.label :answer %>
<%= answers_builder.text_box :answer %>
<% end %>
<% end %>
<%= link_to_add_fields 'New', f, :questions %>
<% end %>
This works because no matter how many question forms are being built on the view, a new answer specific to the question being built is built.

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

Resources