acts_as_audited_collection 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
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