Rails after_action order is in reverse

(Everyday Code – instead of keeping our knowledge in a README.md let’s share it with the internet)

We’ve used Rails after_action for controllers twice in our platforms and products. It allows you to execute something after a rails controller action.

Why? What is the IRL use case?

Users upload files for a 3D model or building instructions. We want to call an external service to convert this 3D model or buildin3d instructions and we schedule a job. We want to ping this external service. So we make a network request in the controller.

This is never a good idea. Generally you want to return fast from the call to your controller action. On Heroku you have a 15 seconds time limit to return and as a user experience it is good to return a result right the way and to do your job on the background and to report on the progress with some clever JS.

But for this specific case we have reasons to try to ping the external service. It generally takes about 0.5-1s, it is not a bottleneck for the user experience, and it works well. Except for the cases when it does not work well.

Today was such a day. Heroku dynos were trying to connect with the service, but it was taking longer than 15 seconds. I don’t know why. So i decided to revise the implementation.

What is special about after_action order?

It is the reverse of before_action.

We want to call the external service in an after action. In this way it is outside of the logic of the controller method and if it fails, we handle the exception in the after_action. It is a good separation. The real code is:

module IsBackend::JenkinsWorkoff extend ActiveSupport::Concern
  included do
    after_action do
      unless @jenkins_rebuild == nil || @jenkins_rebuild.running?
        begin
          JenkinsClientFactory.force_jobs_workoff timeout: 10
        rescue => e
          config.logger.info e.message
        end
      end
    end
  end
end

The issue is with the order of the after_action when they are more then one.

Generally before_action are executed in the order they are defined.

  before_action do
    # will be first
  end

  before_action except: [:index, :destroy] do
    # will be second
  end

But after_action are executed in the reverse order

  after_action do
    # will be SECOND
  end

  after_action except: [:index, :destroy] do
    # will be FIRST
  end

Why? I will leave the PR to explain it best – https://github.com/rails/rails/issues/5464

How do we use it?

When building the buildin3d instruction below we’ve pinged the external service in the build process. Because of the after_action we can enjoy this FabBrix dog.

FabBRIX Pets, Dog in 3D building instructions