I'm trying to write a multiple choice quiz using RoR. I'm a beginner so any help is appreciated. So far, I have a Quizzes controller containing the following code:
class QuizzesController < ApplicationController
def new
#quiz = Quiz.new
end
def create
#quiz = Quiz.new(quiz_params)
if #quiz.save
redirect_to "http://www.rubyonrails.org"
end
end
def quiz_params
params.require(:quiz).permit(:question, :wrong_answer_1,
:wrong_answer_2,
:wrong_answer_3,
:correct_answer)
end
def show
#quiz = Quiz.find(params[:id])
end
end
My quiz has five columns (excluding ID) - question, wrong_answer_1, wrong_answer_2, wrong_answer_3, and correct_answer. I'm pretty sure my database/migrations are correct.
I'm really not sure where to go from here. I want the user to have the ability to select, using radio buttons, the correct_answer and the screen to say correct and vice versa with wrong_answer_1, wrong_answer_2, etc.
you already have a new, so you can create questions with fake answers alraedy right.
now you can create a def index, that will display all your questions.
so do:
def index
#questions = Question.all
end
in your index.html.erb file
do something along the lines of:
#questions.each do |q|...
and display them like q.wronganswer, q.wrongaswer2, etc.
you have to figure out a way to display at random the answer options, otherwise they'll all have the same format of
Question
Wrong Answer
Wrong Answer 2
Wrong Answer 3
Correct Answer
I would modify the app to include anwser as a model. Then each question could has_many answers, and one correct answer. the You should use the radio_button_tag
_show_question.html.erb
<% #question.answers.each do |answer| %>
<li>
<%= radio_button_tag("answer[#{question.id}]", answer.id) %>
<%= label("answer_".concat(answer.id.to_s).to_sym, answer.content) %>
</li>
<% end %>
app/models/question.rb
# == Schema Information
#
# Table name: questions
# ...
# correct_answer_id :integer
# ...
has_many :answers, dependent: :destroy
app/models/answer.rb
# == Schema Information
#
# Table name: answers
# ...
# question_id :integer
# ...
belongs_to :question
app/controllers/quizzes_controller.rb
def show
#quiz.includes(:answers).find_by(params[:id])
end
Related
I have a survey application that I confused on final side.
here is app models properties:
User
- Survey
user_id
title
- Question
title
survey_id
type: multiple_choice|check_boxes|short_answer
- Option
title
question_id
(Till here is okay. I can create surveys these includes more nested forms) Issue is after created surveys. (users responses)
-Response
user_id
survey_id
-Answer
question_id
response_id
option_id
Creating survey with nested attributes is okay. Problem is at Response side. how should be my response controller and response form on Survey show.html.erb?
There is response controller nested attributes below;
def response_params
params.require(:response).permit(:id, :user_id, :survey_id, answers_attributes:[:question_id, :response_id, :option_id ] )
end
I should tell that survey can includes multiple questions these with only radio_buttons (independent radio buttons are other issue)
This issue made me so tired. I'll be glad if you can help me. Thanks.
For Source code: Click for source codes
updated files:
Response model:
class Response < ApplicationRecord
belongs_to :user
belongs_to :survey
has_many :answers, dependent: :destroy
validates :survey, presence: true
counter_culture :option
accepts_nested_attributes_for :answers
end
Survey_controller:
def new_response
#survey = Survey.find(params[:id])
#response = #survey.responses.build
# now, the tricky part, you have to build the Answer objects so you can use the nested form later
#survey.questions.each do |q|
#response.answers.build question: q
end
end
def create_response
#survey = Survey.find(params[:id])
#response = #survey.build(response_params)
#response.user = current_user
#response.save
end
Routes:
Rails.application.routes.draw do
devise_for :users
resources :surveys do
member do
get :new_response
get :create_response
end
end
root 'surveys#index'
end
form:
- # surveys/new_response.html.haml
- # You need to define a nested route inside survey resource to create the response
= form_for #response, url: create_response_survey_path(#survey) do |f|
- # you can iterate over all the answers already initialized
= f.fields_for :answers do |ff|
- # get the question from the current answer to show the title and options and a hidden_field with the question id
- q = ff.object.question
= q.title
= ff.hidden_field :question_id
- # add the radios for each options for the question
- q.options.each do |option|
= label_tag do
= ff.radio_button :option_id, option.id
= option.title
= f.submit 'Send'
I wouldn't use the Survey's show action to show the form to create a Response, I think it's better to approach it as a new_response action to make it cleaner and leave the show action just to show the actual survey (not to respond it). Something like:
class SurveysController < ApplicationController
def new_response
#survey = Survey.find(params[:id])
#response = #survey.responses.build
# now, the tricky part, you have to build the Answer objects so you can use the nested form later
#survey.questions.each do |q|
#response.anwers.build question: q
end
end
Now, you can have a form for the response:
- # surveys/new_response.html.haml
- # You need to define a nested route inside survey resource to create the response
= form_for #response, url: create_response_survey_path(#survey) do |f|
- # you can iterate over all the answers already initialized
= f.fields_for :answers do |ff|
- # get the question from the current answer to show the title and options and a hidden_field with the question id
- q = ff.object.question
= q.title
= ff.hidden_field :question_id
- # add the radios for each options for the question
- q.options.each do |option|
= label_tag do
= ff.radio_button :choice_id, option.id
= option.title
= f.submit 'Send'
Your response_params should be something like:
def response_params
params.require(:response).permit(answers_attributes: [:question_id, :choice_id])
end
note that I removed the :survey_id and the :user_id, you don't want a user to hack your form, change a survey_id or user_id and add responses to another survey made by another user!
and your create_response action:
def create_response
#survey = Survey.find(params[:id])
#response = #survey.build(response_params)
#response.user = current_user
#response.save
end
Hope it makes sense.
I have what I believe is fairly simple model setup:
class Booking < ApplicationRecord
has_many :payments
end
class Payment < ApplicationRecord
belongs_to :booking
end
Now, I want to create a form that allows a user to register payments in batch. That is, the form should have a number of input rows, each one representing a payment for some booking (i.e., each row has some fields for the columns of Payment plus a booking_id field). Upon submitting, each row should cause the creation of a corresponding Payment, which should be associated with the Booking indicated by the user for that row.
This seems to be surprisingly tricky, and my Google-Fu is failing me. I've tried the following (inspired by this post describing a solution without associations), which I thought would work, but which, well, doesn't:
class Admin::PaymentController < Admin::Controller
def batch
#payments = []
5.times do
#payments << Payment.new
end
end
def submit
params["payments"].each do |payment|
if payment["booking_id"] != "" || payment["amount"] != ""
Payment.create(payment_params(payment))
end
end
end
private
def payment_params(p)
p.permit(:booking_id, :amount)
end
end
<%= form_tag admin_payment_submit_path do %>
<% #payments.each do |payment| %>
<%= fields_for 'payments[]', payment do |p| %>
<%=p.text_field :booking_id%>
<%=p.number_field :amount%>
<% end %>
<% end %>
<%= submit_tag %>
<% end %>
This renders the form without erroring out, but the HTML names work out such that only a single payment (the last one) is submitted (e.g., name="payments[booking_id]"). Furthermore, upon submitting, I get the error
undefined method `permit' for "booking_id":String Did you mean? print
Which is less than helpful.
I've tried other variations too, but I feel like at this point I'm just feeling my way in the dark. Any guidance would be greatly appreciated!
params in controller is a instance of ActiveController::Parameter that has permit method.
But params["payments"] is a just array as subset of params.
For multiple payment params
def submit
payment_params.each do |payment|
if payment["booking_id"].present? || payment["amount"].present?
Payment.create(payment)
end
end
end
private
def payment_params
params.permit(payments: [:booking_id, :amount])["payments"]
end
For Single payment param
def submit
if payment_param["booking_id"].present? || payment_param["amount"].present?
Payment.create(payment_param)
end
end
private
def payment_param
params.require(:payments).permit(:a, :b)
end
Should I manually add a user_id into the hidden form? Or is there a better way?
I have models:
class Project < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :projects
end
In my view:
<%= simple_form_for #project do |f| %>
<%= f.hidden_field :value => current_user.id %>
<% end %>
Or is there another way to do this? I thought if the model was associated with each other, it would automatically add the user_id into the database for projects?
Thanks!
Edit: Converted all trip to projects, so others needing help knows
Nope. That shouldn't be in form, because of everybody can change hidden value at the form.
Assuming you are using strong parameters
def create
#project = Project.new(project_params)
#project.save # or something else
end
def project_params
params.require(:project).permit(something_permitted_here)
.merge(user_id: current_user.id)
end
If you put user id on hidden form it will cause a breach in security. Because using browser tools a hacker can change the user_id and can inject information for other user. A better way is to put it into controller.
Inside controller, you can do so:
def create
#project.user = current_user
#project.save
#...
end
This way you protect yourself against someone manually changing the user_id in html.
Use the association to your advantage:
def create
#project = current_user.projects.build(project_params)
if #project.save
# ...etc
end
I'm using nested attributes in my Ruby on Rails app (4.0.2) such that survey has_many questions and accepts_nested_attributes_for questions and question belongs_to survey.
The problem that I have run into is that when I only want to look at the questions that belong to a particular survey I get an "undefined method "id" for nil:NilClass"-error. When on the other hand I look at all the questions the index action works without problems.
My questions controller index action:
def index
#questions = Question.where(:survey_id => #survey.id).all # if instead I use #questions = Question.all it works fine, but is not what I want.
##questions = #survey.questions.all
#surveys = Survey.all
#survey = Survey.first
end
My surveys/index.html.erb page:
<%= link_to("questions", { :controller => 'questions', :survey_id => survey.id }, :class => 'btn btn-xs') do %>
<%= glyph 'th-list' %>
<%- end -%>
My Question model:
class Question < ActiveRecord::Base
belongs_to :survey
scope :sorted, lambda { order("questions.created_at ASC")}
end
I also use a before_action that I call find_survey which looks like this:
def find_survey
# If in each action calling this method (find_survey) has :survey_id sent
if params[:survey_id]
# We will then go to the database and look for (and find) :survey_id and set that to #survey.
#survey = Survey.find(params[:survey_id])
end
end
Could anybody point me in the right direction?
The undefined method on NilClass is because your instance variable #survey is nil, and when you're calling Question.where(:survey_id => #survey.id).all you're getting that error.
If you're associations are set up right you should be able to run #survey.questions and not have to perform the search on Questions. That's part of ActiveRecord rails magic. You SHOULDN'T have to call #survey.questions.all because that will return all intances of the Question class, leave that off and you should be good to go.
As far as why this isn't working for you now, it may just be an order thing — you're calling #survey before you define it on the line below.
Based on the error you posted, #survey is likely nil in this line:
#questions = Question.where(:survey_id => #survey.id).all
So maybe your "before_action" is not being called?
On my questions index page i have a list of all the questions. I want to be able to answer each question right on the page by rendering the answers form. I am getting this error when i try and do that:
undefined method `answers' for #<Question:0x00000103dc0100>
It highlights the second line of the answers controller:
def create
#question = Question.find(params[:question_id])
#answer = #question.answers.new(answer_params)
#answer.save
end
Here is the view:
<% #questions.each do |question| %>
<%= question.body %>
<%= render :partial => "answers/form", locals: {question:question} %>
<% end %>
And the form looks like this:
<%= simple_form_for [question, Answer.new] do |f| %>
<%= f.input :body %>
<% end %>
Lastly, the questions controller:
def index
#questions = #comment.questions.order
#answer = answer.new
end
Since you have a belongs_to has_one relationship, the correct association is this:
Question.find(params[:question_id]).answer
Note that answer is singular. This is because each Question has only one Answer - thus, instances of Question do not have the method answers, as indicated in the exception.
If you wanted each question to have multiple answers, you'd define the following associations:
# app/models/question.rb
class Question < ActiveRecord::Base
has_many :answers
end
# app/models/answer.rb
class Answer < ActiveRecord::Base
belongs_to :question
end
With a belongs_to has_many relationship, you'd be able to access multiple answers on each question as follows:
Question.find(params[:question_id]).answers
UPDATE 1:
There are a couple ways to add an Answer to a Question.
Option 1: Utilizing the Rails build method as made available by the has_one association:
Question.find(params[:question_id]).build_answer(answer_params)
Option 2: Directly assign an Answer to a Question:
answer = Answer.first
question = Question.first
question.answer = answer
In either method, note that, because each Question is limited to a single Answer, a question's existing answer will be replaced, rather than added to.
UPDATE 2:
Here's how your controller action should look in its entirety utilizing each of the suggested methods:
Option 1:
# app/controllers/answers_controller.rb
def create
#question = Question.find(params[:question_id])
#question.build_answer(answer_params)
end
Option 2:
# app/controllers/answers_controller.rb
def create
#answer = Answer.new(answer_params)
#question = Question.find(params[:question_id])
#question.answer = #answer
end
If you have belongs_to question, and has_one :answer, then you can only do the following:
Question.find(1).answer
not
Question.find(1).answers
which would be a has_many relationship
EDIT
#answer = answer.new
is almost definitely not what you want.
You want
#answer = Answer.new
Or
#answer = #question.answers.new
Or
#answer = #question.answer.new
It seems like you have not set your associations in your model. Basically, Rails is looking for a method called "answers" in your Question model. Have you set belongs_to :question in your Answer model and has_many :answers in your Question model? Can't tell if that's the real issue without seeing your model code.
More info on associations: http://guides.rubyonrails.org/association_basics.html
Edit - since you have a has_one association, the syntax is a bit different.
Try changing "answers" to "answer" - see the table on generated methods for different association types in this doc: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001834