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?
Related
I have a model “Thing,” each of which has_many “Comments,” each of which in turn has_many “Votes.” I want to be able to vote on comments on the Thing show page. This is what I have so far:
Comments Controller:
def votecomment
#comment = Comment.find(params[:id])
Vote.create!(voteable_id: params[:id], voteable_type: 'Comment')
redirect_to current_thing
end
Things View:
<%= link_to “Vote”, vote_comment_path(:id => comment.id), method: :post %>
Routes:
post 'comments/:id/vote' => 'comments#vote', as: 'vote_comment'
But I'm getting back this error:
NameError in CommentsController#votecomment
undefined local variable or method `current_thing' for #<CommentsController:0x007f98efa69c00>
I tried moving the method to the Things controller, but I got the exact same type of error.
What am I doing wrong?
Assuming you have the following relation in comment.rb
belongs_to :thing
You can access the thing object of a comment using #comment.thing. Since redirect_to accepts objects, you can do
redirect_to #comment.thing
You have to understand that nothing is called current_thing if you are familiar with devise and you see ex current_user this is a method in the gem not a populated method with each model you create.
So if you want something like that add method to your application_controller or even application helper to get current_thing
def current_thing
Thing.find() --> or whatever the way you get that current thing.
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'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
I'm learning Rails by writing simple TODO tasks aplication.
Two models are:
class List < ActiveRecord::Base
has_many :tasks, :dependent => :destroy
# ...
end
class Task < ActiveRecord::Base
belongs_to :list
# ...
end
Tasks are routed as a nested resources under Lists. So when a new Task is created by user a POST message is sent to /lists/:list_id/tasks. So far in Tasks#new view's form there is
f.hidden_field :list_id, :value => params[:list_id]
but it's a terrible solution, because anyone can change value of that hidden field.
What is the convention here? Should I put something like
#task.list_id = params[:list_id]
in Tasks#create action and get rid of the hidden field, or maybe
#task = List.find(params[:list_id]).tasks.new(params[:task])
if #task.save
# ...
end
or there is even a better way I don't know about?
Edit:
Yeah, well there was similar question and its answer is pretty much covering my question. If you have different one please post it.
You're right - that would be horrible. No need for hidden fields. Something like the following.
In your TasksController:
def new
#list = List.find(params[:list_id])
#task = #list.tasks.build
end
def create
#list = List.find(params[:list_id])
#task = #list.tasks.new(params[:task])
# etc
end
In your Task#new view:
<% form_for [#list, #task] ... %>
...
<% end %>
If you are concerned about security (like one user creating to-dos in another user's lists - and I assume you are, because you didn't want to use a hidden field stating that anyone can change value of that hidden field), I don't see how #bjg solution is any better then yours, since you're getting #list from params anyways, and anybody can manipulate params on the browser (changing the URL to post to is as easy as changing the hidden field value).
One common way to solve this without having to implement a more complex permission solution is to just use current_user association's, like this:
def new
#list = current_user.lists.where(id: params[:list_id]).take
#task = #list.tasks.build
end
def create
#list = current_user.lists.where(id: params[:list_id]).take
#task = #list.tasks.new(params[:task])
# etc
end
This way, no matter what is the value of params[:list_id] (it could have been manipulated by the user), you can rest assured the #task will end up on that user's account, since #list will only find a record that belongs to current_user.
You can evolve this in a real-world app by returning an error message if #list is not found.