Rails Single Form with Multiple Instances of A Model

I wanted to have a single form in my Rails app that submitted and created multiple instances of a Model. Seems like this would be a relatively common thing to do, right? But I found it surprisingly difficult to find good examples that I could follow. I worked on this for a long time, and hopefully this will save you some time.

I originally tried the approach of simply having multiple identical forms on the same page, and using Javascript to recursively submit them (hidden to the user). I imagine this could work, but I had difficulty getting it working, and realized that this approach had problems: 1) It is very slow and inefficient and 2) its not scalable. And I felt “there has to be a better way”.

I tried to use the Surveyor gem but it is no longer in development, and I couldn’t get it working with the latest Ruby and Rails 5. Toward the end of my work I was told about the Reform gem, which looks like it could work, but I had already gotten things figured out so I haven’t tried that gem.

So, on to the specifics of the problem. I wanted to create an ‘exam’ for users, that would be on a single web page. They would answer several questions and get a score. The way I have this set up is with a Question, Response, and Answer models. The Answer model stores the correct answers, while the Response model stores a user’s responses. Now if anyone knows a better way to structure this, I’d love to know, but its what I’ve come up with so far.

class Question < ApplicationRecord
	# Associations
	has_many :answers
end

class Answer < ApplicationRecord
	# Associations
	belongs_to :question
end

class Response < ApplicationRecord
	# Associations
	belongs_to :user
	belongs_to :question
end

Ok, so on the exam page, I want to display all of the Questions, and provide inputs so the user can submit all of their Responses, one for each Question. I want to do this on one webpage, and have one form that submits all of the user’s Responses. There are multiple ways to approach this, and I found a very helpful list (this gist) from Ryan Bates (of Railscasts) and discussed in the Rails Google Group thread.

The way I chose to approach it, I wanted to get the params to look like this:

Parameters: {"utf8"=>"✓", "authenticity_token"=>"", 
"responses"=>
  {
    "0"=>{"response_value"=>"a", "user_id"=>"23", "question_number"=>"1"},
    "1"=>{"response_value"=>"b", "user_id"=>"23", "question_number"=>"2"},
    "2"=>{"response_value"=>"c", "user_id"=>"23", "question_number"=>"3"}
  },
 "commit"=>"Submit Exam", "method"=>"post"}

This is similar to ‘approach 4’ in the Ryan Bates gist referenced above. Essentially, I wanted a single form to submit params where the multiple response objects are in a hash in the params, where they are ordered. They are referenced in the hash by a plural of the model, responses.

So for example, the first response is under {"responses"=>{"0"}}

{"response_value"=>"a", "user_id"=>"23", "question_number"=>"1"}

I didn’t know how to do this, but after a lot of reverse engineering, here’s what I learned. In order to make this work when the HTML form is submitted, we need the name value of each input to be correct and unique. The name attribute of an html input is what links it to how it is defined in the params. So we need our name attribute to be as such in the rendered HTML:

input id="responses_response_value_a" class="radio" 
name="responses[0][response_value]" type="radio" value="a"

So for each Question in the exam, the Response will correspond to a unique numbered value in the params hash. In the above example, it is the 0 in responses[0][response_value]. So for example, the HTML input for response number 3 is:

input id="responses_response_value_c" class="radio" 
name="responses[2][response_value]" type="radio" value="c"

Keep in mind that I’m numbering these starting from 0, as in indices of an array. But you could use any unique value. The reason for this is to separate them in the hash, so then in our controller we can iterate over the params hash, calling create on each response in the params (which we will get to below). I chose to make the unique identifiers simple integers starting from zero to make the iteration code in the controller easier, as you will see below.

Ok, so thats the HTML that we want to generate. Of course we could manually create that in our Rails view file in straight HTML. But that wouldn’t be very “Railsy”. So how do we create that in our erb view file in Rails? Well, we can use Rails methods…

But first, in our controller for the Exam page, we need to create an array of the multiple instances of the Responses model that we are going to create when we submit the form. We do this before we render the exam page. So in whichever controller handles the page where we have our exam, in my case its called pages_controller.rb, I want to create an empty Response for each Question in the exam. I do this be creating an array of empty response objects:

