Search code examples
ruby-on-railsrubyruby-on-rails-3ruby-on-rails-4ujs

Rails controller - execute action only if the a Rails UJS method inside succeed (mutually dependent methods)


Following another question (Rails controller - execute action only if the two methods inside succeed (mutually dependent methods)), I would like to ensure that inside one of my controller's action, if the user does not see the message displayed by a Rails UJS method, then the first methods of the controller action are not implemented either.

CONTEXT

I have a Controller with a method called 'actions'. When the app goes inside the method 'example_action', it implements a first method (1) update_user_table and then (2) another update_userdeal_table. (both will read and write database) and then (3) a third method which is related to a Rails UJS(ajax) call.

My issue is the following: in case of timeout in the middle of the controller, I want to avoid the case where the User table (via method 1) is updated, the UserDeal table is updated (via method 2) but NOT the thrid method i.e the ajax request that displays a message FAILS (error, timeout,...status like 500 or 404 or canceled or timeout...).

In my app, for mobile users if they're in a subway with internet connection, they launch the request that goes through 'example_action' controller, performs successfully the first method (1) and second method (2) but then they enter a tunnel for 60 seconds with very very low (<5b/sec) or NO internet connection, so for UX reasons, I timeout the request and display to the user 'sorry it took too long, try again'. The problem is that if I could not show to them the result (3), I need to be able to not execute (1) and(2).

I need the two methods (1) and(2) and(3) to be "mutually dependent": if one does not succeed, the other one should not be performed. It's the best way I can describe it.

Today Here is my code. It's not working as I am manually testing by clicking and then after just 2 seconds I disconnect the internet connection. I see in my database that (1) and(2) were performed and the databases were updated but I saw the message 'sorry it took too long, try again'.

Is that the right approach ? if yes how to do this? If not, should I try a different angle like: if (1) and(2) were successful but not(3) should I store the fact the rails UJS xhr status was an error or timeout, that consequently the modal wxas not effectively displayed to the user and then show to them the result/message once they get back online?

Here is the code

html page for the user the user click on a button that triggers a Rails UJS aajax request that will display ultimately the modal message

<div id=zone"> 
   <%= link_to image_tag(smallest_src_request),
          deal_modal_path,
           remote: true %>
</div>

This send to a route that points to this controller action

Deal controller

class DealsController < ApplicationController
  def deal_modal
    Deal.transaction do
       update_user_table # that's the (1)
       update_userdeal_table  # that's the (2)
       # show_modal_message
       respond_to do |format|
         format.js
       end  
  end

  private
  def update_user_table
    # update the table User so it needs to connect to internet and acces the distant User table
  end
  def update_userdeal_table
    # update the table UserDeal table so it needs to connect to internet and access the distant UserDeal table
  end
end

This points to a js.erb view file

deal_modal.js.erb

showModalMessage("Here is your result <variable and all>);

To manage the ajax, error, timeouts... (if necessary to the resolution of the question), I use Rails UJS settings.

IMPORTANT: It is here that in case of error or timeout, I send the error / timeout modal message that comes in place of the one you normally get (see just above "Here is your result..")

$(document).on('page:change', function () {
      $("#zone").
        on('ajax:error',function(event,xhr, status, error){
          console.log(' ajax call failed:', error);
          var msg;
          msg = Messenger().post({
            hideAfter:  4, 
            message:    "sorry it took too long, try again."
          });
    });

$(document).on('page:change', function () {
  //set timeout Rails UJS ajax option that will display message for ajax:error cases defined above
  $.rails.ajax = function(options) {
    if (!options.timeout) {
      options.timeout = 5000;
    }    
    return $.ajax(options);
  };
});

Solution

  • So the transaction will only rollback if an error is thrown. If an unhandled error is thrown, your application will crash and it will show a 500 error in some way.

    In order to display the response to the user, on success or error, you will need to render something. So you don't want to prevent the respond_to block from executing. One way to handle this would be to set a flag via an instance variable.

    def deal_modal
      begin
        Deal.transaction do
          update_user_table
          update_userdeal_table
        end
        @success = true
      rescue
        @success = false
      end
      # show_modal_message
      respond_to do |format|
        format.js
      end  
    end
    

    Then in deal_modal.js.erb

    <% if @success %>
      showModalMessage("Here is your result <variable and all>");
    <% else %>
      showModalMessage("There was a problem");
    <% end %>
    

    EDIT:

    Dealing with connection issues is definitely tricky and there isn't really one ideal solution. I would generally let the database continue uninterrupted and let it return either a success or failure on it's own time. For lengthy transactions, you can use a gem like delayed_job or sidekiq to process the action in the background and let the rails controller return a response saying "...pending..." or something. Unless you're using websockets on the frontend, this means continually polling the server with ajax requests to see if the background process is complete.