audited-hp 4.3.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 +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +32 -0
- data/.yardopts +3 -0
- data/Appraisals +22 -0
- data/CHANGELOG +153 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +299 -0
- data/Rakefile +18 -0
- data/gemfiles/rails40.gemfile +9 -0
- data/gemfiles/rails41.gemfile +8 -0
- data/gemfiles/rails42.gemfile +8 -0
- data/gemfiles/rails50.gemfile +8 -0
- data/lib/audited-rspec.rb +4 -0
- data/lib/audited.rb +29 -0
- data/lib/audited/audit.rb +149 -0
- data/lib/audited/auditor.rb +309 -0
- data/lib/audited/rspec_matchers.rb +177 -0
- data/lib/audited/sweeper.rb +60 -0
- data/lib/audited/version.rb +3 -0
- data/lib/generators/audited/install_generator.rb +20 -0
- data/lib/generators/audited/migration.rb +15 -0
- data/lib/generators/audited/templates/add_association_to_audits.rb +11 -0
- data/lib/generators/audited/templates/add_comment_to_audits.rb +9 -0
- data/lib/generators/audited/templates/add_remote_address_to_audits.rb +10 -0
- data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +10 -0
- data/lib/generators/audited/templates/install.rb +30 -0
- data/lib/generators/audited/templates/rename_association_to_associated.rb +23 -0
- data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +9 -0
- data/lib/generators/audited/templates/rename_parent_to_association.rb +11 -0
- data/lib/generators/audited/upgrade_generator.rb +57 -0
- data/spec/audited/audit_spec.rb +199 -0
- data/spec/audited/auditor_spec.rb +607 -0
- data/spec/audited/sweeper_spec.rb +106 -0
- data/spec/audited_spec_helpers.rb +20 -0
- data/spec/rails_app/config/application.rb +8 -0
- data/spec/rails_app/config/database.yml +24 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/development.rb +21 -0
- data/spec/rails_app/config/environments/production.rb +35 -0
- data/spec/rails_app/config/environments/test.rb +47 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/inflections.rb +2 -0
- data/spec/rails_app/config/initializers/secret_token.rb +3 -0
- data/spec/rails_app/config/routes.rb +3 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/active_record/models.rb +99 -0
- data/spec/support/active_record/schema.rb +81 -0
- data/test/db/version_1.rb +17 -0
- data/test/db/version_2.rb +18 -0
- data/test/db/version_3.rb +19 -0
- data/test/db/version_4.rb +20 -0
- data/test/db/version_5.rb +18 -0
- data/test/db/version_6.rb +17 -0
- data/test/install_generator_test.rb +17 -0
- data/test/test_helper.rb +19 -0
- data/test/upgrade_generator_test.rb +77 -0
- metadata +229 -0
@@ -0,0 +1,177 @@
|
|
1
|
+
module Audited
|
2
|
+
module RspecMatchers
|
3
|
+
# Ensure that the model is audited.
|
4
|
+
#
|
5
|
+
# Options:
|
6
|
+
# * <tt>associated_with</tt> - tests that the audit makes use of the associated_with option
|
7
|
+
# * <tt>only</tt> - tests that the audit makes use of the only option *Overrides <tt>except</tt> option*
|
8
|
+
# * <tt>except</tt> - tests that the audit makes use of the except option
|
9
|
+
# * <tt>requires_comment</tt> - if specified, then the audit must require comments through the <tt>audit_comment</tt> attribute
|
10
|
+
# * <tt>on</tt> - tests that the audit makes use of the on option with specified parameters
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# it { should be_audited }
|
14
|
+
# it { should be_audited.associated_with(:user) }
|
15
|
+
# it { should be_audited.only(:field_name) }
|
16
|
+
# it { should be_audited.except(:password) }
|
17
|
+
# it { should be_audited.requires_comment }
|
18
|
+
# it { should be_audited.on(:create).associated_with(:user).except(:password) }
|
19
|
+
#
|
20
|
+
def be_audited
|
21
|
+
AuditMatcher.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Ensure that the model has associated audits
|
25
|
+
#
|
26
|
+
# Example:
|
27
|
+
# it { should have_associated_audits }
|
28
|
+
#
|
29
|
+
def have_associated_audits
|
30
|
+
AssociatedAuditMatcher.new
|
31
|
+
end
|
32
|
+
|
33
|
+
class AuditMatcher # :nodoc:
|
34
|
+
def initialize
|
35
|
+
@options = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def associated_with(model)
|
39
|
+
@options[:associated_with] = model
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def only(*fields)
|
44
|
+
@options[:only] = fields.flatten
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def except(*fields)
|
49
|
+
@options[:except] = fields.flatten
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def requires_comment
|
54
|
+
@options[:comment_required] = true
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def on(*actions)
|
59
|
+
@options[:on] = actions.flatten
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def matches?(subject)
|
64
|
+
@subject = subject
|
65
|
+
auditing_enabled? &&
|
66
|
+
associated_with_model? &&
|
67
|
+
records_changes_to_specified_fields? &&
|
68
|
+
comment_required_valid?
|
69
|
+
end
|
70
|
+
|
71
|
+
def failure_message
|
72
|
+
"Expected #{@expectation}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def negative_failure_message
|
76
|
+
"Did not expect #{@expectation}"
|
77
|
+
end
|
78
|
+
|
79
|
+
alias_method :failure_message_when_negated, :negative_failure_message
|
80
|
+
|
81
|
+
def description
|
82
|
+
description = "audited"
|
83
|
+
description += " associated with #{@options[:associated_with]}" if @options.key?(:associated_with)
|
84
|
+
description += " only => #{@options[:only].join ', '}" if @options.key?(:only)
|
85
|
+
description += " except => #{@options[:except].join(', ')}" if @options.key?(:except)
|
86
|
+
description += " requires audit_comment" if @options.key?(:comment_required)
|
87
|
+
|
88
|
+
description
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def expects(message)
|
94
|
+
@expectation = message
|
95
|
+
end
|
96
|
+
|
97
|
+
def auditing_enabled?
|
98
|
+
expects "#{model_class} to be audited"
|
99
|
+
model_class.respond_to?(:auditing_enabled) && model_class.auditing_enabled
|
100
|
+
end
|
101
|
+
|
102
|
+
def model_class
|
103
|
+
@subject.class
|
104
|
+
end
|
105
|
+
|
106
|
+
def associated_with_model?
|
107
|
+
expects "#{model_class} to record audits to associated model #{@options[:associated_with]}"
|
108
|
+
model_class.audit_associated_with == @options[:associated_with]
|
109
|
+
end
|
110
|
+
|
111
|
+
def records_changes_to_specified_fields?
|
112
|
+
if @options[:only] || @options[:except]
|
113
|
+
if @options[:only]
|
114
|
+
except = model_class.column_names - @options[:only].map(&:to_s)
|
115
|
+
else
|
116
|
+
except = model_class.default_ignored_attributes + Audited.ignored_attributes
|
117
|
+
except |= @options[:except].collect(&:to_s) if @options[:except]
|
118
|
+
end
|
119
|
+
|
120
|
+
expects "non audited columns (#{model_class.non_audited_columns.inspect}) to match (#{expect})"
|
121
|
+
model_class.non_audited_columns =~ except
|
122
|
+
else
|
123
|
+
true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def comment_required_valid?
|
128
|
+
if @options[:comment_required]
|
129
|
+
@subject.audit_comment = nil
|
130
|
+
|
131
|
+
expects "to be invalid when audit_comment is not specified"
|
132
|
+
@subject.valid? == false && @subject.errors.key?(:audit_comment)
|
133
|
+
else
|
134
|
+
true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class AssociatedAuditMatcher # :nodoc:
|
140
|
+
def matches?(subject)
|
141
|
+
@subject = subject
|
142
|
+
|
143
|
+
association_exists?
|
144
|
+
end
|
145
|
+
|
146
|
+
def failure_message
|
147
|
+
"Expected #{model_class} to have associated audits"
|
148
|
+
end
|
149
|
+
|
150
|
+
def negative_failure_message
|
151
|
+
"Expected #{model_class} to not have associated audits"
|
152
|
+
end
|
153
|
+
|
154
|
+
alias_method :failure_message_when_negated, :negative_failure_message
|
155
|
+
|
156
|
+
def description
|
157
|
+
"has associated audits"
|
158
|
+
end
|
159
|
+
|
160
|
+
protected
|
161
|
+
|
162
|
+
def model_class
|
163
|
+
@subject.class
|
164
|
+
end
|
165
|
+
|
166
|
+
def reflection
|
167
|
+
model_class.reflect_on_association(:associated_audits)
|
168
|
+
end
|
169
|
+
|
170
|
+
def association_exists?
|
171
|
+
!reflection.nil? &&
|
172
|
+
reflection.macro == :has_many &&
|
173
|
+
reflection.options[:class_name] == Audit.name
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "rails/observers/activerecord/active_record"
|
2
|
+
require "rails/observers/action_controller/caching"
|
3
|
+
|
4
|
+
module Audited
|
5
|
+
class Sweeper < ActionController::Caching::Sweeper
|
6
|
+
observe Audited::Audit
|
7
|
+
|
8
|
+
def around(controller)
|
9
|
+
self.controller = controller
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
self.controller = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def before_create(audit)
|
16
|
+
audit.user ||= current_user
|
17
|
+
audit.remote_address = controller.try(:request).try(:remote_ip)
|
18
|
+
audit.request_uuid = request_uuid if request_uuid
|
19
|
+
end
|
20
|
+
|
21
|
+
def current_user
|
22
|
+
controller.send(Audited.current_user_method) if controller.respond_to?(Audited.current_user_method, true)
|
23
|
+
end
|
24
|
+
|
25
|
+
def request_uuid
|
26
|
+
controller.try(:request).try(:uuid)
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_observer!(klass)
|
30
|
+
super
|
31
|
+
define_callback(klass)
|
32
|
+
end
|
33
|
+
|
34
|
+
def define_callback(klass)
|
35
|
+
observer = self
|
36
|
+
callback_meth = :_notify_audited_sweeper
|
37
|
+
klass.send(:define_method, callback_meth) do
|
38
|
+
observer.update(:before_create, self)
|
39
|
+
end
|
40
|
+
klass.send(:before_create, callback_meth)
|
41
|
+
end
|
42
|
+
|
43
|
+
def controller
|
44
|
+
::Audited.store[:current_controller]
|
45
|
+
end
|
46
|
+
|
47
|
+
def controller=(value)
|
48
|
+
::Audited.store[:current_controller] = value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
ActiveSupport.on_load(:action_controller) do
|
54
|
+
if defined?(ActionController::Base)
|
55
|
+
ActionController::Base.around_action Audited::Sweeper.instance
|
56
|
+
end
|
57
|
+
if defined?(ActionController::API)
|
58
|
+
ActionController::API.around_action Audited::Sweeper.instance
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
require 'active_record'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
require 'generators/audited/migration'
|
6
|
+
|
7
|
+
module Audited
|
8
|
+
module Generators
|
9
|
+
class InstallGenerator < Rails::Generators::Base
|
10
|
+
include Rails::Generators::Migration
|
11
|
+
extend Audited::Generators::Migration
|
12
|
+
|
13
|
+
source_root File.expand_path("../templates", __FILE__)
|
14
|
+
|
15
|
+
def copy_migration
|
16
|
+
migration_template 'install.rb', 'db/migrate/install_audited.rb'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Audited
|
2
|
+
module Generators
|
3
|
+
module Migration
|
4
|
+
# Implement the required interface for Rails::Generators::Migration.
|
5
|
+
def next_migration_number(dirname) #:nodoc:
|
6
|
+
next_migration_number = current_migration_number(dirname) + 1
|
7
|
+
if ::ActiveRecord::Base.timestamped_migrations
|
8
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
9
|
+
else
|
10
|
+
"%.3d" % next_migration_number
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
add_column :audits, :association_id, :integer
|
4
|
+
add_column :audits, :association_type, :string
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
remove_column :audits, :association_type
|
9
|
+
remove_column :audits, :association_id
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :audits, :force => true do |t|
|
4
|
+
t.column :auditable_id, :integer
|
5
|
+
t.column :auditable_type, :string
|
6
|
+
t.column :associated_id, :integer
|
7
|
+
t.column :associated_type, :string
|
8
|
+
t.column :user_id, :integer
|
9
|
+
t.column :user_type, :string
|
10
|
+
t.column :username, :string
|
11
|
+
t.column :action, :string
|
12
|
+
t.column :audited_changes, :text
|
13
|
+
t.column :version, :integer, :default => 0
|
14
|
+
t.column :comment, :string
|
15
|
+
t.column :remote_address, :string
|
16
|
+
t.column :request_uuid, :string
|
17
|
+
t.column :created_at, :datetime
|
18
|
+
end
|
19
|
+
|
20
|
+
add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
|
21
|
+
add_index :audits, [:associated_id, :associated_type], :name => 'associated_index'
|
22
|
+
add_index :audits, [:user_id, :user_type], :name => 'user_index'
|
23
|
+
add_index :audits, :request_uuid
|
24
|
+
add_index :audits, :created_at
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.down
|
28
|
+
drop_table :audits
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
if index_exists? :audits, [:association_id, :association_type], :name => 'association_index'
|
4
|
+
remove_index :audits, :name => 'association_index'
|
5
|
+
end
|
6
|
+
|
7
|
+
rename_column :audits, :association_id, :associated_id
|
8
|
+
rename_column :audits, :association_type, :associated_type
|
9
|
+
|
10
|
+
add_index :audits, [:associated_id, :associated_type], :name => 'associated_index'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
if index_exists? :audits, [:associated_id, :associated_type], :name => 'associated_index'
|
15
|
+
remove_index :audits, :name => 'associated_index'
|
16
|
+
end
|
17
|
+
|
18
|
+
rename_column :audits, :associated_type, :association_type
|
19
|
+
rename_column :audits, :associated_id, :association_id
|
20
|
+
|
21
|
+
add_index :audits, [:association_id, :association_type], :name => 'association_index'
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
rename_column :audits, :auditable_parent_id, :association_id
|
4
|
+
rename_column :audits, :auditable_parent_type, :association_type
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
rename_column :audits, :association_type, :auditable_parent_type
|
9
|
+
rename_column :audits, :association_id, :auditable_parent_id
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
require 'active_record'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
require 'generators/audited/migration'
|
6
|
+
|
7
|
+
module Audited
|
8
|
+
module Generators
|
9
|
+
class UpgradeGenerator < Rails::Generators::Base
|
10
|
+
include Rails::Generators::Migration
|
11
|
+
extend Audited::Generators::Migration
|
12
|
+
|
13
|
+
source_root File.expand_path("../templates", __FILE__)
|
14
|
+
|
15
|
+
def copy_templates
|
16
|
+
migrations_to_be_applied do |m|
|
17
|
+
migration_template "#{m}.rb", "db/migrate/#{m}.rb"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def migrations_to_be_applied
|
24
|
+
Audited::Audit.reset_column_information
|
25
|
+
columns = Audited::Audit.columns.map(&:name)
|
26
|
+
|
27
|
+
yield :add_comment_to_audits unless columns.include?('comment')
|
28
|
+
|
29
|
+
if columns.include?('changes')
|
30
|
+
yield :rename_changes_to_audited_changes
|
31
|
+
end
|
32
|
+
|
33
|
+
unless columns.include?('remote_address')
|
34
|
+
yield :add_remote_address_to_audits
|
35
|
+
end
|
36
|
+
|
37
|
+
unless columns.include?('request_uuid')
|
38
|
+
yield :add_request_uuid_to_audits
|
39
|
+
end
|
40
|
+
|
41
|
+
unless columns.include?('association_id')
|
42
|
+
if columns.include?('auditable_parent_id')
|
43
|
+
yield :rename_parent_to_association
|
44
|
+
else
|
45
|
+
unless columns.include?('associated_id')
|
46
|
+
yield :add_association_to_audits
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if columns.include?('association_id')
|
52
|
+
yield :rename_association_to_associated
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|