0

I am trying to create a personality test.

I have a Questions table that looks like this


       ID  | Question      |
    ----------------------------
        1  | How likely would you etc... 


and a Results table that looks like this

       ID  | Answer      | Question_ID | User_Id
    ------------------------------------------------
        1  |  1          |  1          |   1

I have 68 questions that I want to loop through and I want to store the answers (which are integers on a scale from 1-10) in my Results table.

How do I create a form that will save data to my Results table?

I am having trouble populating the answer column of my Results table.

Here's what I have. It doesn't work.

<%= form_with(model: @result, local: true) do |form| %>
  <% @questions.each do |question| %> 
    <div>
      <h4><%= question.id %>. <%=question.question %></h4><br /> 
      <div class="hidden-field">
        <%= form.hidden_field :question_id, value: question.id %>
      </div>
      <div class="field">
        <%= form.number_field :answer, placeholder:'Please answer on a scale from 1 to 10' %>
      </div>
    </div> 
  <% end %>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

In my controller, I have a Home Controller (to display a home page)

class HomeController < ApplicationController
  def index 
      @questions = Question.all.order(:created_at)
      @user = User.new
      @result = Result.new
  end
end

I also have my Results Controller

class ResultsController < ApplicationController
  before_action :set_result, only: [:show, :edit, :update, :destroy]

  def new
    @result = Result.new
  end

  def create
    @result = Result.new(result_params)
  end

  private
    def set_result
      @result = Result.find(params[:id])
    end

    def result_params
      params.require(:result).permit(:answer, :user_id, :question_id)
    end
end

The questions are displaying perfectly fine. I can even store the data, but only one record gets saved and it only populates the question_id column with 68 and leaves the answer column NULL

What am I doing wrong?

Community
  • 1
  • 1
David Lee
  • 571
  • 6
  • 20

2 Answers2

1

A few tips to get you headed in the right direction:

User.new is a problem.

New users don't have an ID, so their non-existent ID can't be saved to the database.

I noticed your form doesn't have a user_id field. This is good. Since it will be the same for all records, once you have a viable @user record saved to the database first, you can just set the @user.id to the record in the controller:

`@record.create(user_id: @user.id, question_id: results_params[:question_id])

Also, you don't need to have :user_id in your permitted parameters if it's not a field in your form. This opens you up to form value injection:

    def result_params
      params.require(:result).permit(:answer, :question_id) # delete :user_id
    end

You want to create multiple records at once

Totally doable. You just can't use @result = Result.new(result_params) because this only creates one @result.

You'll need to loop over the params[:results] in the create action and call @result.create() on each one.

Pro tip for someday: you could actually delegate this to a method in your model: Result.batch_create(params)

Look at your form submission parameters for tips

Place a debugger (I use byebug or pry) call at the top of your create action. This will halt your terminal output and give you a console inside your server.

You should be able to see the parameters that have just been submitted in your server log in your terminal (this depends on what server you are using).

Or, once the debugger has paused the server and given you a console, just type params to see what is being sent. You should see a whole collection of results, not just one.

You've opened yourself up to duplicates

Just automatically creating a new @result each time means your database could have lots of duplicate values.

I think what you should do is first check to see if a record exists where the user_id and question_id match what you have in the form, then either update or create the record accordingly.

Longhand, this method would look something like this (again, remember we need to do this inside a loop of every form result parameter):

params[:results].each do |result_param|
  if Result.where(user_id: result_param[:user_id], question_id: result_param[:question_id]).exists?
    result = Result.where(user_id: result_param[:user_id], question_id: result_param[:question_id])
  else
    result = Result.new(user_id: result_param[:user_id], question_id: result_param[:question_id])
  end

But, this is really bad code and of course Rails gives you a method for this:

result = Result.where(user_id: result_param[:user_id], question_id: result_param[:question_id]).first_or_create

Be sure to read this good article about .first_or_create

TL;DR you've got a few core issues to explore to make this work, but you're on the right track.

Community
  • 1
  • 1
Chiperific
  • 4,428
  • 3
  • 21
  • 41
  • 1
    Thanks so much @chiperific! You post helped me understand what I should be reading up on. I figured out a solution, but I'd love feedback. I'll post the solution as an answer since I know this code works, but would welcome any edits. – David Lee Nov 27 '19 at 17:04
1

So after reading up on this, I have code that works. This post helped tremendously along with @chiperific reply

I put this form in my results/new.html.erb file

    <%= form_with(model: result, local: true) do |form| %>
      <% @questions.each do |question| %> 
        <%= fields_for "result[]", result do |result_field| %>
          <h4><%= question.id %>. <%=question.question %></h4><br /> 
           <%= result_field.hidden_field :question_id, value: question.id %>
           <%= result_field.number_field :answer, placeholder:'Please answer on 
            a scale from 1 to 10' %>
        <% end %>
      <% end %>
      <div class="actions">
        <%= form.submit %>
      </div>
   <% end %>

I put this in my results controller

class ResultsController < ApplicationController
  before_action :set_result, only: [:show, :edit, :update, :destroy]

  def new
    @result = Result.new
    @questions = Question.all.order(:created_at)
  end

  def create
    params[:result].each do |result_param|
     result = Result.new(result_param.permit(:answer, :user_id, :question_id))
    end
  end

  private
    def set_result
      @result = Result.find(params[:id])
    end
end

The key insight was that I need to loop through my results and create a new result instance for every single one of my results.

I removed my Home controller and home view page and changed my routes.rb file to root 'results#new'

I have not addressed the issues of duplicate records (which is very valid) so I know my code will need some refactoring, but this code answers my initial question (I'm very open to feedback)

David Lee
  • 571
  • 6
  • 20
  • Good progress! 1: I still don't see how you are saving the `user_id` to the `result` since it's still not a field on your report or a variable in your controller. 2: Duplicates can be handled by doing `Result.where(result_param).first_or_create` – Chiperific Nov 27 '19 at 22:16
  • the user_id is something I have to figure out. I want people to take this personality test without creating a user account. However, I'd like to give people the option of creating an account and saving the results if they wish. In this situation, I would need to figure out how to store a user_id to the results. – David Lee Dec 02 '19 at 20:28