ruby on rails form_for self referential model - ruby-on-rails

Here's a line from my form, used to create a relationship between two users
<%= form_for(current_user.relationships.build(followed_id: #user.id) do |f| %>
<%= f.hidden_field :followed_id %>
<%= f.submit "follow" %>
Relationship controller create method:
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
I'm trying to figure out how to move the current_user.relationships.build logic out of the form and into the controller, is it possible?

I was able to figure out how to move the logic out of the form.
Basically the object being passed to the form_for helper needs to be initialized. I put the initialization logic into user helper and passed the method to the form_for helper. so this is what you get
user helper method
def initializing_relationships
current_user.relationships.build(followed_id: #user.id)
end
in the form you call initializing_relationships
<%= form_for(initializing_relationships) do |f| %>
This really cleans up the form.

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

Rails: If my Quiz model should be created on completing the quiz, how does form_for know what to refer to?

I've been told that I should not create my Quiz object before my quiz is completed; A user could go to the quiz page, not complete it, and there would be an 'unused' quiz sitting on the database. I can see the logic of that.
I CAN'T see how my quiz is supposed to work without being passed a #quiz object. Here's my QuizzesController, which, when the quiz is needed, gets routed to the 'new' action:
class QuizzesController < ApplicationController
def index
end
def new
#user = current_user
#quiz = Quiz.create(user_id: current_user.id)
end
def create
#results = Quiz.where(user_id: current_user.id).last
redirect_to results_path
end
end
At the moment, you can see that I'm coding the actions as simply as possible. Later, in the 'new' action, I'll add a test to see if the current_user has done the quiz and, if so, redirect to results_path.
Here is my form partial which is rendered as part of quizzes/new.html.erb:
<%= form_for(#quiz) do |f| %>
<p>
<%= f.check_box(:answer1) %>
<%= f.check_box(:answer2) %>
<%= f.check_box(:answer3) %>
<%= f.check_box(:answer4) %>
<%= f.check_box(:answer5) %>
<%= f.check_box(:answer6) %>
<%= f.check_box(:answer7) %>
<%= f.check_box(:answer8) %>
</p>
<p>
<%= f.submit("Get my results!") %>
</p>
<% end %>
Once again, the quiz is very simple while I figure out what's going on.
But I'd like to know, if the #quiz object is not created in the 'new' action, what would I pass into form_for to build the form?
You can instantiate a Quiz object without saving it to the database:
def new
#user = current_user
#quiz = Quiz.new(user_id: current_user.id)
end
The generally used sequence of requests/actions is the following:
The new action just initializes the model's instance with default values, and renders the record with empty fields, usually in a edit view.
def new
#quiz = Quiz.new(user_id: current_user.id)
render :edit
end
create action create the record, and after the create action you should render either the view of the newly created record by redirection to show action with the same view, or to redirect to a new action, in case you are creating a sequence of the same instances of a model.
def create
#quiz = Quiz.create(params)
render :show # or redirect_to :new
end
edit action is to prepare edit fields, is it renders edit view with filled-in fields.
def edit
#quiz = Quiz.where(id: params[:id]).first
end
update action updates the record with values set in edit view, then it renders the show view on the current record.
def update
#quiz = Quiz.update(params)
render :show
end
show action just shows the model's found out with stored in the DB values, then it renders show view with filled-in fields.
def show
#quiz = Quiz.where(id: params[:id]).first
end
So in your show.erb view you get rendering the newly built, or found out instance of Quiz:
<%= form_for #quiz, url: {action: "create"} do |f| %>
<p>
<%= f.check_box(:answer1) %>
<%# ... %>
</p>
<p>
<%= f.submit "Create Quiz" %>
</p>
<% end %>
But I prefer simple-form gem:
<%= simple_form_for #quiz do |f| %>
<%= f.input :answer1, as: :boolean, checked_value: true, unchecked_value: false %>
<%= f.button :submit %>
<% end %>

How to get a Rails form that is submitted with invalid data to repopulate the user entered fields

I'm working to build a Rails 3 + devise, user registration page. This will be an additional page that does not replace the existing devise registration page. This page will include user info and billing info.
I'm trying to get the form to submit and if the form fields do not save, have the reloaded page include the user's previously inputted data. Here's a snippet:
<%= form_for(User.new, :url => '/pricing/sign_up') do |f| %>
<%= f.label :email %>
<%= f.email_field :email %>
<% end %>
When the form submits with invalid data. When the view re-renders, the existing email entered is not persisted. How can I make the existing user's input persist to help the user quickly correct mistakes and submit a valid form?
The key is to have the form_for use the right object. So, instead of
<%= form_for(User.new, :url => '/pricing/sign_up') do |f| %>
you should be using an instance variable to contain the object, like this
<%= form_for(#user, :url => '/pricing/sign_up') do |f| %>
The controller actions would look like this:
# Note: this may need to be an `edit` method instead?
def new
#user = User.new
end
# Note: this may need to be an `update` method instead?
def create
#user = User.new(params[:user])
if #user.save
# Do something... Usually a redirect with success message.
else
render :new
end
end
What this create method is doing is it's filling the #user object with params from the form. And then the call to #user.save will, behind the scenes, call #user.valid? and, if no errors are returned, then the record is saved to the database. But this part is key. If #user.valid? does result in errors, then the errors collection on #user will be populated. Then, after the render :new completes, and re-renders your user form, the form will be able to spit out errors messages by accessing the #user.errors collection. Otherwise, the way you had it before, you always had a User.new object in the form which would never have had any errors because it was never used to attempt record validation before.
How to display the errors in your form is a matter of preference and a little beyond the scope of this question. Here's a guide: http://guides.rubyonrails.org/active_record_validations.html#displaying-validation-errors-in-views
I think it's because of your form_for declaration where you're creating new instance of User on every call.
If you move the User.new to your controller and render the new action upon failure in create action then you should see the user entered values in the form fields.
Something like the following should work:
# app/controllers/users_controller.rb
def new
#user = User.new
end
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
...
else
format.html { render :new }
end
end
end
Then in your view:
<%= form_for(#user, :url => '/pricing/sign_up') do |f| %>
<%= f.label :email %>
<%= f.email_field :email %>
<% end %>
The values of the form fields are driven by the model you pass to form_for, so in your case they will always be empty because you are passing a brand new user object.
You should be using an instance variable which is set in the controller; in the new action this will be a new User model but in the create action it will be a model which has attributes set via the form:
# app/controllers/users_controller.rb
def new
#user = User.new
end
def create
#user = user.create(user_params)
if #user.save
redirect_to user_path(#user)
else
render :new
end
end
Then in the form:
<%= form_for(#user, :url => '/pricing/sign_up') do |f| %>
<%= f.label :email %>
<%= f.email_field :email %>
<% end %>

Rails f.error_messages alwais empty

What did I do :)? In rails form has f.error_messages alwais empty. How can I fix/check this?
Thx
The AR#validate method fills the model's error hash with validation errors.
If between instantiating the model and the call f.error_messages you do not call validate (via AR#save or directly) the #errors hash never gets filled and the errors are never shown).
Also make sure you do not redirect ( the validated object gets lost and a new one is created and has no "filled" #errors hash ), but call render :action => ...
Are you looking for error_messages_for :model?
After validation, this function will build a list of error messages for your view.
For example:
# users_controller.rb
def create
#user = User.new(params[:user])
if #user.save
redirect_to #user
else
render :action => 'new'
end
end
# view/users/new.html.erb
<%= error_messages_for :user %>
<% form_for #user do |f| %>
...
<% end %>

Resources