Rendering form errors in the current view in Rails - ruby-on-rails

I have an MainPagesController index page that is rendering the 'new' page from QuotesController that has a form. How do I render the MainPagesController index page with the errors of the form?
MainPages/index
<h1>Welcome to Book Quotes</h1>
<p>
Post your favourite quotes from your favourite books
<%= render 'quotes/new' %>
</p>
<%= render 'quotes/all_quotes' %>
Quotes/new
<h1>Add a quote</h1>
<%= render 'quotes/form' %>
Quotes/_form
<%= form_for #quote do |f| %>
<% if #quote.errors.any? %>
<ul>
<% #quote.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
<p>
<%= f.label :passage %><br>
<%= f.text_field :passage %>
</p>
<p>
<%= f.label :book_title %><br>
<%= f.text_field :book_title %>
</p>
<p>
<%= f.label :book_author %><br>
<%= f.text_field :book_author %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
QuotesController
def create
#quote = Quote.new(quote_params)
if #quote.save
redirect_to root_url
else
render #not sure what goes here
end
end

Since the form that you're dealing with is a nested form, the standard advice of render :new won't help you here. Instead, you could redirect the user back to the index page, passing the errors via the flash, and update your view to handle displaying those errors.
(Just a thought: it might be worth looking into making this action powered by AJAX. The user experience might be nicer, and it simplifies your code design.)
Anyway, in your QuotesController, the #create action needs to note the errors and pass them along as it redirects the user back to where they came from:
def create
#quote = Quote.new(quote_params)
if #quote.save
redirect_to root_url
else
flash[:quote_errors] = #quote.errors.full_messages
redirect_to :back # or main_pages_path
end
end
Then, your Quotes/_form view needs to handle those errors:
<%= form_for #quote do |f| %>
<% if flash[:quote_errors] %>
<ul>
<% flash[:quote_errors].each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
# ...
Now, this is a bit ugly. You might be wondering -- couldn't we just pass the #quote object back via the flash, so the view doesn't have to change? But while that's technically possible, serializing objects into session is a dangerous path to take. I'd suggest avoiding it.
One other option would be to make the quote submission an action not on QuotesController, but on your MainPages controller. E.g.,
class MainPagesController < ApplicationController
def index
# ...
end
def create_quote
#quote = Quote.new(quote_params) # need to move quote_params in, too
if #quote.save
redirect_to root_url
else
render :index
end
end
# ...
This allows the #quote instance variable to be accessible from your form, so the error handling as-is will work just fine. It's not very RESTful, but then again, neither are most front-end website flows.

Related

Rails 7 failed save only shows errors if I render with a status

I've been working with rails for a while, but I thought I'd try a course to cement my knowledge.
But I already get stumped on a basic save/error action.
I am trying to show error messages after model validation fails.
If the model validation fails, I render 'new'again, where the model instance should have error messages. But if I try to print the error messages like <%= modelinstance.errors.inspect %> it just shows an empty array.
The weird thing is, if I instead do render :new, status: :unprocessable_entity it gladly renders the whole error thing.
I was just wondering why this is the case, when the ruby on rails guide is allowing the string version.
Controller:
...
def index
articles = Article.all
render locals: {
articles: articles
}
end
def new
#article = Article.new
end
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article
else
render :new, status: :unprocessable_entity
end
end
...
View:
<h1>Create a new article</h1>
<% if #article.errors.any? %>
<h2>The following errors prevented the article from saving:</h2>
<% #article.errors.full_messages.each do |msg| %>
<%= msg %>
<% end %>
<% end %>
...
<%= form_with scope: #article, url: articles_path, local: true do |f| %>
<p>
<%= f.label :title %>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :description %>
<%= f.text_area :description %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>'
...
It's due to the introduction of Turbo in Rails 7. Without that status, Turbo wouldn't really know what to do with the redirects.
You can read more about it here:
https://turbo.hotwired.dev/handbook/drive#redirecting-after-a-form-submission
Otherwise, you could just disable Turbo and it should go back to "normal".

Getting "param is missing or the value is empty: post" explained below

