Custom event tracking with ActiveSupport::Notifications and Audited

Track Your Custom Events With Audited & ActiveSupport::Notifications

When it comes to logging or tracking changes in Ruby on Rails models, We typically tend to use either paper_trail or audited gems as these are most popular and widely used gems in Ruby on Rails applications for tracking changes in Rails models, but what if you want to track the custom events on controller level(like following) in addition to model changes? 

  • User login and logout 
  • Password change success or fail
  • User login failed or succeeded
  • OR any other user activity

In this post I will share my learnings on how we can track controller level custom events with the help of model changes tracking gem audited and ActiveSupport::Notifications.

Brief about ActiveSupport::Notifications

ActiveSupport::Notifications gives an APIs for event instrumentation which works on pub/sub model i.e. you can publish an event notification/trigger for the particular subscriber which in turn takes necessary actions for the given event trigger.

For example, publishing and subscribing of a ‘login_success‘ event

Publishing an event

<br>
ActiveSupport::Notifications.instrument('login_failed', event_data)<br>
#This triggers the notification for login_failed event<br>

Subscribing to an event

</p>
<p>ActiveSupport::Notifications.subscribe('login_failed') do |*args|<br>
  event = ActiveSupport::Notifications::Event.new(*args)<br>
  #Take necessary actions on this event<br>
end</p>
<p>

Using Audited for saving custom events

I have created an EventHandler service which publishes/creates events and is used by controllers and subscribers.

<br>
#app/services/event_handler.rb<br>
class EventHandler<br>
  def initialize(event)<br>
    @event = event<br>
  end</p>
<p>  def process!<br>
    audit_data = {<br>
      action: @event.name,<br>
      username: @event.payload['email'] || @event.payload['username'],<br>
      comment: @event.payload[:comment]<br>
    }</p>
<p>    audit_data[:auditable] = @event.payload[:auditable] if @event.payload[:auditable]<br>
    audit_data[:user] = @event.payload[:user] if @event.payload[:user]<br>
    Audited::Audit.create!(audit_data)<br>
  end</p>
<p>  def self.trigger_event_audit(event_name, params, resource, comment=nil)<br>
    event_data = {}<br>
    event_data.merge!(params[:user])<br>
    event_data.merge!(auditable: resource, user: resource, comment: comment)<br>
    ActiveSupport::Notifications.instrument(event_name, event_data)<br>
  end<br>
end</p>
<p>

Using in controller action

Let’s say I have the following piece of code in my controller which allows the user to login.

<br>
class Users::SessionsController &lt; Devise::SessionsController<br>
  ...<br>
  #POST /resource/sign_in<br>
  def create<br>
    self.resource = warden.authenticate(auth_options)<br>
    if resource.present?<br>
      ...<br>
      EventHandler.trigger_event_audit('login_success', params, resource)<br>
      respond_with resource, location: after_sign_in_path_for(resource)<br>
    else<br>
      EventHandler.trigger_event_audit('login_failed', params, resource, 'invalid email/username or password address')<br>
      respond_with resource, location: root_path<br>
    end<br>
  end<br>
  ...<br>
end<br>

Subscribing to events

<br>
  #config/initializers/event_trigger_subscriber.rb<br>
  ActiveSupport::Notifications.subscribe('login_failed') do |*args|<br>
    event = ActiveSupport::Notifications::Event.new(*args)<br>
    EventHandler.new(event).process!<br>
  end</p>
<p>  ActiveSupport::Notifications.subscribe('login_success') do |*args|<br>
    event = ActiveSupport::Notifications::Event.new(*args)<br>
    EventHandler.new(event).process!<br>
  end<br>

Conclusion

Rails ActiveSupport Instrumentation is very handy whenever we need to write the event based triggers for notifications, event tracking, data logging etc. The aim of this post is to demonstrate how we can use the Rails ActiveSupport Instrumentation with audited gem, Specially if implementation of audited gem is in place and on top of it we need to track the custom events without creating any new database schema.

If you have any suggestions or feedback, please feel free to add them in comments section.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.