track_history 0.0.1

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.
@@ -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
+