Skip to main content Link Search Menu Expand Document (external link)

Audited

Audited is one of the most popular gems in Ruby on Rails. This gem will help us keep track of what/when/who changed a record.

In this article, we will install audited and discover how it works in our application.

Table of Contents

  1. What is Audited
  2. How to install audited
  3. Setup
  4. Usage
  5. Current User Tracking

What is Audited

Audited (formerly known as acts_as_audited) is an ORM plugin that records all model modifications. Audited additionally keeps track of who made the modifications, saves comments, and associates models with the changes.

How to install audited

Reminder:

Make sure that your containers are up and running.

In your Gemfile, add gem audited.

gem 'audited'

Then run bundle install.

 root@0122:/usr/src/app# bundle install
Fetching gem metadata from https://rubygems.org/..........
Resolving dependencies...
...
Installing audited 5.2.0
Bundle complete! 24 Gemfile dependencies, 96 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

Setup

Create the audits table with rails generate audited:install command, then migrate.

 root@0122:/usr/src/app# rails generate audited:install
      create  db/migrate/xxxxxxxxxxxxxx_install_audited.rb
 root@0122:/usr/src/app# rails db:migrate
== xxxxxxxxxxxxxx InstallAudited: migrating ===================================
-- create_table(:audits, {:force=>true}) -> 0.0284s
-- add_index(:audits, [:auditable_type, :auditable_id, :version], {:name=>"auditable_index"}) -> 0.0134s
-- add_index(:audits, [:associated_type, :associated_id], {:name=>"associated_index"})        -> 0.0163s
-- add_index(:audits, [:user_id, :user_type], {:name=>"user_index"})                          -> 0.0120s
-- add_index(:audits, :request_uuid)                                                          -> 0.0120s
-- add_index(:audits, :created_at)                                                            -> 0.0116s
== xxxxxxxxxxxxxx InstallAudited: migrated (0.0956s) ==========================

By default, the “safe YAML” loading method does not enable all classes to be deserialized (rails 6+ versions). To identify classes in our application that are judged as safe, we need to add it to yaml column permitted classes in our application.rb.

# config/application.rb

# ...
module App
  class Application < Rails::Application
    # ...
    config.generators.system_tests = nil
+   config.active_record.yaml_column_permitted_classes = %w[String Integer NilClass Float Time Date FalseClass Hash Array DateTime TrueClass BigDecimal
+                                                           ActiveSupport::TimeWithZone ActiveSupport::TimeZone ActiveSupport::HashWithIndifferentAccess]
  end
end

Usage

Simply call audited on the model you want to audit.

# app/models/order.rb

class Order < ApplicationRecord
  include AASM
+ audited
  belongs_to :user
  # ...
end

Try it out on console and see the audits.

irb(main):001:0> user = User.client.first
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`genre` = 0 ORDER BY `users`.`id` ASC LIMIT 1
=> #<User id: 2, email: "sid.klocko@bednar-koss.net", created_at: "xxxx-xx-xx xx:xx:xx.xxxxxxxxx +xxxx", updated_at: "xxxx-xx-xx xx:xx:xx.xxxxxxxxx +xxxx", genre: "client", balance: 0.5e2>
irb(main):002:0> order = user.orders.create(amount: 20)
  TRANSACTION (0.2ms)  BEGIN
  Order Create (0.2ms)  INSERT INTO `orders` (`amount`, `serial_number`, `user_id`, `state`, `created_at`, `updated_at`) VALUES (20.0, NULL, 2, 'pending', 'xxxx-xx-xx xx:xx:xx.xxxxxxxxx', 'xxxx-xx-xx xx:xx:xx.xxxxxxxxx')                                                 
  Audited::Audit Create (0.4ms)  INSERT INTO `audits` (`auditable_id`, `auditable_type`, `associated_id`, `associated_type`, `user_id`, `user_type`, `username`, `action`, `audited_changes`, `version`, `comment`, `remote_address`, `request_uuid`, `created_at`) VALUES (19, 'Order', NULL, NULL, NULL, NULL, NULL, 'create', '---\namount: !ruby/object:BigDecimal 18:0.2e2\nserial_number: \nuser_id: 2\nstate: pending\n', 1, NULL, NULL, 'f825fd62-b4b6-40f0-9411-82fd4a8a781a', 'xxxx-xx-xx xx:xx:xx.xxxxxxxxx')
  Audited::Audit Maximum (0.5ms)  SELECT MAX(`audits`.`version`) FROM `audits` WHERE `audits`.`auditable_id` = 19 AND `audits`.`auditable_type` = 'Order'
  Audited::Audit Create (0.4ms)  INSERT INTO `audits` (`auditable_id`, `auditable_type`, `associated_id`, `associated_type`, `user_id`, `user_type`, `username`, `action`, `audited_changes`, `version`, `comment`, `remote_address`, `request_uuid`, `created_at`) VALUES (19, 'Order', NULL, NULL, NULL, NULL, NULL, 'update', '---\nserial_number:\n- \n- gem-000000019\n', 2, NULL, NULL, '700068a5-7db1-47d4-85df-6cd52641b9f0', 'xxxx-xx-xx xx:xx:xx.xxxxxxxxx') 
  Order Update (0.4ms)  UPDATE `orders` SET `orders`.`serial_number` = 'gem-000000019', `orders`.`updated_at` = 'xxxx-xx-xx xx:xx:xx.xxxxxxxxx' WHERE `orders`.`id` = 19
  TRANSACTION (0.6ms)  COMMIT                           
