Dynamic Error Messaging in Ruby on Rails
Attention, attention, here is how you create alerts in Ruby on Rails!
Published on May 8, 2020
by
Filed under development
Through the years, whenever I needed notifications in an application, I would use the embedded Ruby on Rails middleware called flash. It basically enables you to pass value from one action to the other. As stated in the Ruby documentation:
Anything you place in the flash will be exposed to the very next action and then cleared out. This is a great way of doing notices and alerts.
Here is an example:
flash[:notice] = "Item successfully created"
It’s easy to use — just put the container with the flash on top of your HTML tree. When you reload the page or move to another action, a message will be presented to the user.
This is all well and good, but the most important word to point out in the previous sentence is reload. Meaning, you need to force the reload of your page if you want to show the message. And, in some cases, I didn’t want that to happen.
Why? Because, in most of my projects, I had three or more forms that would only change one line on the show page. Imagine the amount of reloads needed while trying to change two or three attributes on the show page. The user experience is not great when the user sees only the spinner for half of their time on the page.
I tried to find some hidden gems or external libraries that would help me handle this difficulty. Unfortunately, I didn’t find anything suitable. I could be wrong, but everything seemed pretty robust for my needs. So, I decided to embark on a journey of creating a useful solution.
If you succeeded to bear with my prelude and want to hear more, let’s begin!
Basically, I’ll go through the code I’ve written, highlighting the most important parts of it. If you’re curious, you can examine the full code. Keep in mind that I’ve set up the jQuery and Bootstrap for this project, but you can use anything else. The main idea is to show the use of AJAX for form submissions and dynamic content changes.
As a first step, you’ll need to set this div somewhere in your application. The placement doesn’t matter because its position will be absolute in regard to the rest of the applications HTML. I’ve put mine into app/views/layouts/application.html.erb.
<body>
<div class="notification_container"></div>
<%= yield %>
</body>
Also, you’ll need to add id to the container whose content you’ll be modifying. Mine looks like this in app/views/items/index.html.erb.
<tbody id='items-container'>
<%= render partial: 'items/partials/items_table', locals: { items: @items } %>
</tbody>
After that, we can change our form_for and link_to tags to:
<%= form_for Item.new, remote: true, html: { class: 'dynamic-form notification-messages' }, data: { container: 'items-container', type: 'json'} do |f| %>
<!-- CONTENT GOES HERE ( inputs, buttons etc.) -->
<% end %>
I’ve only shown form_for, but the same procedure needs to be applied to link_to.
Add the dynamic-form class if you want to change the content of a part of your page (we’ll change the content of the element with id='items-container'). If you want to show messages to the user, you need to add the class notification-messages to the form or link. These are my names for classes. You have the freedom to choose different ones, of course. Just remember to use the same names in your Javascript files.
To understand why all of this is working, we have to mention:
remote: true
By using this line of code, we force forms and links to use AJAX as their submission mechanism, rather than the native browser implementation. This will give us Javascript hooks such as ajax:success, ajax:error, and ajax:completed to which we will add our handlers. For those of you looking for more info on remote true, here is where you can find it.
We can now set up our Javascript files. The configuration can be seen in app/webpacker/javascript/global/messages.js:
// if the ajax request from form or link was successful, then replace html content
$(document).on('ajax:success', '.dynamic-form', function(e) {
const data = e.originalEvent.detail[0];
const $target = $(e.target);
const $modal = $target.closest('.modal');
const $targetContainer = $(`#${$target.data('container')}`);
__App._Helpers.removeModal($modal);
appendContent($targetContainer, data.html_content)
});
// show the message when the the ajax request returned success
$(document).on('ajax:success ajax:error', '.notification-messages', function(e) {
const data = e.originalEvent.detail[0];
showMessage(data)
});
As you can see, the forms and the links with class "dynamic-form" will change the content of the element with id set in an HTML attribute "data-container". As for the links and forms with the "notification-messages", they will present the message to the user.
Next, we’ll need to make changes to our controller. Here’s what that will look like in app/controllers/items_controller.rb:
def create
item = Item.new(items_params)
respond_to do |format|
message = ''
messageClass = ''
html_content = nil
if item.save
items = Item.all
message = 'Item have been successfully created.'
messageClass = 'isa_success'
html_content = render_to_string partial: '/items/partials/items_table', locals: { items: items }
else
messageClass = 'isa_error'
recipe.errors.full_messages.each { |e| message += e.to_s + "\n" }
end
format.json { render json: { message: message, messageClass: messageClass, html_content: html_content } }
end
end
I'm only presenting the create method of items controller because the destroy method or the update would be the same. To demystify this file, we’ll go line by line and skip the process of creating an item.
So, we are using the respond_to method to return the JSON to the Javascript handlers.
If everything is ok and the database doesn’t return an error, we get the items and render the HTML content using render_to_string helper. Also, we set the messageClass and message, which we’ll also return.
@items = Item.all
html_content = render_to_string partial: '/items/partials/items_table', locals: { items: @items }
If something went wrong, we get the error from the database and fill the message and set the messageClass to be "isa_error".
After that, we return the data using format.json.
format.json { render json: { message: message, messageClass: messageClass, html_content: html_content } }
Little demo of the dynamic messages.
There you have it – dynamic error message! Our controller is now set up for returning JSON to Javascript handlers.
Good luck! 🍀
Join our newsletter
Like what you see? Why not put a ring on it. Or at least your name and e-mail.
Have a project on the horizon?