def exam_page
    @empty_responses = []

    Question.all.each do
        @empty_responses << Response.new
    end
end

Great, so now we have an array of empty Responses, one for each Question. Now in our view, we can iterate over that array, and create form inputs for each Response, corresponding to the values for all those empty responses, which will get saved when we submit the form. So in exam_page.html.erb


form_tag responses_path(method: :post) do
   @empty_responses.each do |response|
     fields_for 'responses[]', response, include_id: false do |r|
      # grab each question and display its text
      q = Question.where(question_number:                    
                @empty_responses.index(response) + 1).first
      q.question_text
      r.collection_radio_buttons(:response_value,               
            q.answers.order("answer_text"), :answer_text, :answer_text, 
            include_hidden: false) do |r|                 
               r.radio_button(class: 'radio' + 
               @empty_responses.index(response).to_s, name:
               'responses[' + (q.question_number - 1).to_s +   
               '][response_value]') + r.label(:class => 'radio-label')
             end
      r.hidden_field :user_id, value: current_user.id,
             name: 'responses[' + @empty_responses.index(response).to_s
              + '][user_id]'
      r.hidden_field :question_number, value: q.question_number,
             name: 'responses[' + @empty_responses.index(response).to_s  
            + '][question_number]'
    end 
  end
  submit_tag 'Submit Exam', class: 'btn btn-primary btn-xl'
end

(Note that I removed the erb tags because I had trouble getting WordPress to display them correctly)
Ok, a lot going on here, lets break it down. So we use the form_tag for a new Response. I use the form_tag instead of form_for, as form_forseemed to be more difficult to get to handle multiple Responses, as it typically only accepts one. form_tag seemed to be more vanilla HTML.

In the next line we iterate over our array of empty responses that we created in the controller. Next we use fields_for to tell Rails that we are creating inputs for something different than a simple direct ‘one level deep’ Response. Remember we are going ‘two levels deep’ in our params, to include multiple Responses in our params hash. As this is not the default behavior, using fields_for allows us to tell Rails that we are creating input values for something other than what the form is for in the first line with the form_tag. Of course fields_for is often used if you want to include input values for a different Model, but in our case we are co-opting it to help us submit multiple instances of the same Model. We use "responses[]" to get us to the ‘second level’ in the params hash. So this means that any input values that come after our fields_for "responses[]" will correspond in the params hash to {responses=>{}}.

And thats how our name value on the HTML input becomes "responses[X][response_value]". Make sense?

So that’s the basics. However, since in my case I’m creating multiple choice questions with radio buttons, it ads some extra complications (of course!). I use the Rails form helper collection_radio_buttons. Lets go through each of these parameters as this was quite confusing to me initially. :response_value is the field in the database of the Response model that we want this data to be saved to. q.answers.order("answer_text") is the list of Answers, one for each radio button. The first :answer_text is the value that we want the radio button to be associated with, and the second :answer_text is the text that we want to display next to each radio button. Theinclude_hidden: false is an option that we pass in, which I’ll discuss below. Whew!

Here again, the name value is critical for us to get the HTML code right so that the Response gets put correctly into the params when submitted to the server. So here we specify the name attribute for each radio button. We need the iterate through and create the indices to be used in our params hash, as discussed above. Thats why I use the code: name: 'responses[' + (q.question_number - 1).to_s + '][response_value]'. So this gives us the "responses[X][response_value]" that we wanted in the HTML. The -1 is because I numbered my questions starting at 1, but I wanted to number the responses in the hash starting at 0. Now its important to note that for radio_buttons, the group of radio_buttons that are grouped together as a single set of choices must have the same name attribute.

The id value in the HTML is also important, as you can see it also links us to 2 levels deep by saying responses_response_value_c where ‘c’ is the value that is going to be submitted by choosing that input. This is automagically created by the rails helpers, and I found I didn’t need to alter it.

Here’s what the ultimate HTML looks like for the radio button options for the Response to the first Question:

input class="radio0" name="responses[0][response_value]" type="radio" 
      value="a" id="responses_response_value_a"
label class="radio-label" for="responses_response_value_a">"a"
input class="radio0" name="responses[0][response_value]" type="radio" 
      value="b" id="responses_response_value_b"
