How does new method know the errors in the create method? - ruby-on-rails

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.

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".

Displaying validation errors on "new" view

In this section of the Rails guide, it is instructed to add #article = Article.new in the new method of ArticlesController, explaining that otherwise we won't be able to access #article.errors.
From what I understand, #articles = Article.new creates a new instance of Article, and what we need is the #article variable that we tried to submit. I know it works but I need to understand why.
Controller code:
class ArticlesController < ApplicationController
def index
#articles = Article.all
end
def show
#article = Article.find(params[:id])
end
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
end
View code:
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
<% if #article.errors.any? %>
<div id='error_explanation'>
<h2>
<%= pluralize(#article.errors.count, "error") %> prohibited this articles from being saved:
</h2>
<ul>
<% #article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :text %><br>
<%= form.text_area :text %>
</p>
<p>
<%= form.submit %>
<% end %>
<%= link_to 'Back', articles_path %>
It says this:
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.
so it has nothing to do with accessing validation errors.
Without the #article variable in new action you would call errors method in your view on nil value and nil doesn't have such a method, so you would get an error undefined method 'errors' for nil:NilClass. In case of #article variable set to Article.new you call errors method on an Article class instance and since there are no validation errors (yet) the #error_explanation block will not be rendered.
But when you try to create a new record, then validation occurs. And if there are validation errors your rails app renders the new template again, but it does it with the create action. Therefore, the #article variable this time is the one from the create method and since we have validation errors in it, the #error_explanation block will be rendered and user will see what's wrong.

First argument in form cannot contain nil or be empty (rails 5)

I've been following the rails blog tutorial (you know, that one ) and i've come to a point where every time i reference #articles on the update form, rails takes it as a nil, it says:
First argument in form cannot contain nil or be empty
Here's the form
<h1>Edit article</h1>
<%= form_for #article 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 %>
And the controller for the articles:
class ArticlesController < ApplicationController
def new
#article = Article.new
end
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article
else
render 'new'
end
end
def show
#article = Article.find(params[:id])
end
def index
#articles = Article.all
end
def update
#article = Article.find(params[:id])
if #article.update(article_params)
redirect_to #article
else
render 'edit'
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
end
It does not look like you have an edit method in your Articles controller.
def edit
#article = Article.find(params[:id])
end
Just so it is clear. The edit method is what is called with the GET route that shows the form. Update is the PATCH/PUT route that takes the form in and updates the record. So, the form is shown by the edit method via GET, and processed by the update method via PUT/PATCH.

Rendering form errors in the current view in 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.

ArgumentError in Posts#edit - first argument nil

I am going through the "Getting Started with Rails Tutorial" and am stuck on update aka Edit. It is throwing the ArgumentError in Posts#edit - first argument in form can't be nil or empty. Here is the highlighted line:
First argument in form cannot contain nil or be empty
Extracted source (around line #1):
<%= form_for #post do |f| %>
It seems to have started when I implemented the partial forms part of the tutorial.
Here is the post_contoller, edit action and _forms.html respectively:
Post_controller:
class PostsController < ApplicationController
def new
#post = Post.new
end
def create
#post = Post.new(params[:post].permit(:title, :text))
if #post.save
redirect_to #post
else
render 'new'
end
end
def show
#post = Post.find(params[:id])
end
def index
#posts = Post.all
end
def update
#post = Post.find(params[:id])
if #post.update(params[:post].permit(:title, :text))
redirect_to #post
else
render 'edit'
end
end
def destroy
#post = Post.find(params[:id])
#post.destroy
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title, :text)
end
end
Edit.html
<h1>Edit post</h1>
<%= render 'form' %>
<%= link_to 'Back', posts_path %>
_form.html
<%= form_for #post do |f| %>
<% if #post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#post.errors.count, "error") %> prohibited
this post from being saved:</h2>
<ul>
<% #post.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 %>
The error shows an ID of "7" which is the record I am trying to update.
All other functions work (show, new, delete) and BTW "new" uses the same partial form and works fine.
Any help would be much appreciated. Thanks!
U should add this in your _controller.rb
def edit
#post = Post.find(params[:id])
end
you have to pass in the post object as local. You cannot directly access instance variables defined in controller in partial. It will be nil
<h1>Edit post</h1>
<%= render :partial => 'form', :locals => {:post => #post} %>
<%= link_to 'Back', posts_path %>
In the partial
<%= form_for post do |f| %>
check below the private is used for the below function only and write all functions above this:
private
def article_params
params.require(:article).permit(:title, :text)
end

Resources