Rails passing hidden form data via Controller - ruby-on-rails

UPDATE: It seems like the error was probably due to a devise conflict. I used some content from another app which apparently made devise go nutty and not be able to read user sessions. I'll be starting with a clean install and seeing if I have better luck. Thanks!
I am creating a page which has a single question on it. On that page I have a form where people can answer that question. The form itself just has an answer block and a submit button. However, I want to tie it to both the user submitting the form and the question the answer is linked to. For security I want to do this in the controller, not the view. The answer form is being shown on the questions#index page. I am currently getting the following error:
undefined method `user' for #<AnswersController:0x007fe618e5cc10>
I suspect that if it made it past the user I would get the same for 'question'
The questions#index looks like this:
<div class="home_question">
<h1><%= #daily.question %></h1>
<div class="answer_form">
<%= form_for #answer do |answer| %>
<%= answer.label :answer, "What do you think?" %>
<%= answer.text_area :answer %>
<%= answer.button %>
<% end %>
</div>
</div>
The Questions Controller looks like this:
def index
#daily = Question.find_by(show_month: Time.now.month, show_day: Time.now.day)
#answer = Answer.new
end
The Answers Controller looks like this:
def index
#answers = Answer.all
#answer = Answer.new
end
def new
#answer = Answer.new
end
def create
#answer = Answer.new(answer_params)
#user = user(params[:id])
#question = question(params[:id])
if #answer.save
redirect_to root
else
render 'new'
end
end
private
def answer_params
params.require(:answer).permit(:answer, :users_id, :questions_id)
end
The data currently being passed by the form is:
{"utf8"=>"✓",
"authenticity_token"=>"mA16+dg7+Edzxvu/FVWR5r8PZ9zdNaOyvOwSz1VpOXU=",
"answer"=>{"answer"=>"test"},
"button"=>""}

The error that you're getting is because in the controller you're using the user variable which isn't defined. You want to use the uppercase model name User.
Getting the user id
If this is a logged in user then you can get their id by looking in the session. Usually Rails application will have a helper like this:
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
You don't need this helper if you're using devise -- it's already done for you.
Getting the question id
First make sure that your associated resources are nested. So in our routes.rb file:
resources :questions do
resources :answers
end
This means that to create an answer we'll need to POST /questions/:id/answers.
The form changes to:
<%= form_for [#daily, #answer] do |answer| %>
<%= answer.label :answer, "What do you think?" %>
<%= answer.text_area :answer %>
<%= answer.button %>
<% end %>
and in the controller:
def create
#answer = Answer.new(answer_params)
#answer.user = current_user
#answer.question = Question.find(params[:id])
if #answer.save
redirect_to root
else
render 'new'
end
end

Related

Simple_form does not accept the instance variable from action create

I am creating a very simple StackOverflow type of website
written in Ruby on Rails. I created four actions in my questions controller and one of them is 'Create'.
I proceeded in the view page index.html.erb and I create a simple_form where I get input(question) from a user.
I get an error (NoMethodError in Questions#index..undefined method `[]' for nil:NilClass)
The question belongs to the current_user and I think that might be the problem. I thought maybe I need to initialize one more variable in order to get my form to work.
Can please someone tell me what am I missing here?
Thank you in advance!
class QuestionsController < ApplicationController
def index
#questions = Question.all
end
def show
#question = Question.find(params[:id])
end
def new
#question = Question.find(params[:user_id])
#question = Question.new
end
def create
#question = Question.new(accepted_params)
if #question.save
redirect_to questions_show_path, notice: 'Question submitted'
else
#question = Question.find(params[:id])
render :new
end
end
<%= simple_form_for #questions, #user do |f| %>
<%= f.input :title %>
<% f.input :content %>
<%= f.submit :submit, class: 'btn btn-secondary'%>
<% end %>
You should use the following code:
def create
#question = Question.new(accepted_params)
if #question.save
redirect_to question_path(#question), notice: 'Question submitted'
else
render :new
end
end
Let me explain:
on a create action there is no params[:id] (because we are creating a new item)
also: we established that saving failed so trying to retrieve it from the database would only make sense on an edit action
third: simple-form will look at the error-messages and incorporate them into the form, so the user can then fix the errors.
and lastly: I fixed your redirect_to to be more "rails"-like, but this depends on your route-definition. I am assuming you have something like resources :questions in your routes (but if you do not give a parameter that could also never work imho)
E.g. if you have a validate_presence_of :name in your model, this could cause a validation-error upon save, and then we could present the field in red in the form when rerendering.
[TYPO in form?]
Lastly, after your comment I noticed it said simple_form_for #questions, #user and that should be either be the singular simple_form_for #question. If you want to edit the question as a nested path for the user, I think the correct form is simple_form_for [#user, #question].

Parameter in find method

I'm following a guide to Ruby on Rails and there is something I don't understand. I have this model called Comment which belongs_to another two models, called User and Book.
This model's controller, Comments has the following create action:
def create
book = Book.find(params[:comment][:book_id])
comment = book.comments.build(comment_params)
comment.user = current_user
if comment.save
redirect_to comment.book
end
end
comment_params is just this:
def comment_params
params.require(:comment).permit(:body)
end
This create action is called when clicking the "Submit" button from this form, which is a partial called _comments located in the books view folder and rendered in the books' show action:
<%= form_for #book.comments.build do |f| %>
<%= f.hidden_field :book_id, value: #book.id %>
<%= f.text_area :body %>
<%= f.submit "Submit" %>
<% end %>
#book is indeed defined in books#show.
I don't understand why I have to pass the [:comment] parameter to the Book.find method in order to find the book. I thought just the [:book_id] would suffice.
You're not actually passing the :comment parameter but rather accessing the :book_id that's nested in the :comment hash. Your params look something like:
{
:comment => {
:book_id => 1
}
}
If you simply passed params[:book_id] you would get back nil.
If book_id is a field in comments table you don't need to retrieve the book. Just do
def create
comment = Comment.new(comment_params)
comment.user = current_user
if comment.save
redirect_to comment.book
end
end
Also, if the User model has a comments association, and in this action you are sure that a current_user is set, you can do
def create
comment = current_user.comments.build(comment_params)
if comment.save
redirect_to comment.book
end
end

Creating a new record in one-to-many relation in ruby on rails

I am trying to create an app in which we can ask questions and a question have many answers. So I used one-to-many association here.
Now I have two models Question Answer and two controllers questions & answers.
I have a form in the \questions\show.html.erb
<%= form_for #answer, :url => "answer", :method => 'post' do |f| %>
<%= f.hidden_field :question_id, :value => #question.id %> #this i am not sure about
<%= f.text_area :content %>
<%= f.submit "SUBMIT" %>
<% end %>
The show method in controller is:
def show
#question = Question.find(params[:id])
#answers = #question.answers
#answer = Answer.new
end
I also created a route to answers controller's create method
post 'questions/show/answer' => "answers#create"
Now the problem is that the question_id is passed as string but I want it to be passed as integer so that I can associate it to the current Question.
How can I do it, or is there any other way to do it.
Use new action in your controller to render a new object of answer for your form, then in create action, find the question you want to answer and create the answer under it. Like so:
def new
#answer = Answer.new
end
def create
#question = Question.find(params[:id])
#question_created = #question.answers.create(answers_params).save
if #question_created
// do something or redirect somewhere
end
end
private
def answers_params
params.require(:answer).permit(:content)
end
Then in your routes, you can use resources to create RESTful routes needed
resources :questions do
resources :answers
end
(Note: each action should be used for the specific required purpose, except in instances when you want to make AJAX calls to get a form in the show action)

Routing issue? Rails First argument in form cannot contain nil or be empty

UPDATE: I am an idiot. I added the #answers to the users controller instead of the questions controller. now that I changed it to questions controller it is showing. I am having submission errors, but I will start a new thread if I can't figure them out.
I have read a couple dozen of the related questions and tried the solutions without success. I'm wondering if I have my routing set up incorrectly. This is the error I am getting:
First argument in form cannot contain nil or be empty on line 5
Here is my form code. It is an answer form, but it is being shown on the questions index page under a specific question.
<%= form_for #answer do |answer| %> <<< LINE GENERATING ERROR
<%= answer.label :answer, "What do you think?" %>
<%= answer.text_area :answer %>
<%= answer.button %>
<% end %>
Here is my routing information:
resources :answers
get 'answers/new' => 'questions#index'
post 'answers' => 'questions#index'
Here is my answers_controller information:
class AnswersController < ApplicationController
def index
#answers = Answer.all
end
def new
#answer = Answer.new
end
def create
#answer = Answer.new(answer_params)
if #answer.save
redirect_to root
else
render 'new'
end
end
private
def answer_params
params.require(:answer).permit(:answer, :users_id, :questions_id)
end
The first argument to form_for can't be nil - rails can't tell that that you want to create an answer (the instance variable may be called #answer but form for doesn't know the name of the variable passed to it, only the value)
In cases like this, the easiest is something like
<%= form_for Answer.new do %>
...
<% end %>
Alternatively you could leave your view unchanged and set #answer in the controller that is rendering this view (by the sounds of things this is your QuestionsController

Showing form error messages

I'm having trouble getting my redirect and error messages to work. From what I've read you cant get a forms errors to show up when you use redirect so I am trying to use render after it fails.
I have a new post form on a topic page. The url is "topic/1". If you make a post about the topic and something is wrong with the input I want it to go back to the page at topic/1 and display errors and I cant figure out how to get it to go back. Redirect (:back) does what I want but doesnt show the forms errors.
The form on the topic's show.html page:
<%= form_for(#post) do |f| %>
<%= render 'shared/post_error_messages' %>
<%= f.label :title, "Post Title" %>
<%= f.text_field :title %>
<%= f.label :content %>
<%= f.text_field :content %>
<%= f.hidden_field :parent_id, value: 0 %>
<%= f.hidden_field :topic_id, value: #topic.id %>
<%= f.hidden_field :user_id, value: current_user.id %>
<%= f.submit "Create Post" , class: "btn btn-small btn-primary" %>
<% end %>
Create action in the Posts controller
def create
#post = Post.new(post_params)
#topic = Topic.find_by(id: params[:topic_id])
if #post.save
redirect_to #post
else
#topic = Topic.new
render "/topics/show"
end
end
I guess I'm mostly trying to do the render with the id from the page that the form was originally on.
Errors
The problem isn't anything to do with the way you're rendering the form (render or redirect) - it's to do with the way you're handling your ActiveRecord object.
When you use form_for, Rails will append any errors into the #active_record_object.errors method. This will allow you to call the following:
form_for error messages in Ruby on Rails
<%= form_for #object do |f| %>
<% #location.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
<% end %>
This only works if you correctly create your ActiveRecord object, which you seem to do
--
Nested
#config/routes.rb
resources :topics do
resources :posts, path: "", path_names: {new: ""}, except: [:index] #-> domain.com/topics/1
end
You'll be much better using the following setup for a nested route:
<%= form_for [#topic, #post] do |f| %>
...
<% end %>
This allows you to create a form which will route to the topics_posts_path, which is basically what you need. The controller will then balance that by using the following:
#app/controllers/topics_controller.rb
Class TopicsController < ApplicationController
def new
#topic = Topic.find params[:topic_id]
#post = Post.new
end
def create
#topic = Topic.find params[:topic_id]
#post = Post.new post_params
end
private
def post_params
params.require(:post).permit(:attributes)
end
end
You are overwriting the Topic you original found with a brand new, empty one - which shouldn't be necessary and which is causing the posts related to it to disappear.
Also - if your topic and post are related - you should create the post on the appropriate association #topic.posts instead of the main Post class.
The #topic.posts.new means that the post's topic-id is automatically updated with the value of the #topic.id ... which means you don't need to set it in the hidden-field on the form.
In fact it's better if you don't - just delete that hidden field entirely.
If you add that to the first time you get a new post too (eg in topics/show) then you won't need to pass in a value to the hidden-field.
Also I'd do the same for all the other hidden-fields on the server-side too. You don't really want the user to use firebug to hack the form and add some other user's id... so do it in the create action and don't bother with the hidden field
This should work:
def create
#topic = Topic.find_by(id: params[:topic_id])
#post = #topic.posts.new(post_params)
#post.user = current_user
#post.parent_id = 0
if #post.save
redirect_to #post
else
render "/topics/show"
end
end
if it doesn't - let me know what error messages you get (so we can debug)

Resources