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 < 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.