label class="radio-label" for="responses_response_value_b">"b"
input class="radio0" name="responses[0][response_value]" type="radio"
      value="c" id="responses_response_value_c"
label class="radio-label" for="responses_response_value_c">"c"
input class="radio0" name="responses[0][response_value]" type="radio"
      value="d" id="responses_response_value_d"
label class="radio-label" for="responses_response_value_d">"d"

And here’s what my params look like in Terminal after submitting the form with multiple responses (3 Responses to 3 Questions):

Parameters: {"utf8"=>"✓", "authenticity_token"=>"...", 
"responses"=> {
     "0"=>{"response_value"=>"a", "user_id"=>"23", "question_number"=>"1"},
     "1"=>{"response_value"=>"b", "user_id"=>"23", "question_number"=>"2"},
     "2"=>{"response_value"=>"c", "user_id"=>"23", "question_number"=>"3"}},
"commit"=>"Submit Exam", "method"=>"post"}

Great! Now some additional nuances. As we mentioned above, on the fields_for call, we pass in include_id: false. This is because, according to the fields_for documentation, it automatically generates a hidden field to store the ID. However, in our case this is interfering with our Responses and the name values linking to the params. To turn this off, we pass in include_id: false to prevent fields_for from rendering it automatically.

A similar thing happens in the collection_radio_buttons code; you see I pass in the include_hidden: false value. This is because Rails 5 by default now creates a hidden value that is blank so that even if the user submits the form without clicking on any radio button, a blank value will be sent to the server (default HTML doesn’t do that). I learned this from this blog post by Prajakta Tambe, which explains this very well and includes other links to the documentation. Since I’m customizing all of these inputs, I needed to turn off that default behavior.

Whew! That was a lot of info. Hopefully it sort of makes sense to you? This took me forever to understand and figure out, and hopefully it saves you time.

Update: Someone asked about how to access these params in the controller, so I’m adding the controller code below. In the create function, basically just iterating through each of the “responses” in the params, and creating a new instance of the Response model for each, passing in the parameters that were sent in the params hash.

def create
  params["responses"].each do |key, value|
    Response.create(response_params(value))
  end
end

Also, as explained in this Vic Friedman post, we have to adjust the strong params function to handle submitting the params in the altered structure in the create function.

So in the bottom of the controller, our new params function now accepts an argument, which is our new params structure:

def response_params(my_params)
  my_params.permit(:question_number, :answer_id, :user_id, :correct,       :response_value)
end

So that’s what I put in the controller code.

credits:

Vic Friedman

Ryan Bates Gist

NestedAssignment

Rails Documentation for Building Complex Forms

Railsguides – Simple Form Array Text Input

Ryan Bates Advanced Rails Recipes

StackOverflow Question 1

StackOverflow Question 2

Using AJAX and jQuery in Rails 5

RichOnRails – AJAX in Rails

LaunchSchool

Advertisements

Rails Form form_for Objects Reverse Order of Array

This is pretty simple, but I couldn’t figure out how to do it.  Basically I had a view that went through each object in the nested table array, and rendered it.  However, instead of rendering them all in the order they were created from top to bottom on the page, I wanted to render them in reverse order, so the most recent one was at the top of the page.  There might be several ways of doing this, but here is how I finally did it, and its pretty simple. (probably shouldn’t have taken me this long to figure it out, but hopefully you can read this and save some time!)

Here is the original code in the view:

  
<%= simple_form_for(@lot) do |f| %>
  <strong>Lot Number:</strong> <%= @lot.number %>
  <strong>Item</strong> <%= Item.find(@lot.item_id).number_rev_name %>
  <strong>Inventory Quantity:</strong> <%= @lot.inventory_qty %>

  <-- Here is where I render a partial for each build_lot, which is a 
         has_many, :through join_table -->
  <%= f.simple_fields_for :build_lots,  do |build_lot| %>
    <%= render 'build_lot_fields', :f => build_lot %>
  <% end %>
  <%= f.button :submit, class: 'btn btn-primary' %>
<% end %>

And here is the code from the partial for each build_lot, _build_lot_fields.html.erb

