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.
Related
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
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
I have a form that lets users add a new blocked tv show to their list of blocked shows. The form is not taking the param values (:user_id, :title, :image) that I tried to set in the controller. I'm a beginner, so I'm guessing the syntax is the problem.
Also I am getting a Couldn't find Tvshow without Id error when trying to use the #tvshow instance variable to set the param values of :title and :image. Each Blocked show should have the same title and image as the tvshow that the user selects in the collection_select. Is there an easier way to do this?
View
<%= form_for #blockedshow do |b| %>
<%= b.label :tvshow_id, "Add a Blocked TV Show " %><br/>
<%= collection_select(:blockedshow, :tvshow_id, Tvshow.all, :id, :title, prompt: true) %>
<%= submit_tag 'Add' %>
<% end %>
Controller
class BlockedshowsController < ApplicationController
def new
#blockedshow = Blockedshow.new
end
def create
#tvshow = Tvshow.find params[:blockedshow][:id]
#blockedshow = Blockedshow.new(safe_blockedshow)
params[:user_id] = current_user.id
params[:title] = #tvshow.title
params[:image] = #tvshow.image
if #blockedshow.save
flash[:notice] = "New Blocked TV Show added successfully"
redirect_to tv_show_index_path
else
render 'new'
end
end
private
def safe_blockedshow
params.require(:blockedshow).permit(:title, :user_id, :tvshow_id, :image)
end
end
Blockedshow model
class Blockedshow < ActiveRecord::Base
has_many :phrases
has_many :tvshows
belongs_to :user
end
Tvshow model
class Tvshow < ActiveRecord::Base
has_many :phrases
belongs_to :blockedshow
def self.search_for (query)
where('title LIKE :query', query: "%#{query}%")
end
end
Routes
resources :blockedshows
post 'blockedshows', to:'blockedshows#create#[:id]'
you are getting the issue because params[:blockedshow][:id] is not passed, if your trying to access the Tvshow id selected by from the drop-list you can do the following
#tvshow = Tvshow.find params[:blockedshow][:tvshow_id]
Just fixed by changing the controller to this:
def create
#tvshow = Tvshow.find params[:blockedshow][:tvshow_id]
#blockedshow = Blockedshow.new(
:user_id =>current_user.id,
:title=> #tvshow.title,
:image=> #tvshow.image,
:tvshow_id=>#tvshow.id
)
I'm still making online test program.
This is my model.
Survey
class Survey < ActiveRecord::Base
belongs_to :user
has_many :questions, :dependent => :destroy
accepts_nested_attributes_for :questions, :reject_if => lambda {|a| a[:content].blank?}, :allow_destroy => true
Question : there is is_correct column which indicates whether students get the right answer or not.
class Question < ActiveRecord::Base
belongs_to :survey
has_many :answers, :dependent => :destroy
accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
Answer : there is correct column which teacher checks making the survey(test), and there is user_answer column which students mark taking the test.
class Answer < ActviveRecord::Base
belongs_to :question
and I made taking exam interface on show.html.erb in survey views. So if students fill this check box and click the submit button, they can get their result page. but I can't show the result in the result page.
This is my survey controller.
def grading
#survey = Survey.new
#survey.user_id = current_user.id
if #survey.questions.answers.user_answer and #survey.questions.answers.correct
#survey.questions.is_correct = true
end
redirect_to results_surveys_path(#survey)
end
def results
end
The error message I saw is 'undefined method `answers' for []:ActiveRecord::Relation'. I thought that there were problem between question and answer table...
I thought auto grading part is easy, but I was wrong. I have no idea about this and I don't have any reference, except your help.
any idea welcome.
Thanks advanced.
Updated Question
Here is another question.
Now I can access to nested objects.(I think and I hope) but the result page(result.html.erb in survey views) can't show any result : "undefined method `name' for nil:NilClass".
result.html.erb
<h1><%= #survey.name %></h1>
<h3><%= #survey.user.username %></h3>
As I told in previous link, I have another form_tag in show.html.erb in survey views. then redirect to the result page with routes.rb.
resources :surveys do
collection do
get 'results'
end
end
I thought I can show the result page using is_correct column in question tables.
I didn't write anything in the result method in survey controller. Because when I redirect the page I wrote like this. Which means using #survey in result method, doesn't it?
redirect_to results_surveys_path(#survey)
Here is the result of rake routes.
seriousin#classcasts:~/ClassCasts$ rake routes | grep survey
results_surveys GET /surveys/results(.:format) surveys#results
surveys GET /surveys(.:format) surveys#index
POST /surveys(.:format) surveys#create
new_survey GET /surveys/new(.:format) surveys#new
edit_survey GET /surveys/:id/edit(.:format) surveys#edit
survey GET /surveys/:id(.:format) surveys#show
PUT /surveys/:id(.:format) surveys#update
DELETE /surveys/:id(.:format) surveys#destroy
surveys_grading POST /surveys/grading(.:format) surveys#grading
seriousin#classcasts:~/ClassCasts$
I think my basic idea caused all of my problem. Here is my survey controller.
class SurveysController < ApplicationController
def index
#surveys = Survey.all
end
def grading
#survey = Survey.new
#survey.user_id = current_user.id
#survey.questions.each do |question|
question.auto_check
end
redirect_to results_survey_path(#survey)
end
def results
#survey = Survey.where(params[:id])
end
def show
#survey = Survey.find(params[:id])
end
def new
#survey = Survey.new
end
def edit
#survey = Survey.find(params[:id])
end
def create
#survey = Survey.new(params[:survey])
#survey.user_id = current_user.id
end
def update
#survey = Survey.find(params[:id])
end
def destroy
#survey = Survey.find(params[:id])
#survey.destroy
end
end
as you can see, I'm using show page in survey views as another input form with grading method. I can use '#survey = Survey.new' in create method, it makes sense! but as I wrote in grading method, it generates another new survey, I think.
So I need to change that line. can you please help me?
Sending data
OK. when I submit in _form.html.erb in survey views, I can send data like this.
Parameters: {"id"=>"14", "survey"=>{"name"=>"The First Test!", "description"=>"This is the first test!", "questions_attributes"=>{"0"=>{"_destroy"=>"false", "id"=>"41", "content"=>"Question 2", "answers_attributes"=>{"0"=>{"_destroy"=>"false", "id"=>"66", "content"=>"Answer 2 of Question 2", "correct"=>"0"}, "1"=>{"_destroy"=>"false", "id"=>"67", "content"=>"Answer 1 of Question 2", "correct"=>"1"}}}, "1"=>{"_destroy"=>"false", "id"=>"42", "content"=>"Question 1", "answers_attributes"=>{"0"=>{"_destroy"=>"false", "id"=>"68", "content"=>"Answer 2 of Question 1", "correct"=>"0"}, "1"=>{"_destroy"=>"false", "id"=>"69", "content"=>"Answer 1 of Question 1", "correct"=>"1"}}}, "1376575795482"=>{"_destroy"=>"false", "content"=>"Question 3", "answers_attributes"=>{"1376575802879"=>{"_destroy"=>"false", "content"=>"Answer 1 of Question 3", "correct"=>"0"}}}}}, "commit"=>"Update Survey", "utf8"=>"✓", "authenticity_token"=>"/vNuB5Ck3QM5p+5ksL3tlmb+ti5nTA/qS96+vbPQkNw="}
This is OK. but because of form_tag in show.html.erb, show page contains another input form.
<%= form_tag({:controller => "surveys", :action => "grading"}) do %>
after submit again in show.html.erb I want to redirect to the results.html.erb with proper result. but there are errors like this.
Started POST "/surveys/grading" for 110.174.136.30 at Thu Aug 15 23:19:52 +0900 2013
Processing by SurveysController#grading as HTML
Parameters: {"utf8"=>"✓", "#<Answer:0x7fafd8c5f5a0>"=>"1", "#<Answer:0x7fafd95704a0>"=>"0", "#<Answer:0x7fafd9116a58>"=>"1", "authenticity_token"=>"/vNuB5Ck3QM5p+5ksL3tlmb+ti5nTA/qS96+vbPQkNw=", "#<Answer:0x7fafd8d03a38>"=>"0", "commit"=>"Submit", "#<Answer:0x7fafd8cfc580>"=>"1"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 12 LIMIT 1
Completed 404 Not Found in 3ms
ActionController::RoutingError (No route matches {:action=>"results", :id=>#<Survey id: nil, name: nil, description: nil, attempts: nil, user_id: 12, created_at: nil, updated_at: nil>, :controller=>"surveys"}):
app/controllers/surveys_controller.rb:31:in `grading'
Do you think that I need to change whloe answering mechanism?
Try like this, you are trying to call your method on an array. your have to iterate over it and then you can assign any value to it. you can do some re-factoring in your code as well to avoid loops.
in your grading method, do this:
def grading
#survey = Survey.new
#survey.user_id = current_user.id
#survey.questions.each do |question|
question.auto_check
end
redirect_to results_surveys_path(#survey)
end
In your Question model write a method auto_check like this:
def auto_check
answers.each do |answer|
is_correct = true if answer.user_answer and answer.correct
self.save!
end
end
I think it is a better approach.Thanks.
Updated Answer:
Whenever you try to pass an id in your path, that means that is a member function, if you are defining collection in your routes, that means you cannot pass an id in that(As you routes output shows. Have a keen look into it). change your routes like this:
resources :surveys do
member do
get 'results'
end
end
Now you can access your url like this:
results_survey_path(#survey)
and in your results method :
def results
#survey = Survey.where(params[:id])
end
Hope, now it will work.
I have gone through tons of the form_for nested resource questions and can't get any of the solutions to work for me. I figured its time to ask a personalized question.
I have two models, jobs and questions, jobs has_many questions and questions belong_to jobs.
I used scaffolding to create the controllers and models then nested the resources in the routes.rb.
root :to => "pages#home"
resources :jobs do
resources :questions
end
get "pages/home"
get "pages/about"
get "pages/contact"
class Job < ActiveRecord::Base
has_many :questions
end
class Question < ActiveRecord::Base
belongs_to :job
end
Right now I am trying to access '/jobs/1/questions/new' and keep getting the
NoMethodError in Questions#new
I started with the error No route matches {:controller=>"questions"} when the code was
<%= form_for(#question) do |f| %>
I know this is wrong, so I started to try other combos and none of them worked.
I've tried
<%= form_for([#job.questions.build ]) do |f| %>
that
<%= form_for([#job, #job.questions.build ]) do |f| %>
that
<%= form_for(#job, #question) do |f| %>
Among a bunch of other combinations and that are not working.
Here is a link to my rake routes : git clone https://gist.github.com/1032734
Any help is appreciated and let me know if you need more info, thanks.
I just pass the URL as an extra option:
<%= form_for(#question, :url => job_questions_path(#job)) do %>
EDIT:
Also try:
form_for([#job, #question])
This is how I solved mine :)
In your questions/_form.html.erb
<%= form_for [#job, #question] do %>
For this to work, you need the job's id. You'll pass it as follows:
In the questions_controller.rb
def new
#job = Job.find(params[job_id])
#question = #job.questions.build
end
Build(.build) is similar to using new(.new) in the code above, with differences only in older versions of rails; rails 2 down.
Now for the create action (still in questions_controller.rb)
def create
#job = Job.find(params[:job_id])
#question = #job.questions.build(question_params)
end
If you only use these, the job_id and user_id field in the question model will be empty. To add the ids, do this:
In your questions_controller.rb add job_id to job_params like so:
def question_params
params.require(:question).permit(:ahaa, :ohoo, :job_id)
end
Then to pass the user's id (if you are using Devise), do:
def create
#job = Job.find(params[:job_id])
#question = #job.questions.build(question_params)
#question.user_id = current_user.id
end