=> #<Order:0x00007f8e509067b8>
irb(main):0043:0> order.audits
  Audited::Audit Load (0.5ms)  SELECT `audits`.* FROM `audits` WHERE `audits`.`auditable_id` = 19 AND `audits`.`auditable_type` = 'Order' ORDER BY `audits`.`version` ASC
=>
[#<Audited::Audit:0x00007f8e50685958
  id: 1, auditable_id: 19, auditable_type: "Order",
  ...
  action: "create",                                              
  audited_changes: {"amount"=>0.2e2, "serial_number"=>nil, "user_id"=>2, "state"=>"pending"},
  ... >,
 #<Audited::Audit:0x00005591c1bde018
  id: 1, auditable_id: 19, auditable_type: "Order",
  ...
  action: "update",
  audited_changes: {"serial_number"=>[nil, "gem-000000019"]},
  ... >]

Current User Tracking

All audited modifications made in a request will automatically be attributed to the current user. Audited utilizes the current_user function in your controller by default.

Let’s create a TopUps feature that will create order. Add resources top_ups for new and create actions.

# config/routes.rb

Rails.application.routes.draw do
  # ...
  constraints(ClientDomainConstraint.new) do
    resources :posts do
      resources :comments, except: :show
    end
+   resources :top_ups, only: [:new, :create]
  end
  # ...
end

Generate controller.

 root@0122:/usr/src/app# rails g controller TopUps
      create  app/controllers/top_ups_controller.rb                                         
      invoke  erb                                                                           
      create    app/views/top_ups                                                           
      invoke  helper            
      create    app/helpers/top_ups_helper.rb

Modify the TopUpsController.

# app/controllers/top_ups_controller.rb

class TopUpsController < ApplicationController
  before_action :authenticate_user!

  def new
    @order = Order.new
  end

  def create
    @order = Order.new
    @order.amount = params[:order][:amount]
    @order.user = current_user
    @order.save
  end
end

Add new top ups view.

<!-- app/views/top_ups/new.html.erb -->

<%= form_with model: @order, url: top_ups_path do |f| %>
  <%= f.label :amount %>
  <%= f.number_field :amount %>

  <%= f.submit %>
<% end %>

Temporarily add debugger to create and take a look at what is happening.

# app/controllers/top_ups_controller.rb

class TopUpsController < ApplicationController
  # ...
  def create
    @order = Order.new
    @order.amount = params[:order][:amount]
    @order.user = current_user
    @order.save
+   debugger
  end
end

Login a client user, go to client.com:3000/top_ups/new, enter an amount and submit. Now take a look at logs.

xx:xx:xx web.1  |      8|   def create
xx:xx:xx web.1  |      9|     @order = Order.new
xx:xx:xx web.1  |     10|     @order.amount = params[:order][:amount]
xx:xx:xx web.1  |     11|     @order.user = current_user
xx:xx:xx web.1  |     12|     @order.save
xx:xx:xx web.1  | =>  13|     debugger
xx:xx:xx web.1  |     14|   end
xx:xx:xx web.1  |     15| end
xx:xx:xx web.1  |  (rdbg) current_user  
xx:xx:xx web.1  |  #<User id: 2, email: "sid.klocko@bednar-koss.net", ... >
xx:xx:xx web.1  |  (rdbg) @order.audits.last.user
xx:xx:xx web.1  |  #<User id: 2, email: "sid.klocko@bednar-koss.net", ... >

Remove the debugger.

# app/controllers/top_ups_controller.rb

class TopUpsController < ApplicationController
  # ...
  def create
    @order = Order.new
    @order.amount = params[:order][:amount]
    @order.user = current_user
    @order.save
-   debugger
  end
end

Now we successfully integrated audited Gem into our application. To know more about audited Gem, read Ruby Docs Audited.


Back to top

Copyright © 2020-2022 Secure Smarter Service, Inc. This site is powered by KodaCamp.