Show Rails errors, notices and devise messages in one place - ruby-on-rails

I have my application.html.erb file where this code located:
<% if notice %>
<p class="notice"><%= notice %></p>
<% elsif alert %>
<p class="alert"><%= alert %></p>
<% end %>
<%= devise_error_messages! %>
All is great, but form validations are made by using (as usual), this:
<%= form_for #project do |f| %>
<% if #project.errors.any? %>
<h2><%= pluralize(#project.errors.count, "error") %> prevented this project from saving:</h2>
<ul>
<% #project.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
...
<% end %>
How to include them into main app page & show them always in top?

How to include them into main app page & show them always in top?
This is deeper than just adding a partial (as Umang Raghuvanshi suggested).
The problem is that the errors method is part of an #object, which means you have to make sure you're able to access this object whenever you want to output the errors in application.html.erb.
Partials should never be dependent on #instance_variables - they're meant to be loaded in any part of your app. You can pass local variables to them, allowing you to send predefined data as you need.
In your case, I would use a partial but populate it with a content_for:
--
This is what I'd do:
#app/views/layouts/application.html.erb
<%= render partial: "shared/errors" %>
#app/views/shared/_errors.html.erb
<%= content_tag :div, class: "errors" do %>
<% if errors.try?(:any?) %>
<% errors.full_messages.each do |msg| %>
<%= content_tag :li, msg %>
<% end %>
<% else %>
<% flash = %w(notice error alert) %>
<% flash.each do |f| %>
<%= content_tag :div, class: f, flash => [f.to_sym] if flash[f.to_sym] %>
<% end %>
<%= devise_error_messages! %>
<% end %>
<% end %>
#app/views/projects/new.html.erb
<%= form_for #project do |f| %>
<%= render "shared/errors", locals: {errors: #project.errors} %>
<% end %>
This will allow you to style the .errors div to be absolutely position at the top:
JSFiddle
#app/assets/stylesheets/application.css
.errors {
display: inline-block;
width: 50%;
position: absolute;
top: 5%;
}

Extract the code into a partial and include it into your application.html.erb.
_your_partial_name.erb
<% if #project.errors.any? %>
<h2><%= pluralize(#project.errors.count, "error") %> prevented this project from saving:</h2>
<ul>
<% #project.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
application.html.erb
<%= render partial: 'your-partial-name' %>
<% if notice %>
<p class="notice"><%= notice %></p>
<% elsif alert %>
<p class="alert"><%= alert %></p>
<% end %>
<%= devise_error_messages! %>
You could also try using flash.

Related

Can't render a partial from the controller

I'm working on a kind of blog and would like the render a partial from my controller if a condition is true.
This is my controller method:
def comments_session
render partial: 'comments/form' if signed_in?
render partial: 'comments/show'
end
And this is my view:
<%= comments_session %>
These are my partials:
_show.html.erb
<% #post.comments.sort_by(&:created_at).reverse.each do |comment| %>
<div class="col-lg-12 mt-3 pt-3 border-top">
<% if signed_in? %>
<% if current_user.username == comment.user.username %>
<%= link_to 'Excluir', post_comment_path(#post, comment), :class =>"btn btn-sm btn-danger btn_round float-right border-dark",
method: :delete, data: {confirm: "Deseja realmente excluir esse comentário?"} %>
<% else %>
<% end %>
<% else %>
<% end %>
<h6><strong>~ <%= comment.user.username %></strong></h6>
<p class="comment"><%= comment.body %></p>
<p class="lead distance_of_time"><small>
<%= time_ago_in_words(comment.created_at) %>
</small></p>
</div>
<% end %>
and _form.html.erb
<%= form_with(model: #comment, url: [#post, #comment], local: true) do |f| %>
<% if #comment.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(comment.errors.count, "error") %> prohibited this comment from being saved:</h2>
<ul>
<% comment.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="row">
<div class="col-lg-10 form-group">
<%= f.text_area :body, :class =>"form-control", :placeholder =>"Escreva o Que Quiser!" %>
</div>
<div class="col-lg-1 form-group">
<%= f.submit "Comentar", :class =>"btn btn-lg btn-primary border-dark text-light" %>
</div>
</div>
<% end %>
The method renders "_show.html.erb" perfectly, but doesn't do the same with the other...
I tried to change and remove the condition, but it didn't work :/
Please, I'd like to know why I can't render it
Thanks!
Rails is not allowed to render more than one partial in controller.
def comments_session
if signed_in?
render partial: 'comments/form'
else
render partial: 'comments/show'
end
end
Can you try this way?
def comments_session
return render partial: 'comments/form' if signed_in?
render partial: 'comments/show'
end
I think you're getting a DoubleRenderError or something like that

How to display errors on a form_for with multiple resources

I have an order form where I can add order items
<h2>Order: <%= #order.title %></h2>
<h3>Items</h3>
<%= render #order.items %>
<h4>Add an item:</h4>
<%= form_for([#order, #order.items.build]) do |f| %>
<%= f.label :Item %>
<%= f.submit 'Submit' %>
<% end %>
I want to check if there are any errors when creating the item.
This is what I use on the order form:
<% if #order.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#order.errors.count, "error") %> prohibited
this order from being saved:
</h2>
<ul>
<% #order.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
I don't know how to display errors when there is multiple resources in the form for.
I've run into this before and ended up wanting to really customize the messages. In order to do it, I used a solution like this:
<% if #errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#errors.count, "error") %> prohibited
this order from being saved:
</h2>
<ul>
<% #errors.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
This allows you to create an #errors object in your controller where you build array of messages. For example something like this:
#errors = []
#errors += #order.errors.map(&:full_messages)
#errors += #items.map(&:errors).map(&:full_messages).flatten
...
I hope this helps!

Undefined method each with nested resources

I'm trying to follow Ryan Bates Polymorphic association tutorial in order to put some comments to my site.
The thing is I have nested resources:
#Nesting Resources
resources :users do
resources :photos do
resources :comments
resources :tags
end
end
So I'm getting an error in my view (photos/show)
undefined method `each' for nil:NilClass
I suppose the problem is in my controller, with comments, that is not defined correctly, but since I have nested resources, I don't know how to do it.
Photos Controller
def show
#photo = Photo.friendly.find(params[:id])
#user = #photo.user
#commentable = #photo
#comments = #commentable.comments
#comment = Comment.new
end
New Comment Partial
<h2>Comments</h2>
<% if #comments.any? %>
<%= render "comments/comments" %>
<% else %>
TodavĂ­a no hay comentarios
<% if user_signed_in? %>
<%= render "comments/form" %>
<% end %>
<% end %>
Form partial (comments/form)
<%= form_for [#commentable, #comment] do |f| %>
<% if #comment.errors.any? %>
<div class="error_messages">
<h2>Please correct the following errors.</h2>
<ul>
<% #comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.text_area :content, rows: 8 %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Comments partial (comments/comments)
<div id="comments">
<% #comments.each do |comment| %>
<div class="comment">
<%= simple_format comment.content %>
</div>
<% end %>
</div>
Your error says undefined method each for nil:NilClass. As you can guess you are calling each on a nil object. Here your #comments is nil and hence giving your trouble.
In your view try something like this:
<div id="comments">
<% if #comments %>
<% #comments.each do |comment| %>
<div class="comment">
<%= simple_format comment.content %>
</div>
<% end %>
<% end %>
</div>
Edit:
So if you look at your code you have #comments till here:
<% if #comments.any? %>
<%= render "comments/comments" %>
<% else %>
#do stuff
<% end %>
it's after you call your partial that your #comment is lost so try this:
<% if #comments.any? %>
<%= render "comments/comments", comments: #comment %>
<% else %>
#do stuff
<% end %>
and then in your view use
<div id="comments">
<% comments.each do |comment| %>
<div class="comment">
<%= simple_format comment.content %>
</div>
<% end %>
</div>

How to customise rails validation error messages with a nested form setup

I have a form with a nested object something like this:
<%= form_for(#person) do |f| %>
<% if #person.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#person.errors.count, "error") %> prohibited this record from being saved</h2>
<ul>
<% #person.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= render 'person_fields', f: f, :person => #person %>
<%= f.fields_for :posts do |builder| %>
<%= render 'post_fields', f: builder %>
<% end %>
<br />
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The thing is the validation error messages come up in the format {attribute}{message}, i.e the regular full_messages format. The {attribute} also includes the model name which isn't what I want, I just want to display the attribute and the message.
I'm thinking I can potentially us the rails internationalisation api but could use some guidence; has anyone else managed to do this?
I worked out how to this... just for reference, here's my solution:
<%= form_for(#person) do |f| %>
<% #person.errors.messages.each do |msg| %>
<% msg[1].each do |m| %>
<% if msg[0].to_s.split(".")[-1] == "base" %>
<li><%= m %></li>
<% else %>
<li><%= msg[0].to_s.split(".")[-1].humanize.titlecase %> <%= m %></li>
<% end %>
<% end %>
<% end %>
<%= render 'person_fields', f: f, :person => #person %>
<%= f.fields_for :posts do |builder| %>
<%= render 'post_fields', f: builder %>
<% end %>
<br />
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I've used the custom-err-msg plugin in the past and had a lot of success.

Control Structure in Rails Partial

I'm building a Rails app up from the Sample App featured in Michael Hartl's book. In order to display error messages on user signup, I'm using a partial in the shared directory - app/views/shared/_error_messages.html.erb:
<%if #fact %>
<% #data = #fact %>
<% elsif #user %>
<% #data = #user %>
<% end %>
<% if #data.errors.any? %>
<div id="error_explanation">
<div class="alert alert-error">
The form contains <%= pluralize(#data.errors.count, "error") %>.
</div>
<ul>
<% #data.errors.full_messages.each do |msg| %>
<li>* <%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Originally, this partial just started with something like:
<% if #user.errors.any? %>
However, since I've decided to re-use this partial to show errors on other pages, I'm having to use different objects (#user, #fact) depending on which page I'm using it on. This is easily solved by adding that IF statement at the top,
<%if #fact %>
<% #data = #fact %>
<% elsif #user %>
<% #data = #user %>
<% end %>
-but this feels icky. Is there a controller somewhere I should be putting this kind of logic for shared partials?
You can pass local variables to partial instead:
<% if object.errors.any? %>
<div id="error_explanation">
<div class="alert alert-error">
The form contains <%= pluralize(object.errors.count, "error") %>.
</div>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li>* <%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
And in your template, for example:
<%= render 'shared/error_messages', object: #user %>
Marek's answer is probably the best. However, here is an alternative which though not scalable, is closer to what you have done.
Just replace:
<%if #fact %>
<% #data = #fact %>
<% elsif #user %>
<% #data = #user %>
<% end %>
with:
<% #data = #fact||#user %>
You could use render to point to the partial from your controllers for users and fact. Check out the API documentation on rendering partials.

Resources