track_history 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,109 @@
1
+ module TrackHistory
2
+
3
+ autoload :VERSION, File.join(File.dirname(__FILE__), 'track_history', 'version')
4
+ require 'active_record'
5
+
6
+ def self.install
7
+ ActiveRecord::Base.send(:include, self)
8
+ end
9
+
10
+ def self.included(base)
11
+ base.extend ActsAsMethods
12
+ base.send(:include, InstanceMethods)
13
+ end
14
+
15
+ module ActsAsMethods
16
+
17
+ # Make a model historical
18
+ # Takes a hash of options, which can only be :model_name to force a different model name
19
+ # Default model name is ModelHistory
20
+ def track_history(options = {})
21
+ @historical_fields = []
22
+ @historical_tracks = {}
23
+ define_historical_model(self, options[:model_name])
24
+ yield if block_given?
25
+ end
26
+
27
+ def annotate(field, &block) # haha
28
+ @historical_tracks ||= []
29
+ @historical_tracks[field] = block
30
+ unless @klass_reference.columns_hash.has_key?(field.is_a?(Symbol) ? field.to_s : field)
31
+ raise ActiveRecord::StatementInvalid.new("No such attribute '#{field}' on #{@klass_reference.name}")
32
+ end
33
+ end
34
+
35
+ def historical_fields
36
+ @historical_fields
37
+ end
38
+
39
+ def historical_tracks
40
+ @historical_tracks
41
+ end
42
+
43
+ private
44
+
45
+ def define_historical_model(base, class_name)
46
+
47
+ class_name ||= "#{base.name}History"
48
+ klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
49
+ @klass_reference = klass
50
+
51
+ # infer fields
52
+ klass.columns_hash.each_key do |k|
53
+ matches = k.match(/(.+?)_before$/)
54
+ if matches && matches.size == 2 && field_name = matches[1]
55
+ @historical_fields << field_name if klass.columns_hash.has_key?("#{field_name}_after")
56
+ end
57
+ end
58
+
59
+ # create the history class
60
+ rel = base.name.singularize.underscore.downcase.to_sym
61
+ klass.belongs_to rel
62
+ klass.send(:alias_method, :historical_relation, rel)
63
+ klass.send(:include, HistoricalHelpers)
64
+
65
+ # tell the other class about us
66
+ # purposely don't define these until after getting historical_fields
67
+ has_many :histories, :class_name => class_name, :order => 'created_at desc'
68
+ before_save :record_historical_changes
69
+
70
+ end
71
+
72
+ end
73
+
74
+ module HistoricalHelpers
75
+
76
+ # Get a list of the modifications in a given history
77
+ def modifications
78
+ historical_relation.class.historical_fields.reject do |field|
79
+ send(:"#{field}_before") == send(:"#{field}_after")
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ module InstanceMethods
86
+
87
+ private
88
+
89
+ def record_historical_changes
90
+ return if self.class.historical_fields.empty?
91
+ # go through each and build the hashes
92
+ attributes = {}
93
+ self.class.historical_fields.each do |field|
94
+ next unless send(:"#{field}_changed?")
95
+ attributes.merge! :"#{field}_before" => send(:"#{field}_was"), :"#{field}_after" => send(field.to_sym)
96
+ end
97
+ return if attributes.empty? # nothing changed - skip out
98
+ # then go through each track
99
+ self.class.historical_tracks.each do |field, block|
100
+ attributes[field] = block.nil? ? send(field) : (block.arity == 1 ? block.call(self) : instance_eval(&block)) # give access to the user object
101
+ end
102
+ self.histories.create(attributes)
103
+ end
104
+
105
+ end
106
+
107
+ end
108
+
109
+ TrackHistory::install
@@ -0,0 +1,5 @@
1
+ module TrackHistory
2
+
3
+ VERSION = '0.0.1'
4
+
5
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: track_history
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - John Crepezzi
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-05 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ description: Smart, performant model auditing
36
+ email: john.crepezzi@patch.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - lib/track_history/version.rb
45
+ - lib/track_history.rb
46
+ has_rdoc: true
47
+ homepage: http://github.com/seejohnrun/historical
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options: []
52
+
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ requirements: []
74
+
75
+ rubyforge_project: historical
76
+ rubygems_version: 1.3.7
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Smart model auditing
80
+ test_files: []
81
+