Magazine

Dynamic Error Messaging in Ruby on Rails

Attention, attention, here is how you create alerts in Ruby on Rails!

16 dynamic error messaging

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!


The Journey

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 or play with the demo application. 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! Our controller is now set up for returning JSON to Javascript handlers. Feel free to contact me on visko@arsfutura.co if I didn’t cover your burning questions.

🤓

Leave a comment Be the first!