when i click new post and try to save a new post it gives me that error, then i go to the controller :
private
def posts_params
params.require(:post).permit(:title, :description)
end
and change 'require(:post)' to 'require(:posts' then i works
but then i try to edit the new post i just created and when i click to save it it gives me the same error, then i just change it back to 'required(:post)' and it works, why this is happening ? it's like a loop, if one works the other doesn't and to work i have to change that one thing
Controller:
class PostsController < ApplicationController
def index
#posts = Post.all
end
def edit
#posts = Post.find(params[:id])
end
def update
#posts = Post.find(params[:id])
if #posts.update(posts_params)
redirect_to #posts
else
render 'edit'
end
end
def new
#posts = Post.new
end
def create
#posts = Post.new(posts_params)
if #posts.save
redirect_to #posts
else
render 'new'
end
end
def show
#posts = Post.find(params[:id])
end
private
def posts_params
params.require(:post).permit(:title, :description)
end
end
view edit:
<h1>Editing post</h1>
<%= form_for(#posts) do |f| %>
<% if #posts.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#posts.errors.count, "error") %> prohibited
this post from being saved:
</h2>
<ul>
<% #posts.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :description %><br>
<%= f.text_area :description %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', posts_path %>
view new:
<h1>New Article</h1>
<%= form_for :posts, url: posts_path do |f| %>
<% if #posts.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#posts.errors.count, "error") %> prohibited
this post from being saved:
</h2>
<ul>
<% #posts.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :description %><br>
<%= f.text_area :description %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', posts_path %>
can someone point the problem out ?
You are mixing
form_for(#posts) do |f|
and
form_for :posts, url: posts_path
In your forms.
the :posts version will generate params[:posts] and the #posts version will generate params[:post]. Hence the issue you are seeing. Make sure you posts_params is as follows.
def posts_params
params.require(:post).permit(:title, :description)
end
then just change both of your forms to be
<%= form_for(#posts) do |f| %>
rails will figure out which to call automatically for you, so you will not have to specify the paths..
On a side note, I would probably change #posts to be #post everywhere but the index action, just so that it makes more sense, Since in new,edit,etc.. you are dealing with a singular post.
Since rails is looking at the Model/class of the variable when generating the routes (When given an instance variable) the name of the variable doesn't matter to the framework, but makes it easier (in my opinion) for the programmer to understand

How does new method know the errors in the create method?

A sample code taken from http://guides.rubyonrails.org/getting_started.html
Controller
def new
#article = Article.new
end
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article
else
render 'new'
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
new.html.erb
<%= form_for :article, url: articles_path do |f| %>
<% if #article.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#article.errors.count, "error") %> prohibited
this article from being saved:
</h2>
<ul>
<% #article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
A statement from this link
The reason why we added #article = Article.new in the
ArticlesController is that otherwise #article would be nil in our
view, and calling #article.errors.any? would throw an error.
My doubt:
If there is an error while filling the form, how does the #article in new know about the errors caused to the #article instance of the create action? Aren't they both different variables? And when we render 'new' shouldn't the #artcle of create method goes out of scope which holds the error description and #article of new contain no error information?
when there is any error in #article creation, there is no redirection but rendering which means that it will just render/display the new action view i.e new.html.erb without going to the new action or more precisely without making another call to new action. see this http://brettu.com/rails-daily-ruby-tip-28-whats-the-difference-between-redirect_to-and-render-in-rails/
On errors when it will render the new.html.erb it will use #article object which has all the errors which you get from these lines
#article = Article.new(article_params)
#article.save #save basically runs the validations
So basically after you submit your form, the purpose of new action is done. Now whole thing will be handled by create action in which on errors it will display the errors and uses #article object which you initialized and saved in create action and on successful creation it will make another call to show action using redirect
Hope it makes sense.

How to redirect_to(:back) two times?

def update
if #note.update_attributes(note_params)
redirect_to :back, notice: "Note was updated."
else
render :edit
end
end
Is there a way to redirect back twice?
Here you go:
This is where the link for editing goes:
<p id="notice"><%= notice %></p>
<% url = "#{request.protocol}#{request.host_with_port}#{request.fullpath}" %>
<%= link_to 'Create New Page and Return Here', edit_page_path(1, :url => Base64.encode64(url) ) %>
<br>
After submit your url will be something like this:
http://localhost:3000/pages/1/edit?url=aHR0cDovL2xvY2FsaG9zdDozMDAwL2R1bW1pZXM%3D%0A
In the edit form:
I called it pages/_form.html.erb, Pass the URL as a hidden params.
<%= form_for(#page) do |f| %>
<% if #page.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#page.errors.count, "error") %> prohibited this page from being saved:</h2>
<ul>
<% #page.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :permalink %><br>
<%= f.text_field :permalink %>
</div>
<%= hidden_field_tag :url, params[:url].to_s %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
In controller that you have update method, in this case pages_controller.rb, simply Base64 it back and redirect the user:
def update
redirection = nil
if params[:url].present?
redirection = Base64.decode64(params[:url].to_s)
end
if #page.update(page_params)
if redirection.present?
path = redirection
else
path = #page
end
redirect_to path, notice: 'All Done.'
else
render :edit
end
end
Now user updates the form and redirected back to the first show or index page or any page that she is coming from.
Hope this help.
PS: You might want to clean it up a bit and pass the url from the controller, and put some checks on it. So you don't define any var at the view level. In the above code I just tried to solve this issue not really a design pattern oriented :)

Embedding form partial in unconventional view

I have rendered the regular scaffold form partial for a task model in my user show view. My thinking been so the user can post and see the post on the same page. I defined a task in the user show action like so
def show
#user = User.find(params[:id])
#task = current_user.tasks.new
respond_to do |format|
format.html # show.html.erb
format.json { render json: #user }
end
end
And it does create the post but it does not show them. Any ideas as to why that is ?
Show page
#_form
<%= form_for(#task) do |f| %>
<% if #task.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#task.errors.count, "error") %> prohibited this task from being saved:</h2>
<ul>
<% #task.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :description %><br />
<%= f.text_field :description %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
#_form
#index page
<% current_user.tasks.each do |task| %>
<%= task.description %>
<% end %>
This is weird because you find a #user, but you use current_user to create a new task instance for your form:
#user = User.find(params[:id])
#task = current_user.tasks.new
I assume current_user already found the user model according to a cookie, session or token so you probably do not need #user in your show action.
With that aside, if you're concerned with the view not displaying the list of the current_user's tasks, then you'll need to make sure the view has the proper markup, hence you should show us what your view currently looks like as well. Here's what I'm assuming you're trying to do:
class UsersController < ApplicationController
def show
#task = current_user.tasks.new
respond_to do |format|
format.html
format.json { render json: current_user }
end
end
end
class TasksController < ApplicationController
def create
#task = current_user.tasks.new params[:task]
if #task.save
# Send a new request to users#show
redirect_to current_user
else
# No request will be sent to users#show and the template will just get
# rendered with #task containing the same values from the initial request
# with form input
render 'users/show'
end
end
end
# app/views/users/show.html.erb
<ul><%= render current_user.tasks %></ul>
<%= form_for #task do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
# app/views/tasks/_task.html.erb
<li><%= task.name %></li>

Resources