acts_as_audited_collection 0.3 → 0.4

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.
data/Rakefile CHANGED
@@ -1,45 +1,42 @@
1
1
  require 'rake'
2
- require 'spec/rake/spectask'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
3
4
 
4
- require 'rubygems'
5
- require 'rake/gempackagetask'
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
6
7
 
7
- PKG_FILES = FileList[
8
- '[a-zA-Z]*',
9
- 'generators/**/*',
10
- 'lib/**/*',
11
- 'rails/**/*',
12
- 'spec/**/*'
13
- ]
14
-
15
- spec = Gem::Specification.new do |s|
16
- s.name = 'acts_as_audited_collection'
17
- s.version = '0.3'
18
- s.author = 'Shaun Mangelsdorf'
19
- s.email = 's.mangelsdorf@gmail.com'
20
- s.homepage = 'http://smangelsdorf.github.com'
21
- s.platform = Gem::Platform::RUBY
22
- s.summary = 'Extends ActiveRecord to allow auditing of associations'
23
- s.files = PKG_FILES.to_a
24
- s.require_path = 'lib'
25
- s.has_rdoc = false
26
- s.extra_rdoc_files = ['README.md']
27
- s.rubyforge_project = 'auditcollection'
28
- s.description = <<EOF
29
- Adds auditing capabilities to ActiveRecord associations, in a similar fashion to acts_as_audited.
30
- EOF
8
+ desc 'Test the acts_as_audited_collection plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
31
14
  end
32
15
 
33
- desc 'Default: run specs.'
34
- task :default => :spec
35
-
36
- desc 'Run the specs'
37
- Spec::Rake::SpecTask.new(:spec) do |t|
38
- t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
39
- t.spec_files = FileList['spec/**/*_spec.rb']
16
+ desc 'Generate documentation for the acts_as_audited_collection plugin.'
17
+ RDoc::Task.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'ActsAsAuditedCollection'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
40
23
  end
41
24
 
42
- desc 'Turn this plugin into a gem.'
43
- Rake::GemPackageTask.new(spec) do |pkg|
44
- pkg.gem_spec = spec
25
+ begin
26
+ require 'jeweler'
27
+ Jeweler::Tasks.new do |gem|
28
+ gem.name = 'acts_as_audited_collection'
29
+ gem.summary = 'Adds auditing capabilities to ActiveRecord associations, in a similar fashion to acts_as_audited.'
30
+ gem.files = Dir[
31
+ '[a-zA-Z]*',
32
+ 'generators/**/*',
33
+ 'lib/**/*',
34
+ 'rails/**/*',
35
+ 'spec/**/*'
36
+ ]
37
+ gem.authors = ['Shaun Mangelsdorf']
38
+ gem.version = '0.4'
39
+ end
40
+ rescue LoadError
41
+ puts "Jeweler could not be sourced"
45
42
  end