<div class='nested-fields build-lot-fields'>
  <div class='well'>
    <%= f.input :my_build_id, collection: MyBuild.order(:start_date), label_method: :description, value_method: :id, :prompt => 'Choose a Build', selected: MyBuild.where(current: true), input_html: { id: 'build_select'} %>
    <%= f.input :pull_quantity, label: 'Pull Quantity', as: :string %>
    <%= f.label 'Date' %><%= Date.today %><br>
    <%= f.input :employee_id, collection: Employee.order(:first_name), label_method: :first_name, value_method: :id, prompt: "Choose your name" %>
  </div>
</div>

So this simple trick to reverse the order of the build_lots array that is iterated through to render each partial, is to simply add another parameter to the fields_for call (in this case simple_fields_for from the simple_form gem). This second parameter that we add will be the build_lots array reversed. You can find more about the usage of the fields_for method on its documentation page.

But first we need to create this reversed array in the controller. So in the controller (in the function for the view where I want to show the reversed build_lots) I added this line:

@reverseBuildLots = @lot.build_lots.reverse

Then back in the view, I simply change the form_for line like this:

<%= f.simple_fields_for :build_lots, @reverseBuildLots do |build_lot| %>

passing in the @reverseBuildLots parameter, which is an array of build_lots in reverse order that we created in the controller.

Pretty simple, but took me a while to figure out. Hopefully this will save you some time!

credit here

Change or Set Form Input Value with jQuery, CoffeeScript

So this is pretty basic, but it took me 10 minutes of Googling to figure it out.  If you need to specifically assign a value to a form input, here is how to do it via coffeescript:

$(".submit").on "click", ->
  email = $('input[name="user[email]"]').val();
  $('.hiddenEmail').val(email);

The key is the .val() method. To get the value of an element, you call the .val() method without passing in any parameters.

email = $('input[name="user[email]"]').val();

To set the value of an element, you pass in the value you want into the .val() method.

$('.hiddenEmail').val(email);

Some credit here.

Fixing Nested http Params Are HTML Escaped with Slashes

This may not be super common, but it was hard to debug for me. I had an error when I was posting form parameters using the .post_form method from one rails app to another, where a nested params hash was getting html escaped and ending up with tons of slashes or backslashes appearing in the params hash, like this:

Parameters: {"utf8"=>"✓", "authenticity_token"=>"anLqF1UcQULf35xHJZW5Ya9rjMAgkse7h1R=", "user"=>"{\"name\"=>\"nqne\", \"last_name\"=>\"nwen\", \"email\"=>\"laughing@gmail.com\", \"password\"=>\"foobartest\", \"password_confirmation\"=>\"foobartest\"}"}

Now obviously when I was trying to receive these parameters in my other rails app, it was not reading them correctly. Apparently this happens because the .post_form method turns nested hashes into escaped strings for some reason. This is the code that was giving me this error:

require "net/http"
uri = URI('http://www.myotherrailsapp.com')
x = Net::HTTP.post_form(uri, params)

As you can see, when I post a nested params hash via .post_form, it escapes them as html strings. The way I solved this was to switch to using the .post method. So replacing the third line above with:

http = Net::HTTP.new(uri.host)
response = http.post(uri.path, params.to_query)	

Note also that I call the .to_query method on the params hash. See this Stack Overflow question for an example of that. Essentially it converts a hash into a string.  Bingo, this solved it!

Another workaround that worked but is not ideal is to not use the rails form helper to create your form params.  So for example, the form I was submitting was created with standard rails form helpers:

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>
  <%= f.label :name, "First Name" %> <%= f.text_field :name, autofocus: true %>
  <%= f.label :last_name %> <%= f.text_field :last_name %>
  <%= f.label :email %> <%= f.email_field :email %>
  <%= f.label :password %> <%= f.password_field :password, autocomplete: "off" %>
  <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, autocomplete: "off" %>
  <%= f.submit "Sign up" %>
<% end %>

And that creates a nested params hash that the .post_form method escapes. But if instead I just use direct html to create the params, as in the email field below:

  Sign up with your email <input name="email" type="email" autofocus="true" />

Then it doesn’t get escaped by the .post_form method. I prefer the first method, as it allows you to keep the rails form helpers in place.

(another related SO question)

Hope this was helpful!

