track_history 0.0.4 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,71 +19,114 @@ module TrackHistory
19
19
  # Takes a hash of options, which can only be :model_name to force a different model name
20
20
  # Default model name is ModelHistory
21
21
  def track_history(options = {}, &block)
22
- options.assert_valid_keys(:model_name, :table_name)
23
- @historical_fields = []
24
- @historical_tracks = {}
25
- define_historical_model(self, options[:model_name], options[:table_name])
26
- module_eval(&block) if block_given?
22
+ options.assert_valid_keys(:model_name, :table_name, :reference)
23
+ define_historical_model(self, options[:model_name], options[:table_name], options.has_key?(:reference) ? !!options[:reference] : true, &block)
27
24
  end
28
25
 
29
- def annotate(field, &block) # haha
30
- @historical_tracks ||= {}
31
- @historical_tracks[field] = block
32
- unless @klass_reference.columns_hash.has_key?(field.is_a?(Symbol) ? field.to_s : field)
33
- raise ActiveRecord::StatementInvalid.new("No such attribute '#{field}' on #{@klass_reference.name}")
34
- end
26
+ def historical_class
27
+ @klass_reference
35
28
  end
36
29
 
37
30
  def historical_fields
38
- @historical_fields
39
- end
40
-
41
- def historical_tracks
42
- @historical_tracks
31
+ @klass_reference.historical_fields.keys
43
32
  end
44
33
 
45
34
  private
46
35
 
47
- def define_historical_model(base, model_name, table_name)
36
+ def define_historical_model(base, model_name, table_name, track_reference, &block)
37
+
38
+ # figure out the model name
48
39
  model_name ||= "#{base.name}History"
49
- klass = Object.const_set(model_name, Class.new(ActiveRecord::Base))
50
- @klass_reference = klass
51
-
40
+ @klass_reference = Object.const_set(model_name, Class.new(ActiveRecord::Base))
41
+
42
+ # set up a way to record tracks
43
+ @klass_reference.instance_variable_set(:@track_historical_reference, track_reference)
44
+ @klass_reference.send(:extend, HistoryMethods)
45
+
52
46
  # infer fields
53
- klass.send(:table_name=, table_name) unless table_name.nil?
54
- klass.columns_hash.each_key do |k|
47
+ @klass_reference.send(:table_name=, table_name) unless table_name.nil?
48
+ @klass_reference.columns_hash.each_key do |k|
55
49
  matches = k.match(/(.+?)_before$/)
56
50
  if matches && matches.size == 2 && field_name = matches[1]
57
- @historical_fields << field_name if klass.columns_hash.has_key?("#{field_name}_after")
51
+ next if @klass_reference.historical_fields.has_key?(field_name) # override inferrences
52
+ @klass_reference.historical_fields[field_name] = { :before => "#{field_name}_before".to_sym, :after => "#{field_name}_after".to_sym }
58
53
  end
59
54
  end
60
55
 
56
+ # allow other things to be specified
57
+ @klass_reference.module_eval(&block) if block_given?
58
+
61
59
  # create the history class
62
60
  rel = base.name.singularize.underscore.downcase.to_sym
63
- klass.belongs_to rel
64
- klass.send(:alias_method, :historical_relation, rel)
65
- klass.send(:include, HistoricalHelpers)
61
+ @klass_reference.send(:include, HistoricalRelationHelpers)
62
+
63
+ # create a backward reference
64
+ if track_reference
65
+ @klass_reference.belongs_to rel
66
+ @klass_reference.send(:alias_method, :historical_relation, rel)
67
+ has_many :histories, :class_name => model_name, :order => 'created_at desc' if track_reference
68
+ end
66
69
 
67
70
  # tell the other class about us
68
71
  # purposely don't define these until after getting historical_fields
69
- has_many :histories, :class_name => model_name, :order => 'created_at desc'
70
72
  before_update :record_historical_changes
71
73
 
72
74
  end
73
75
 
74
76
  end
75
77
 
