active-audit 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e1fca837bb3e2d692f664310ebe1beffad3484bd
4
+ data.tar.gz: a7f74a56a41b4443b00de87699df32bef7278d48
5
+ SHA512:
6
+ metadata.gz: 5cc11c9ece3877bac607d8083b56bf40e649a929c91768abe22174cdf32527b93c60bc4c917d6e529f79e704957cbd0fc509b27061c3608dba2cc82938fbe8e6
7
+ data.tar.gz: 9d089b1ac422adf173792fa6a4ae1c326c9bc62cbf1d951bdda6bb9ea9ae92bd811add470032b166530606dd12718316b2f53870ff5c91d2d7f52d3e287af24b
@@ -0,0 +1,34 @@
1
+ require 'active_support'
2
+ require 'active_audit/audit_repository'
3
+
4
+ module ActiveAudit
5
+ extend ActiveSupport::Autoload
6
+
7
+ eager_autoload { autoload :Base }
8
+ autoload :StorageAdapters
9
+
10
+ class << self
11
+
12
+ attr_accessor :storage_adapter, :current_user_method, :ignored_attributes, :job_queue, :delayed_auditing, :default_user, :extract_user_profile
13
+
14
+ def configure
15
+ @current_user_method = :current_user
16
+ @ignored_attributes = %w(created_at updated_at)
17
+ @job_queue = :audits
18
+ @delayed_auditing = false
19
+ @extract_user_profile = lambda { |user| { id: user.id } }
20
+ self.eager_load!
21
+ yield(self) if block_given?
22
+ AuditRepository.storage_adapter = storage_adapter if storage_adapter
23
+ end
24
+
25
+ def session
26
+ Thread.current[:auditing_store] ||= {}
27
+ end
28
+
29
+ def add_hint comment
30
+ self.session[:comment] = comment
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,83 @@
1
+ require 'rails/observers/active_model/active_model'
2
+ require 'active_model'
3
+ require 'virtus'
4
+
5
+ module ActiveAudit
6
+ class Audit
7
+ include Virtus.model
8
+
9
+ extend ActiveModel::Callbacks
10
+ define_model_callbacks :create, :save
11
+
12
+ include ActiveModel::Observing
13
+
14
+ attribute :item_id, Integer
15
+ attribute :event, String
16
+ attribute :type, String
17
+ attribute :changes, Hash
18
+ attribute :user, Hash, default: lambda {|o,a| ActiveAudit.default_user }
19
+ attribute :comment, String
20
+ attribute :recorded_at, Time, default: lambda { |o,a| Time.now.utc }
21
+
22
+ before_save do
23
+ self.recorded_at = Time.at(self.recorded_at) if self.recorded_at.is_a? Integer
24
+ self.user = ActiveAudit.extract_user_profile.call(self.user) unless self.user.nil? || self.user.is_a?(Hash)
25
+ end
26
+
27
+ def initialize *args
28
+ attributes = args.extract_options!
29
+ if attributes.empty?
30
+ if args.count == 2
31
+ initialize_from_record(*args)
32
+ else
33
+ raise ArgumentError, "You need to supply at least one attribute"
34
+ end
35
+ else
36
+ super attributes
37
+ end
38
+ end
39
+
40
+ private def initialize_from_record event, record
41
+ if event == 'update'
42
+ if record.respond_to?(:aasm) && record.aasm.current_event
43
+ event = record.aasm.current_event.to_s.sub(/!$/, '')
44
+ end
45
+ self.changes = record.previous_changes.select { |key, value| record.class.auditing_options[:only].include? key }
46
+ self.changes.merge!(record.association_previous_changes.select { |key, value| record.class.auditing_options[:associations].include? key })
47
+ self.changes = self.changes.map do |key, values|
48
+ if values.any? { |v| v.is_a?(Time) }
49
+ [key, values.map { |x| x && x.utc.iso8601 }]
50
+ else
51
+ [key, values]
52
+ end
53
+ end.to_h
54
+ end
55
+ self.event = event
56
+ self.type = record.class.auditing_options[:type]
57
+ self.item_id = record.id
58
+ set_default_attributes
59
+ end
60
+
61
+ def changed?
62
+ ['create', 'destroy'].include?(self.event) || changes.present?
63
+ end
64
+
65
+ def save options={}
66
+ self.run_callbacks :save do
67
+ yield if changed?
68
+ end
69
+ end
70
+
71
+ def self.create(attributes, options={})
72
+ object = self.new(attributes)
73
+ object.run_callbacks :create do
74
+ object.save(options)
75
+ object
76
+ end
77
+ end
78
+
79
+ def serialize
80
+ self.attributes.select {|k,v| v.present?}.merge(recorded_at: self.recorded_at.to_i, type: self.type)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveAudit
2
+ class AuditPusher < ActiveJob::Base
3
+ queue_as ActiveAudit.job_queue
4
+
5
+ def perform *args
6
+ AuditRepository.create(args[0])
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,38 @@
1
+ module ActiveAudit
2
+ class AuditRepository
3
+ class << self
4
+ def storage_adapter=(repo_name)
5
+ @@storage_adapter = load_adapter(repo_name)
6
+ end
7
+
8
+ def storage_adapter
9
+ @@storage_adapter ||= ActiveAudit::StorageAdapters::TestAdapter
10
+ end
11
+
12
+ def find_by_record record, options={}
13
+ storage_adapter.find_by_record(record, options)
14
+ end
15
+
16
+ def create attributes
17
+ save Audit.new(attributes)
18
+ end
19
+
20
+ def save audit
21
+ audit.save do
22
+ storage_adapter.save audit
23
+ end
24
+ end
25
+
26
+ private
27
+ def load_adapter name
28
+ config = \
29
+ begin
30
+ Rails.application.config_for(:active_audit)
31
+ rescue RuntimeError
32
+ {}
33
+ end
34
+ return "ActiveAudit::StorageAdapters::#{name.to_s.camelize}Adapter".constantize.new config
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,82 @@
1
+ require 'active_audit/errors'
2
+ require 'active_audit/audit'
3
+ require 'active_audit/audit_pusher'
4
+ require 'active_audit/dirty_association'
5
+ require 'active_audit/sweeper'
6
+
7
+ module ActiveAudit
8
+ module Base
9
+ extend ActiveSupport::Concern
10
+ include DirtyAssociation
11
+
12
+ class_methods do
13
+
14
+ def auditing_options
15
+ @auditing_options ||= {
16
+ type: self.to_s.underscore,
17
+ only: self.attribute_names - ActiveAudit.ignored_attributes
18
+ }
19
+ end
20
+
21
+ def audit *args
22
+ options = args.extract_options!.dup
23
+ options.assert_valid_keys(:type, :except, :associations, :unless)
24
+ options[:type] = options[:type].to_s if options[:type]
25
+ except = options[:except] ? options[:except].map(&:to_s) : []
26
+ only = if args.present? then args.map(&:to_s) else auditing_options[:only] end
27
+ options[:only] = only - except
28
+ if options[:associations]
29
+ options[:associations] = options[:associations].map(&:to_s)
30
+ stain *options[:associations]
31
+ end
32
+ auditing_options.update options
33
+ end
34
+
35
+ end
36
+
37
+ included do
38
+ if self < ActiveRecord::Base
39
+ after_commit on: [:create] { write_audit('create') }
40
+ after_commit on: [:update] { write_audit('update') }
41
+ after_commit on: [:destroy] { write_audit('destroy') }
42
+ elsif defined?(Mongoid::Document) && self < Mongoid::Document
43
+ after_create { write_audit('create') }
44
+ after_update { write_audit('update') }
45
+ after_destroy { write_audit('destroy') }
46
+ else
47
+ raise Errors::UnsupportedModel, "can audit ActiveRecord and Mongoid models only"
48
+ end
49
+ end
50
+
51
+ def audits options={}
52
+ AuditRepository.find_by_record self, options
53
+ end
54
+
55
+ private
56
+ def audited?
57
+ if condition = self.class.auditing_options[:unless]
58
+ case condition
59
+ when Symbol, String
60
+ self.public_send condition
61
+ when Proc
62
+ condition.call self
63
+ end
64
+ end
65
+ return true
66
+ end
67
+
68
+ def write_audit event
69
+ if audited?
70
+ audit = Audit.new event, self
71
+ if audit.changed?
72
+ if ActiveAudit.delayed_auditing
73
+ AuditPusher.perform_later audit.serialize
74
+ else
75
+ AuditRepository.save(audit)
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,95 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+
3
+ module ActiveAudit
4
+ module DirtyAssociation
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def stain *association_names
9
+ association_names.each do |association_name|
10
+ if reflection = _reflect_on_association(association_name)
11
+ if reflection.collection? && through_refl = reflection.try(:through_reflection)
12
+ through_id = reflection.foreign_key
13
+ through_name = through_refl.name
14
+ define_association_method_suffix association_name
15
+ ActiveRecord::Associations::Builder::CollectionAssociation.define_callback(self, :after_add, through_name, before_add: lambda do |model, relation|
16
+ puts "addubng"
17
+ puts relation.attributes
18
+ DirtyAssociation.record_add association_name, model, relation, through_name, through_id
19
+ end)
20
+ ActiveRecord::Associations::Builder::CollectionAssociation.define_callback(self, :before_remove, through_name, before_remove: lambda do |model, relation|
21
+ DirtyAssociation.record_remove association_name, model, relation, through_name, through_id
22
+ end)
23
+ else
24
+ raise ArgumentError, "'#{association_name}' is not a many-to-many association."
25
+ end
26
+ else
27
+ raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
28
+ end
29
+ end
30
+ end
31
+
32
+
33
+ private
34
+ def define_association_method_suffix association_name
35
+ %w(_changed? _change _was _previously_changed? _previous_change).each do |suffix|
36
+ self.send :define_method, "#{association_name}#{suffix}" do
37
+ send "association#{suffix}", association_name
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ attr_reader :association_previous_changes
44
+
45
+ def association_changes
46
+ @association_changes ||= ActiveSupport::HashWithIndifferentAccess.new
47
+ end
48
+
49
+ def association_changed? association_name
50
+ !!association_changes[association_name]
51
+ end
52
+
53
+ def association_change association_name
54
+ association_changes[association_name]
55
+ end
56
+
57
+ def association_was association_name
58
+ association_changes[association_name].try '[]', 0
59
+ end
60
+
61
+ def association_previously_changed? association_name
62
+ !!association_previous_changes[association_name]
63
+ end
64
+
65
+ def association_previous_change association_name
66
+ association_previous_changes[association_name]
67
+ end
68
+
69
+ def changes_applied
70
+ super
71
+ @association_previous_changes = association_changes
72
+ @association_changes = ActiveSupport::HashWithIndifferentAccess.new
73
+ end
74
+
75
+ module_function
76
+ def init_association_change association_name, model, through, attribute
77
+ old_attributes = model.send(through).map {|r| r.send attribute }
78
+ model.association_changes[association_name] = [old_attributes, old_attributes.dup]
79
+ end
80
+
81
+ def record_add association_name, model, relation, through, attribute
82
+ unless model.association_changes[association_name]
83
+ init_association_change association_name, model, through, attribute
84
+ end
85
+ model.association_changes[association_name][1].push relation.send(attribute)
86
+ end
87
+
88
+ def record_remove association_name, model, relation, through, attribute
89
+ unless model.association_changes[association_name]
90
+ init_association_change association_name, model, through, attribute
91
+ end
92
+ model.association_changes[association_name][1].delete relation.send(attribute)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveAudit
2
+ module Errors
3
+ class UnsupportedModel < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveAudit
2
+ module StorageAdapters
3
+ class ActiveRecordAdapter
4
+
5
+ def initialize options={}
6
+ @connection = ::ActiveRecord::Base.connection
7
+ end
8
+
9
+ def find_by_record record, options={}
10
+ result = @connection.exec_query "SELECT * FROM audits WHERE item_id=$1 AND type=$2 ORDER BY recorded_at DESC;", nil, [[nil, record.id], [nil,record.class.auditing_options[:type]]]
11
+ result.map { |attributes| ActiveAudit::Audit.new attributes }
12
+ end
13
+
14
+ def save audit
15
+ @connection.exec_query('INSERT INTO audits("item_id", "event", "type", "changes", "user_id", "user", "comment", "recorded_at") VALUES ($1,$2,$3,$4,$5,$6,$7,$8);', nil, [
16
+ [nil, audit.item_id],
17
+ [nil, audit.event],
18
+ [nil, audit.type],
19
+ [nil, audit.changes.to_json],
20
+ [nil, audit.user[:id]],
21
+ [nil, audit.user.to_json],
22
+ [nil, audit.comment],
23
+ [nil, audit.recorded_at]
24
+ ])
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,66 @@
1
+ require 'elasticsearch/persistence'
2
+
3
+ module ActiveAudit
4
+ module StorageAdapters
5
+ class ElasticsearchAdapter
6
+ include ::Elasticsearch::Persistence::Repository
7
+
8
+ def initialize options={}
9
+ index options['index'] || :auditing
10
+ client ::Elasticsearch::Client.new url: options['url'] || 'localhost:9200'
11
+ klass ActiveAudit::Audit
12
+ end
13
+
14
+ type :_default_
15
+
16
+ settings do
17
+ mappings _all: { enabled: false }, date_detection: false, dynamic_templates: [ {
18
+ strings: {
19
+ mapping: {
20
+ index: "not_analyzed",
21
+ type: "string"
22
+ },
23
+ match: "*",
24
+ match_mapping_type: "string"
25
+ }
26
+ } ] do
27
+ indexes :item_id, type: "long"
28
+ indexes :event, type: "string", index: "not_analyzed"
29
+ indexes :changes, type: "object", dynamic: true
30
+ indexes :user, type: "object", properties: {
31
+ id: { type: "long" },
32
+ name: {type: "string", index: "not_analyzed"}
33
+ }
34
+ indexes :comment, type: "string", index: "not_analyzed"
35
+ indexes :recorded_at, type: "date"
36
+ end
37
+ end
38
+
39
+ def find_by_record record, options={}
40
+ self.type "#{record.class.auditing_options[:type]}_events"
41
+ query = {
42
+ query: { filtered: { filter: { bool: { must: [{ term: { item_id: record.id }}]}}}},
43
+ sort: { recorded_at: { order: "desc" }}
44
+ }
45
+ if options[:from] && options[:to]
46
+ query[:query][:filtered][:filter][:bool][:must].push range: { recorded_at: { gte: options[:from], lt: options[:to] }}
47
+ elsif options[:from]
48
+ query[:query][:filtered][:filter][:bool][:must].push range: { recorded_at: { gte: options[:from] }}
49
+ elsif options[:to]
50
+ query[:query][:filtered][:filter][:bool][:must].push range: { recorded_at: { lt: options[:to] }}
51
+ end
52
+ if options[:comment]
53
+ query[:query][:filtered][:filter][:bool][:must].push wildcard: { comment: "*#{options[:comment]}*" }
54
+ end
55
+ search(query).to_a
56
+ end
57
+
58
+ define_method :gateway_save, self.new.gateway.method(:save).to_proc
59
+
60
+ def save audit
61
+ self.send :gateway_save, audit, type: "#{audit.type}_events"
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ require 'mongo'
2
+
3
+ module ActiveAudit
4
+ module StorageAdapters
5
+ class MongoAdapter
6
+
7
+ def initialize options={}
8
+ client = ::Mongo::Client.new options['hosts'], options['options'].symbolize_keys
9
+ @collection = client[:audits]
10
+ end
11
+
12
+ def find_by_record record, options={}
13
+ result = @collection.find(type: record.class.auditing_options[:type], item_id: record.id)
14
+ result.map { |audit| ActiveAudit::Audit.new audit.symbolize_keys }
15
+ end
16
+
17
+ def save audit
18
+ @collection.insert_one(audit.attributes)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveAudit
2
+ module StorageAdapters
3
+ class TestAdapter
4
+
5
+ class << self
6
+ def find_by_record record, options={}
7
+ raise NotImplementedError.new "can not retrieve audits with `TestAdapter`."
8
+ end
9
+
10
+ def save audit
11
+ Rails.logger.info "audit -> #{audit.attributes}"
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveAudit
2
+ module StorageAdapters
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :ActiveRecordAdapter
6
+ autoload :ElasticsearchAdapter
7
+ autoload :MongoAdapter
8
+ autoload :TestAdapter
9
+ end
10
+ end
@@ -0,0 +1,55 @@
1
+ require "rails/observers/activerecord/active_record"
2
+ require "rails/observers/action_controller/caching"
3
+
4
+ module ActiveAudit
5
+ class Sweeper < ActionController::Caching::Sweeper
6
+ observe ActiveAudit::Audit
7
+
8
+ def around controller
9
+ begin
10
+ self.controller = controller
11
+ self.user = current_user
12
+ yield
13
+ ensure
14
+ self.controller = nil
15
+ self.user = nil
16
+ end
17
+ end
18
+
19
+ def before_create audit
20
+ audit.user = self.user if self.user
21
+ audit.comment = controller.params[:comment] if controller.respond_to?(:params, true)
22
+ audit.comment ||= ActiveAudit.session[:comment]
23
+ end
24
+
25
+ def current_user
26
+ current_user = controller.send(ActiveAudit.current_user_method) if controller.respond_to?(ActiveAudit.current_user_method, true)
27
+ end
28
+
29
+ def controller
30
+ ActiveAudit.session[:current_controller]
31
+ end
32
+
33
+ def controller=(value)
34
+ ActiveAudit.session[:current_controller] = value
35
+ end
36
+
37
+ def user
38
+ ActiveAudit.session[:current_user]
39
+ end
40
+
41
+ def user=(value)
42
+ ActiveAudit.session[:current_user] = value
43
+ end
44
+
45
+ end
46
+
47
+ ActiveSupport.on_load(:action_controller) do
48
+ if defined?(ActionController::Base)
49
+ ActionController::Base.around_action ActiveAudit::Sweeper.instance
50
+ end
51
+ if defined?(ActionController::API)
52
+ ActionController::API.around_action ActiveAudit::Sweeper.instance
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,34 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/active_record'
3
+
4
+ module ActiveAudit
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include ActiveRecord::Generators::Migration
8
+
9
+ desc "Create initializer and config files for active audit base on the adapter you define. In case of 'active_record' adapter a migration will be generated as well."
10
+
11
+ argument :adapter, required: true, type: :string, desc: "The name of the storage adapter you want to use.",
12
+ :banner => "adapter_name"
13
+
14
+ source_root File.expand_path("../templates", __FILE__)
15
+
16
+ def copy_initializer_file
17
+ template "initializer.rb", "config/initializers/active_audit.rb"
18
+ end
19
+
20
+ def copy_config_file
21
+ unless adapter == 'active_record'
22
+ copy_file "#{adapter}_config.yml", "config/active_audit.yml"
23
+ end
24
+ end
25
+
26
+ def generate_migration
27
+ if adapter == 'active_record'
28
+ migration_template 'migration.rb', 'db/migrate/create_audits.rb'
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,12 @@
1
+ development:
2
+ host: 'http://localhost:9200'
3
+ index: auditing
4
+ test:
5
+ url: 'http://localhost:9200'
6
+ index: auditing
7
+ staging:
8
+ url: 'http://localhost:9200'
9
+ index: auditing
10
+ production:
11
+ url: <%= ENV["AUDIT_STORE_HOST"] %>
12
+ index: <%= ENV["AUDIT_STORE_INDEX"] %>
@@ -0,0 +1,10 @@
1
+ development:
2
+ hosts:
3
+ - localhost:27017
4
+ options:
5
+ database: audits
6
+ production:
7
+ hosts:
8
+ - <%= ENV["AUDITING_MONGO_HOST"].split(',').join '\n\t\t- ' %>
9
+ options:
10
+ database: <%= ENV["AUDITING_MONGO_DATABASE_NAME"] %>
@@ -0,0 +1,9 @@
1
+ ActiveAudit.configure do |config|
2
+ config.storage_adapter = :<%= adapter.to_sym %>
3
+ #config.current_user_method = :current_user
4
+ #config.ignored_attributes = %w(created_at updated_at)
5
+ #config.job_queue = :audits
6
+ #config.delayed_auditing = false
7
+ #config.default_user = { id: 1 }
8
+ #config.extract_user_profile = lambda { |user| { id: user.id } }
9
+ end
@@ -0,0 +1,22 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :audits, :force => true do |t|
4
+ t.column :item_id, :integer
5
+ t.column :type, :string
6
+ t.column :event, :string
7
+ t.column :changes, :json
8
+ t.column :user_id, :integer
9
+ t.column :user, :json
10
+ t.column :comment, :string
11
+ t.column :recorded_at, :datetime
12
+ end
13
+
14
+ add_index :audits, [:type, :item_id], :name => 'item_index'
15
+ add_index :audits, :user_id, :name => 'user_index'
16
+ add_index :audits, :recorded_at
17
+ end
18
+
19
+ def self.down
20
+ drop_table :audits
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ development:
2
+ hosts:
3
+ - localhost:27017
4
+ options:
5
+ database: audits
6
+ production:
7
+ hosts:
8
+ - <%= ENV["AUDITING_MONGO_HOST"].split(',').join '\n\t\t- ' %>
9
+ options:
10
+ database: <%= ENV["AUDITING_MONGO_DATABASE_NAME"] %>
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active-audit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Tariq Abughofa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails-observers
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: virtus
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ description: ORM extension to track model changes. It also support keeping record
42
+ of who made the changes and why.
43
+ email: mhdtariqabughofa@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/active-audit.rb
49
+ - lib/active_audit/audit.rb
50
+ - lib/active_audit/audit_pusher.rb
51
+ - lib/active_audit/audit_repository.rb
52
+ - lib/active_audit/base.rb
53
+ - lib/active_audit/dirty_association.rb
54
+ - lib/active_audit/errors.rb
55
+ - lib/active_audit/storage_adapters.rb
56
+ - lib/active_audit/storage_adapters/active_record_adapter.rb
57
+ - lib/active_audit/storage_adapters/elasticsearch_adapter.rb
58
+ - lib/active_audit/storage_adapters/mongo_adapter.rb
59
+ - lib/active_audit/storage_adapters/test_adapter.rb
60
+ - lib/active_audit/sweeper.rb
61
+ - lib/generators/active_audit/install_generator.rb
62
+ - lib/generators/active_audit/templates/adapter_config/elastic_search_config.yml
63
+ - lib/generators/active_audit/templates/adapter_config/mongo_config.yml
64
+ - lib/generators/active_audit/templates/initializer.rb
65
+ - lib/generators/active_audit/templates/migration.rb
66
+ - lib/generators/active_audit/templates/mongo_config.yml
67
+ homepage: http://rubygems.org/gems/active-audit
68
+ licenses:
69
+ - MIT
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.4.8
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: ORM extension to track model changes.
91
+ test_files: []