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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +32 -0
  4. data/.yardopts +3 -0
  5. data/Appraisals +22 -0
  6. data/CHANGELOG +153 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +19 -0
  9. data/README.md +299 -0
  10. data/Rakefile +18 -0
  11. data/gemfiles/rails40.gemfile +9 -0
  12. data/gemfiles/rails41.gemfile +8 -0
  13. data/gemfiles/rails42.gemfile +8 -0
  14. data/gemfiles/rails50.gemfile +8 -0
  15. data/lib/audited-rspec.rb +4 -0
  16. data/lib/audited.rb +29 -0
  17. data/lib/audited/audit.rb +149 -0
  18. data/lib/audited/auditor.rb +309 -0
  19. data/lib/audited/rspec_matchers.rb +177 -0
  20. data/lib/audited/sweeper.rb +60 -0
  21. data/lib/audited/version.rb +3 -0
  22. data/lib/generators/audited/install_generator.rb +20 -0
  23. data/lib/generators/audited/migration.rb +15 -0
  24. data/lib/generators/audited/templates/add_association_to_audits.rb +11 -0
  25. data/lib/generators/audited/templates/add_comment_to_audits.rb +9 -0
  26. data/lib/generators/audited/templates/add_remote_address_to_audits.rb +10 -0
  27. data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +10 -0
  28. data/lib/generators/audited/templates/install.rb +30 -0
  29. data/lib/generators/audited/templates/rename_association_to_associated.rb +23 -0
  30. data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +9 -0
  31. data/lib/generators/audited/templates/rename_parent_to_association.rb +11 -0
  32. data/lib/generators/audited/upgrade_generator.rb +57 -0
  33. data/spec/audited/audit_spec.rb +199 -0
  34. data/spec/audited/auditor_spec.rb +607 -0
  35. data/spec/audited/sweeper_spec.rb +106 -0
  36. data/spec/audited_spec_helpers.rb +20 -0
  37. data/spec/rails_app/config/application.rb +8 -0
  38. data/spec/rails_app/config/database.yml +24 -0
  39. data/spec/rails_app/config/environment.rb +5 -0
  40. data/spec/rails_app/config/environments/development.rb +21 -0
  41. data/spec/rails_app/config/environments/production.rb +35 -0
  42. data/spec/rails_app/config/environments/test.rb +47 -0
  43. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  44. data/spec/rails_app/config/initializers/inflections.rb +2 -0
  45. data/spec/rails_app/config/initializers/secret_token.rb +3 -0
  46. data/spec/rails_app/config/routes.rb +3 -0
  47. data/spec/spec_helper.rb +21 -0
  48. data/spec/support/active_record/models.rb +99 -0
  49. data/spec/support/active_record/schema.rb +81 -0
  50. data/test/db/version_1.rb +17 -0
  51. data/test/db/version_2.rb +18 -0
  52. data/test/db/version_3.rb +19 -0
  53. data/test/db/version_4.rb +20 -0
  54. data/test/db/version_5.rb +18 -0
  55. data/test/db/version_6.rb +17 -0
  56. data/test/install_generator_test.rb +17 -0
  57. data/test/test_helper.rb +19 -0
  58. data/test/upgrade_generator_test.rb +77 -0
  59. 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,3 @@
1
+ module Audited
2
+ VERSION = "4.3.0"
3
+ 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,9 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :audits, :comment, :string
4
+ end
5
+
6
+ def self.down
7
+ remove_column :audits, :comment
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :audits, :remote_address, :string
4
+ end
5
+
6
+ def self.down
7
+ remove_column :audits, :remote_address
8
+ end
9
+ end
10
+
@@ -0,0 +1,10 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :audits, :request_uuid, :string
4
+ add_index :audits, :request_uuid
5
+ end
6
+
7
+ def self.down
8
+ remove_column :audits, :request_uuid
9
+ end
10
+ 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,9 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ rename_column :audits, :changes, :audited_changes
4
+ end
5
+
6
+ def self.down
7
+ rename_column :audits, :audited_changes, :changes
8
+ end
9
+ 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