data/init.rb CHANGED
@@ -1,3 +1 @@
1
- # Released under the MIT license. See the LICENSE file for details
2
-
3
- require File.join(File.dirname(__FILE__), 'rails', 'init')
1
+ # Include hook code here
@@ -0,0 +1,222 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module AuditedCollection
4
+ module ClassMethods
5
+ def acts_as_audited_collection(options = {})
6
+ unless self.included_modules.include?(InstanceMethods)
7
+ # First time use in this class, we have some extra work to do.
8
+ send :include, InstanceMethods
9
+
10
+ class_inheritable_reader :audited_collections
11
+ write_inheritable_attribute :audited_collections, {}
12
+ attr_accessor :collection_audit_object_is_soft_deleted
13
+
14
+ after_create :collection_audit_create
15
+ before_update :collection_audit_update
16
+ after_destroy :collection_audit_destroy
17
+
18
+ has_many :child_collection_audits, :as => :child_record,
19
+ :class_name => 'CollectionAudit'
20
+ end
21
+
22
+ options = {
23
+ :name => self.name.tableize.to_sym,
24
+ :cascade => false,
25
+ :track_modifications => false,
26
+ :only => nil,
27
+ :except => nil,
28
+ :soft_delete => nil
29
+ }.merge(options)
30
+
31
+ options[:only] &&= [options[:only]].flatten.collect(&:to_s)
32
+ options[:except] &&= [options[:except]].flatten.collect(&:to_s)
33
+
34
+ unless options.has_key? :parent
35
+ raise ActiveRecord::ConfigurationError.new "Must specify parent for an acts_as_audited_collection (:parent => :object)"
36
+ end
37
+
38
+ parent_association = reflect_on_association(options[:parent])
39
+ unless parent_association && parent_association.belongs_to?
40
+ raise ActiveRecord::ConfigurationError.new "Parent association '#{options[:parent]}' must be a belongs_to relationship"
41
+ end
42
+
43
+ # Try explicit first, then default
44
+ options[:foreign_key] ||= parent_association.options[:foreign_key]
45
+ options[:foreign_key] ||= parent_association.primary_key_name
46
+
47
+ # TODO Remove this when polymorphic is supported.
48
+ if parent_association.options[:polymorphic]
49
+ raise ActiveRecord::ConfigurationError.new "Sorry, acts_as_audited_collection polymorphic associations haven't been added yet."
50
+ end
51
+
52
+ options[:parent_type] ||= parent_association.klass.name
53
+
54
+ define_acts_as_audited_collection options do |config|
55
+ config.merge! options
56
+ end
57
+ end
58
+
59
+ def acts_as_audited_collection_parent(options = {})
60
+ unless options.has_key? :for
61
+ raise ActiveRecord::ConfigurationError.new "Must specify relationship for an acts_as_audited_collection_parent (:for => :objects)"
62
+ end
63
+
64
+ child_association = reflect_on_association(options[:for])
65
+ if child_association.nil? || child_association.belongs_to?
66
+ raise ActiveRecord::ConfigurationError.new "Association '#{options[:for]}' must be a valid parent (i.e. not belongs_to) relationship"
67
+ end
68
+
69
+ has_many :"#{options[:for]}_audits", :as => :parent_record,
70
+ :class_name => 'CollectionAudit',
71
+ :conditions => ['association = ?', options[:for].to_s]
72
+ end
73
+
74
+ def define_acts_as_audited_collection(options)
75
+ yield(read_inheritable_attribute(:audited_collections)[options[:name]] ||= {})
76
+ end
77
+
78
+ def without_collection_audit
79
+ result = nil
80
+ Thread.current[:collection_audit_enabled] = returning(Thread.current[:collection_audit_enabled]) do
81
+ Thread.current[:collection_audit_enabled] = false
82
+ result = yield if block_given?
83
+ end
84
+
85
+ result
86
+ end
87
+ end
88
+
89
+ module InstanceMethods
90
+ protected
91
+ def collection_audit_create
92
+ collection_audit_write :action => 'add', :attributes => audited_collection_attributes
93
+ end
94
+
95
+ def collection_audit_update
96
+ audited_collections.each do |name, opts|
97
+ attributes = {opts[:foreign_key] => self.send(opts[:foreign_key])}
98
+ if collection_audit_is_soft_deleted?(opts)
99
+ collection_audit_write(
100
+ :action => 'remove',
101
+ :attributes => attributes
102
+ ) unless collection_audit_was_soft_deleted?(opts)
103
+ elsif collection_audit_was_soft_deleted?(opts)
104
+ collection_audit_write :action => 'add', :attributes => attributes
105
+ end
106
+ end
107
+
108
+ unless (old_values = audited_collection_attribute_changes).empty?
109
+ new_values = old_values.inject({}) { |map, (k, v)| map[k] = self[k]; map }
110
+
111
+ collection_audit_write :action => 'remove', :attributes => old_values
112
+ collection_audit_write :action => 'add', :attributes => new_values
113
+ end
114
+
115
+ collection_audit_write_as_modified unless audited_collection_excluded_attribute_changes.empty?
116
+ end
117
+
118
+ def collection_audit_destroy
119
+ collection_audit_write :action => 'remove', :attributes => audited_collection_attributes
120
+ end
121
+
122
+ def collection_audit_write_as_modified(child_audit=nil)
123
+ each_modification_tracking_audited_collection do |col|
124
+ collection_audit_write(:action => 'modify',
125
+ :attributes => attributes.slice(col[:foreign_key]),
126
+ :child_audit => child_audit
127
+ ) if audited_collection_should_care?(col)
128
+ end
129
+ end
130
+
131
+ def collection_audit_cascade(child, child_audit)
132
+ collection_audit_write_as_modified(child_audit) if respond_to? :audited_collections
133
+ end
134
+
135
+ private
136
+ def collection_audit_is_soft_deleted?(opts)
137
+ if opts[:soft_delete]
138
+ opts[:soft_delete].all?{|k,v| self.send(k) == v}
139
+ else
140
+ false
141
+ end
142
+ end
143
+
144
+ def collection_audit_was_soft_deleted?(opts)
145
+ if opts[:soft_delete]
146
+ opts[:soft_delete].all?{|k,v| self.send(:"#{k}_was") == v}
147
+ else
148
+ false
149
+ end
150
+ end
151
+
152
+ def collection_audit_write(opts)
153
+ # Only care about explicit false here, not the falseness of nil
154
+ return if Thread.current[:collection_audit_enabled] == false
155
+
156
+ mappings = audited_relation_attribute_mappings
157
+ opts[:attributes].reject{|k,v| v.nil?}.each do |fk, fk_val|
158
+ object_being_deleted = collection_audit_is_soft_deleted?(mappings[fk]) &&
159
+ !collection_audit_was_soft_deleted?(mappings[fk])
160
+ object_being_restored = collection_audit_was_soft_deleted?(mappings[fk]) &&
161
+ !collection_audit_is_soft_deleted?(mappings[fk])
162
+ object_is_deleted = collection_audit_is_soft_deleted?(mappings[fk]) &&
163
+ collection_audit_was_soft_deleted?(mappings[fk])
164
+
165
+ unless (object_being_deleted and opts[:action] != 'remove') or
166
+ (object_being_restored and opts[:action] != 'add') or
167
+ object_is_deleted
168
+
169
+ audit = child_collection_audits.create :parent_record_id => fk_val,
170
+ :parent_record_type => mappings[fk][:parent_type],
171
+ :action => opts[:action],
172
+ :association => mappings[fk][:name].to_s,
173
+ :child_audit => opts[:child_audit]
174
+
175
+ if mappings[fk][:cascade]
176
+ parent = mappings[fk][:parent_type].constantize.send :find, fk_val
177
+ parent.collection_audit_cascade(self, audit)
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ def each_modification_tracking_audited_collection
184
+ audited_collections.each do |name, options|
185
+ if options[:track_modifications]
186
+ yield options
187
+ end
188
+ end
189
+ end
190
+
191
+ def audited_collection_attributes
192
+ attributes.slice *audited_relation_attribute_mappings.keys
193
+ end
194
+
195
+ def audited_collection_excluded_attribute_changes
196
+ changed_attributes.except *audited_relation_attribute_mappings.keys
197
+ end
198
+
199
+ def audited_collection_attribute_changes
200
+ changed_attributes.slice *audited_relation_attribute_mappings.keys
201
+ end
202
+
203
+ def audited_collection_should_care?(collection)
204
+ if collection[:only]
205
+ !audited_collection_excluded_attribute_changes.slice(*collection[:only]).empty?
206
+ elsif collection[:except]
207
+ !audited_collection_excluded_attribute_changes.except(*collection[:except]).empty?
208
+ else
209
+ true
210
+ end
211
+ end
212
+
213
+ def audited_relation_attribute_mappings
214
+ audited_collections.inject({}) do |map, (name, options)|
215
+ map[options[:foreign_key]] = options
216
+ map
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -1,230 +1,4 @@
1
- # Released under the MIT license. See the LICENSE file for details
1
+ require 'acts_as_audited_collection/collection_audit'
2
+ require 'active_record/acts/audited_collection'
2
3
 
