Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional context #736

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,26 @@ You can ignore the default callbacks globally unless the callback action is spec
Audited.ignored_default_callbacks = [:create, :update] # ignore callbacks create and update
```

### Context

You can attach context to each audit using an `audit_context` attribute on your model.

```ruby
user.update!(name: "Ryan", audit_context: {class_name: self.class.name, id: self.id})
user.audits.last.context # => {"class_name"=>"User", "id"=>1}
```

or using global context, it will be merged with the model context:

```ruby
Audited.context = {class_name: self.class.name, id: self.id}
user.update!(name: "Ryan")
user.audits.last.context # => {"class_name"=>"User", "id"=>1}

user.update!(name: "Brian", audit_context: {sample_key: "sample_value"})
user.audits.last.context # => {"class_name"=>"User", "id"=>2, "sample_key"=>"sample_value"}
```

### Comments

You can attach comments to each audit using an `audit_comment` attribute on your model.
Expand Down
5 changes: 5 additions & 0 deletions lib/audited.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Audited
# Wrapper around ActiveSupport::CurrentAttributes
class RequestStore < ActiveSupport::CurrentAttributes
attribute :audited_store
attribute :audit_context
end

class << self
Expand Down Expand Up @@ -34,6 +35,10 @@ def store
RequestStore.audited_store ||= {}
end

def context
RequestStore.audit_context ||= {}
end

def config
yield(self)
end
Expand Down
6 changes: 5 additions & 1 deletion lib/audited/audit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class Audit < ::ActiveRecord::Base
belongs_to :user, polymorphic: true
belongs_to :associated, polymorphic: true

before_create :set_version_number, :set_audit_user, :set_request_uuid, :set_remote_address
before_create :set_version_number, :set_audit_user, :set_request_uuid, :set_remote_address, :set_audit_context

cattr_accessor :audited_class_names
self.audited_class_names = Set.new
Expand Down Expand Up @@ -198,5 +198,9 @@ def set_request_uuid
def set_remote_address
self.remote_address ||= ::Audited.store[:current_remote_address]
end

def set_audit_context
self.context = (::Audited.context || {}).merge(context || {})
end
end
end
10 changes: 5 additions & 5 deletions lib/audited/auditor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def audited(options = {})

class_attribute :audit_associated_with, instance_writer: false
class_attribute :audited_options, instance_writer: false
attr_accessor :audit_version, :audit_comment
attr_accessor :audit_version, :audit_comment, :audit_context

self.audited_options = options
normalize_audited_options
Expand Down Expand Up @@ -332,27 +332,27 @@ def audits_to(version = nil)

def audit_create
write_audit(action: "create", audited_changes: audited_attributes,
comment: audit_comment)
comment: audit_comment, context: audit_context)
end

def audit_update
unless (changes = audited_changes(exclude_readonly_attrs: true)).empty? && (audit_comment.blank? || audited_options[:update_with_comment_only] == false)
write_audit(action: "update", audited_changes: changes,
comment: audit_comment)
comment: audit_comment, context: audit_context)
end
end

def audit_touch
unless (changes = audited_changes(for_touch: true, exclude_readonly_attrs: true)).empty?
write_audit(action: "update", audited_changes: changes,
comment: audit_comment)
comment: audit_comment, context: audit_context)
end
end

def audit_destroy
unless new_record?
write_audit(action: "destroy", audited_changes: audited_attributes,
comment: audit_comment)
comment: audit_comment, context: audit_context)
end
end

Expand Down
1 change: 1 addition & 0 deletions lib/generators/audited/install_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class InstallGenerator < Rails::Generators::Base

class_option :audited_changes_column_type, type: :string, default: "text", required: false
class_option :audited_user_id_column_type, type: :string, default: "integer", required: false
class_option :audited_table_name, type: :string, default: "audits", required: false

source_root File.expand_path("../templates", __FILE__)

Expand Down
12 changes: 12 additions & 0 deletions lib/generators/audited/templates/add_context_to_audits.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

<%- table_name = options[:audited_table_name].underscore.pluralize -%>
class <%= migration_class_name %> < <%= migration_parent %>
def self.up
add_column :<%= table_name %>, :context, :jsonb
end

def self.down
remove_column :<%= table_name %>, :context
end
end
19 changes: 9 additions & 10 deletions lib/generators/audited/templates/install.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# frozen_string_literal: true

<%- table_name = options[:audited_table_name].underscore.pluralize -%>
class <%= migration_class_name %> < <%= migration_parent %>
def self.up
create_table :audits, :force => true do |t|
create_table :<%= table_name %> do |t|
t.column :auditable_id, :integer
t.column :auditable_type, :string
t.column :associated_id, :integer
Expand All @@ -12,21 +11,21 @@ def self.up
t.column :username, :string
t.column :action, :string
t.column :audited_changes, :<%= options[:audited_changes_column_type] %>
t.column :version, :integer, :default => 0
t.column :version, :integer, default: 0
t.column :comment, :string
t.column :remote_address, :string
t.column :request_uuid, :string
t.column :created_at, :datetime
end

add_index :audits, [:auditable_type, :auditable_id, :version], :name => 'auditable_index'
add_index :audits, [:associated_type, :associated_id], :name => 'associated_index'
add_index :audits, [:user_id, :user_type], :name => 'user_index'
add_index :audits, :request_uuid
add_index :audits, :created_at
add_index :<%= table_name %>, [:auditable_type, :auditable_id, :version], name: "<%= table_name %>_auditable_index"
add_index :<%= table_name %>, [:associated_type, :associated_id], name: "<%= table_name %>_associated_index"
add_index :<%= table_name %>, [:user_id, :user_type], name: "<%= table_name %>_user_index"
add_index :<%= table_name %>, :request_uuid
add_index :<%= table_name %>, :created_at
end

def self.down
drop_table :audits
drop_table :<%= table_name %>
end
end
14 changes: 12 additions & 2 deletions lib/generators/audited/upgrade_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ class UpgradeGenerator < Rails::Generators::Base
include Audited::Generators::MigrationHelper
extend Audited::Generators::Migration

class_option :audited_table_name, type: :string, default: "audits", required: false

source_root File.expand_path("../templates", __FILE__)

def copy_templates
migrations_to_be_applied do |m|
migration_template "#{m}.rb", "db/migrate/#{m}.rb"
migrations_to_be_applied do |template_name|
name = "db/migrate/#{template_name}.rb"
if options[:audited_table_name] != "audits"
name = name.gsub("_to_audits", "_to_#{options[:audited_table_name]}")
end
migration_template "#{template_name}.rb", name
end
end

Expand Down Expand Up @@ -64,6 +70,10 @@ def migrations_to_be_applied
if indexes.any? { |i| i.columns == %w[auditable_type auditable_id] }
yield :add_version_to_auditable_index
end

unless columns.include?("context")
yield :add_context_to_audits
end
end
end
end
Expand Down
15 changes: 14 additions & 1 deletion spec/audited/auditor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ class CallbacksSpecified < ::ActiveRecord::Base
end

describe "on create" do
let(:user) { create_user status: :reliable, audit_comment: "Create" }
let(:user) { create_user status: :reliable, audit_comment: "Create", audit_context: {sample_key: "sample_value"} }

it "should change the audit count" do
expect {
Expand Down Expand Up @@ -370,6 +370,19 @@ class CallbacksSpecified < ::ActiveRecord::Base
expect(user.audits.first.comment).to eq("Create")
end

it "should store context" do
expect(user.audits.first.context).to eq({"sample_key" => "sample_value"})
end

context "with global context" do
before { Audited.context[:global_key] = "global_value" }
after { Audited.context.delete(:global_key) }

it "should merge global context" do
expect(user.audits.first.context).to eq({"sample_key" => "sample_value", "global_key" => "global_value"})
end
end

it "should not audit an attribute which is excepted if specified on create or destroy" do
on_create_destroy_except_name = Models::ActiveRecord::OnCreateDestroyExceptName.create(name: "Bart")
expect(on_create_destroy_except_name.audits.first.audited_changes.keys.any? { |col| ["name"].include? col }).to eq(false)
Expand Down
1 change: 1 addition & 0 deletions spec/support/active_record/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
t.column :comment, :string
t.column :remote_address, :string
t.column :request_uuid, :string
t.column :context, :jsonb
t.column :created_at, :datetime
end

Expand Down
20 changes: 20 additions & 0 deletions test/upgrade_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,24 @@ class UpgradeGeneratorTest < Rails::Generators::TestCase
assert_includes(content, "class AddCommentToAudits < ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]\n")
end
end

test "generate migration with context column change" do
load_schema 6

run_generator %w[upgrade]

assert_migration "db/migrate/add_context_to_audits.rb" do |content|
assert_match(/add_column :audits, :context, :jsonb/, content)
end
end

test "generate migration with context column change for custom table name" do
load_schema 6

run_generator %w[upgrade --audited_table_name=custom_audits]

assert_migration "db/migrate/add_context_to_custom_audits.rb" do |content|
assert_match(/add_column :custom_audits, :context, :jsonb/, content)
end
end
end