Custom id for input using simple form in Rails

Simple_form  provides a lot of handy html tags for forms. However, the autocreation of classes and id’s was giving me trouble because I couldn’t figure out how to give an element the id I wanted. It’s actually very simple.

input_html: { id: 'my_id'}

is used to give an id to the specific element. This can be a bit tricky when using collection_select, as it takes a bunch of inputs. I typically put the input_html: {} as the last input.

<%= f.input :item_id, collection: Supplier.order(:name), as: :grouped_select, group_method: :items, group_label_method: :name, label_method: :name, value_method: :id, :prompt => 'Choose an existing Item', input_html: { id: 'quantity_item_select'} %>

Check the documentation for collection_select and grouped_collection_select to be sure you put the inputs in the correct order.

You can also use wrapper_html to input custom html for the wrapper div’s that simple_form creates.

wrapper_html: { id: 'my_id'}

I hope this is useful for you, as even though its simple, it took me a while of searching to find it.

Form Horizontal with Bootstrap 3 and Simple Form 2 on Rails 4

I don’t know why this was so difficult, but I had lots of trouble getting the `form-horizontal` class to work in my rails app was quite a chore when I upgraded to bootstrap 3.  After lots of searching and hours of agony, this finally worked for me.  Credit goes to this blog post.

After you have bootstrap 3 (I use the bootstrap-sass gem) and simple_form 2, run the simple_form generator:

rails generate simple_form:install --bootstrap

So the way I understand it, this hack basically overrides the div classes in simple_form to match them up with bootstrap. So first edit the config/initializers/simple_form.rb

config.label_class = 'col-lg-2 control-label'
config.form_class = "simple_form form-horizontal"

In the config/initializers/simple_form_bootstrap.rb replace the classes “control-group” with “form-group” and replace “control” with “col-lg-10”

Now create a new initializer file, for example config/initializers/remove-this-sf_bs3_inputs.rb

inputs = %w[
CollectionSelectInput
DateTimeInput
FileInput
GroupedCollectionSelectInput
NumericInput
PasswordInput
RangeInput
StringInput
TextInput
]

inputs.each do |input_type|
superclass = “SimpleForm::Inputs::#{input_type}”.constantize

new_class = Class.new(superclass) do
def input_html_classes
super.push(‘form-control’)
end
end

Object.const_set(input_type, new_class)
end

Hopefully there will be some official support in the near future that won’t require this workaround. Let me know if you know of an easier way to get form-horizontal to work.

Here’s the Stack Overflow question that I asked regarding this problem.

Rails 4 Form Dropdown Menu with collection_select

Perhaps this was simple for you, but as someone still learning rails, I found confusing and conflicting examples and explanations on how to use collection_select in a rails form, especially with a has_many through association using accepts_nested_attributes_for.  It took me several hours to finally get it working.  Hopefully this will save you some time.

So I have a PurchaseOrder model that is linked to the Items model via a has_many through association, with the join model called Quantities, as you can see below.

purchase_order.rb

Screen Shot 2013-12-18 at 5.15.51 AM

quantity.rb

Screen Shot 2013-12-18 at 5.17.28 AM

item.rb

Screen Shot 2013-12-18 at 5.18.30 AM

And I have a form with nested fields using accepts_nested_attributes_for that looks like this:

purchase_orders/_form.html.erb

Screen Shot 2013-12-18 at 5.21.34 AM

which looks like this in the browser:

Screen Shot 2013-12-18 at 5.23.45 AM

and here’s the _quantity_fields.html.erb partial:

Screen Shot 2013-12-18 at 5.26.10 AM

So here is how I got the dropdown menu for “Part Number” using collection_select.

What I had trouble with was what variables to pass into the collection_select function.  They go as follows:

collection_select ‘model field’, ‘collection of options in the dropdown menu’, ‘model field for values’, ‘model field to show in the menu’

You’ll notice the @items that I passed in to the collection_select.  I created this in the PurchaseOrders controller in the new, create, edit, and update functions:

@items = Item.all.order("part_number")

I read the official rails documentation for collection_select, but it still left me a bit confused.   I also found help from two StackOverflow questions (one, two) that I want to give credit to, and may help you as well.

Let me know if this helped you!