3
- require 'acts_as_audited_collection/collection_audit.rb'
4
-
5
- module ActiveRecord
6
- module Acts
7
- module AuditedCollection
8
- def self.included(base)
9
- base.extend ClassMethods
10
- end
11
-
12
- module ClassMethods
13
- def acts_as_audited_collection(options = {})
14
- unless self.included_modules.include?(InstanceMethods)
15
- # First time use in this class, we have some extra work to do.
16
- send :include, InstanceMethods
17
-
18
- class_inheritable_reader :audited_collections
19
- write_inheritable_attribute :audited_collections, {}
20
- attr_accessor :collection_audit_object_is_soft_deleted
21
-
22
- after_create :collection_audit_create
23
- before_update :collection_audit_update
24
- after_destroy :collection_audit_destroy
25
-
26
- has_many :child_collection_audits, :as => :child_record,
27
- :class_name => 'CollectionAudit'
28
- end
29
-
30
- options = {
31
- :name => self.name.tableize.to_sym,
32
- :cascade => false,
33
- :track_modifications => false,
34
- :only => nil,
35
- :except => nil,
36
- :soft_delete => nil
37
- }.merge(options)
38
-
39
- options[:only] &&= [options[:only]].flatten.collect(&:to_s)
40
- options[:except] &&= [options[:except]].flatten.collect(&:to_s)
41
-
42
- unless options.has_key? :parent
43
- raise ActiveRecord::ConfigurationError.new "Must specify parent for an acts_as_audited_collection (:parent => :object)"
44
- end
45
-
46
- parent_association = reflect_on_association(options[:parent])
47
- unless parent_association && parent_association.belongs_to?
48
- raise ActiveRecord::ConfigurationError.new "Parent association '#{options[:parent]}' must be a belongs_to relationship"
49
- end
50
-
51
- # Try explicit first, then default
52
- options[:foreign_key] ||= parent_association.options[:foreign_key]
53
- options[:foreign_key] ||= parent_association.primary_key_name
54
-
55
- # TODO Remove this when polymorphic is supported.
56
- if parent_association.options[:polymorphic]
57
- raise ActiveRecord::ConfigurationError.new "Sorry, acts_as_audited_collection polymorphic associations haven't been added yet."
58
- end
59
-
60
- options[:parent_type] ||= parent_association.klass.name
61
-
62
- define_acts_as_audited_collection options do |config|
63
- config.merge! options
64
- end
65
- end
66
-
67
- def acts_as_audited_collection_parent(options = {})
68
- unless options.has_key? :for
69
- raise ActiveRecord::ConfigurationError.new "Must specify relationship for an acts_as_audited_collection_parent (:for => :objects)"
70
- end
71
-
72
- child_association = reflect_on_association(options[:for])
73
- if child_association.nil? || child_association.belongs_to?
74
- raise ActiveRecord::ConfigurationError.new "Association '#{options[:for]}' must be a valid parent (i.e. not belongs_to) relationship"
75
- end
76
-
77
- has_many :"#{options[:for]}_audits", :as => :parent_record,
78
- :class_name => 'CollectionAudit',
79
- :conditions => ['association = ?', options[:for].to_s]
80
- end
81
-
82
- def define_acts_as_audited_collection(options)
83
- yield(read_inheritable_attribute(:audited_collections)[options[:name]] ||= {})
84
- end
85
-
86
- def without_collection_audit
87
- result = nil
88
- Thread.current[:collection_audit_enabled] = returning(Thread.current[:collection_audit_enabled]) do
89
- Thread.current[:collection_audit_enabled] = false
90
- result = yield if block_given?
91
- end
92
-
93
- result
94
- end
95
- end
96
-
97
- module InstanceMethods
98
- protected
99
- def collection_audit_create
100
- collection_audit_write :action => 'add', :attributes => audited_collection_attributes
101
- end
102
-
103
- def collection_audit_update
104
- audited_collections.each do |name, opts|
105
- attributes = {opts[:foreign_key] => self.send(opts[:foreign_key])}
106
- if collection_audit_is_soft_deleted?(opts)
107
- collection_audit_write(
108
- :action => 'remove',
109
- :attributes => attributes
110
- ) unless collection_audit_was_soft_deleted?(opts)
111
- elsif collection_audit_was_soft_deleted?(opts)
112
- collection_audit_write :action => 'add', :attributes => attributes
113
- end
114
- end
115
-
116
- unless (old_values = audited_collection_attribute_changes).empty?
117
- new_values = old_values.inject({}) { |map, (k, v)| map[k] = self[k]; map }
118
-
119
- collection_audit_write :action => 'remove', :attributes => old_values
120
- collection_audit_write :action => 'add', :attributes => new_values
121
- end
122
-
123
- collection_audit_write_as_modified unless audited_collection_excluded_attribute_changes.empty?
124
- end
125
-
126
- def collection_audit_destroy
127
- collection_audit_write :action => 'remove', :attributes => audited_collection_attributes
128
- end
129
-
130
- def collection_audit_write_as_modified(child_audit=nil)
131
- each_modification_tracking_audited_collection do |col|
132
- collection_audit_write(:action => 'modify',
133
- :attributes => attributes.slice(col[:foreign_key]),
134
- :child_audit => child_audit
135
- ) if audited_collection_should_care?(col)
136
- end
137
- end
138
-
139
- def collection_audit_cascade(child, child_audit)
140
- collection_audit_write_as_modified(child_audit) if respond_to? :audited_collections
141
- end
142
-
143
- private
144
- def collection_audit_is_soft_deleted?(opts)
145
- if opts[:soft_delete]
146
- opts[:soft_delete].all?{|k,v| self.send(k) == v}
147
- else
148
- false
149
- end
150
- end
151
-
152
- def collection_audit_was_soft_deleted?(opts)
153
- if opts[:soft_delete]
154
- opts[:soft_delete].all?{|k,v| self.send(:"#{k}_was") == v}
155
- else
156
- false
157
- end
158
- end
159
-
160
- def collection_audit_write(opts)
161
- # Only care about explicit false here, not the falseness of nil
162
- return if Thread.current[:collection_audit_enabled] == false
163
-
164
- mappings = audited_relation_attribute_mappings
165
- opts[:attributes].reject{|k,v| v.nil?}.each do |fk, fk_val|
166
- object_being_deleted = collection_audit_is_soft_deleted?(mappings[fk]) &&
167
- !collection_audit_was_soft_deleted?(mappings[fk])
168
- object_being_restored = collection_audit_was_soft_deleted?(mappings[fk]) &&
169
- !collection_audit_is_soft_deleted?(mappings[fk])
170
- object_is_deleted = collection_audit_is_soft_deleted?(mappings[fk]) &&
171
- collection_audit_was_soft_deleted?(mappings[fk])
172
-
173
- unless (object_being_deleted and opts[:action] != 'remove') or
174
- (object_being_restored and opts[:action] != 'add') or
175
- object_is_deleted
176
-
177
- audit = child_collection_audits.create :parent_record_id => fk_val,
178
- :parent_record_type => mappings[fk][:parent_type],
179
- :action => opts[:action],
180
- :association => mappings[fk][:name].to_s,
181
- :child_audit => opts[:child_audit]
182
-
183
- if mappings[fk][:cascade]
184
- parent = mappings[fk][:parent_type].constantize.send :find, fk_val
185
- parent.collection_audit_cascade(self, audit)
186
- end
187
- end
188
- end
189
- end
190
-
191
- def each_modification_tracking_audited_collection
192
- audited_collections.each do |name, options|
193
- if options[:track_modifications]
194
- yield options
195
- end
196
- end
197
- end
198
-
199
- def audited_collection_attributes
200
- attributes.slice *audited_relation_attribute_mappings.keys
201
- end
202
-
203
- def audited_collection_excluded_attribute_changes
204
- changed_attributes.except *audited_relation_attribute_mappings.keys
205
- end
206
-
207
- def audited_collection_attribute_changes
208
- changed_attributes.slice *audited_relation_attribute_mappings.keys
209
- end
210
-
211
- def audited_collection_should_care?(collection)
212
- if collection[:only]
213
- !audited_collection_excluded_attribute_changes.slice(*collection[:only]).empty?
214
- elsif collection[:except]
215
- !audited_collection_excluded_attribute_changes.except(*collection[:except]).empty?
216
- else
217
- true
218
- end
219
- end
220
-
221
- def audited_relation_attribute_mappings
222
- audited_collections.inject({}) do |map, (name, options)|
223
- map[options[:foreign_key]] = options
224
- map
225
- end
226
- end
227
- end
228
- end
229
- end
230
- end
4
+ require 'acts_as_audited_collection/railtie' if defined? Rails