76
- module HistoricalHelpers
78
+ module HistoryMethods
79
+
80
+ attr_reader :historical_fields
81
+
82
+ def track_historical_reference?
83
+ @track_historical_reference
84
+ end
85
+
86
+ def historical_fields
87
+ @historical_fields ||= {}
88
+ end
89
+
90
+ def historical_tracks
91
+ @historical_tracks ||= {}
92
+ end
93
+
94
+ def field(field, options = {}) # TODO rename
95
+ field_s = field.is_a?(String) ? field : field.to_s
96
+ historical_fields[field_s] = {
97
+ :before => options[:before] || "#{field}_before".to_sym,
98
+ :after => options[:after] || "#{field}_after".to_sym
99
+ }
100
+ nil
101
+ end
102
+
103
+ def annotate(field, options = {}, &block) # haha
104
+ options.assert_valid_keys(:as)
105
+ save_as = options.has_key?(:as) ? options[:as] : field
106
+
107
+ unless columns_hash.has_key?(save_as.to_s)
108
+ raise ActiveRecord::StatementInvalid.new("No such attribute '#{field}' on #{@klass_reference.name}")
109
+ end
110
+
111
+ historical_tracks[save_as] = block.nil? ? field : block
112
+ end
113
+
114
+ end
115
+
116
+ module HistoricalRelationHelpers
77
117
 
78
118
  # Get a list of the modifications in a given history
79
119
  def modifications
80
- historical_relation.class.historical_fields.reject do |field|
81
- send(:"#{field}_before") == send(:"#{field}_after")
82
- end
120
+ self.class.historical_fields.reject do |field, options|
121
+ send(options[:before]) == send(options[:after])
122
+ end.keys
83
123
  end
84
124
 
85
125
  def to_s
86
- 'modified ' + modifications.join(', ') + " on #{historical_relation}"
126
+ return 'modified nothing' if modifications.empty?
127
+ str = 'modified ' + modifications.sort.join(', ')
128
+ str += " on #{historical_relation}" if self.class.instance_variable_get(:@track_historical_reference)
129
+ str
87
130
  end
88
131
 
89
132
  end
@@ -93,19 +136,26 @@ module TrackHistory
93
136
  private
94
137
 
95
138
  def record_historical_changes
96
- return if self.class.historical_fields.empty?
139
+ historical_fields = self.class.historical_class.historical_fields
140
+ historical_tracks = self.class.historical_class.historical_tracks
141
+ return if historical_fields.empty? && historical_tracks.empty?
97
142
  # go through each and build the hashes
98
143
  attributes = {}
99
- self.class.historical_fields.each do |field|
144
+ historical_fields.each do |field, field_options|
100
145
  next unless send(:"#{field}_changed?")
101
- attributes.merge! :"#{field}_before" => send(:"#{field}_was"), :"#{field}_after" => send(field.to_sym)
146
+ attributes.merge! field_options[:before] => send(:"#{field}_was"), field_options[:after] => send(field.to_sym)
102
147
  end
103
148
  return if attributes.empty? # nothing changed - skip out
104
149
  # then go through each track
105
- self.class.historical_tracks.each do |field, block|
106
- attributes[field] = block.nil? ? send(field) : (block.arity == 1 ? block.call(self) : instance_eval(&block)) # give access to the user object
150
+ historical_tracks.each do |field, block|
151
+ attributes[field] = block.is_a?(Symbol) ? send(block) : (block.arity == 1 ? block.call(self) : instance_eval(&block)) # give access to the user object
152
+ end
153
+ # record the change
154
+ if self.class.historical_class.track_historical_reference?
155
+ self.histories.create(attributes)
156
+ else
157
+ self.class.historical_class.create(attributes)
107
158
  end
108
- self.histories.create(attributes)
109
159
  end
110
160
 
111
161
  end
@@ -1,5 +1,5 @@
1
1
  module TrackHistory
2
2
 
3
- VERSION = '0.0.4'
3
+ VERSION = '0.0.7'
4
4
 
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: track_history
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 17
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 4
10
- version: 0.0.4
9
+ - 7
10
+ version: 0.0.7
11
11
  platform: ruby
12
12
  authors:
13
13
  - John Crepezzi
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-10 00:00:00 -05:00
18
+ date: 2011-01-11 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -32,6 +32,20 @@ dependencies:
32
32
  version: "0"
33
33
  type: :development
34
34
  version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: mysql
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
35
49
  description: Smart, performant model auditing
36
50
  email: john.crepezzi@patch.com
37
51
  executables: []