paper_trail 1.5.5 → 1.6.0

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/README.md CHANGED
@@ -11,6 +11,7 @@ PaperTrail lets you track changes to your models' data. It's good for auditing
11
11
  * Allows you to get at every version, including the original, even once destroyed.
12
12
  * Allows you to get at every version even if the schema has since changed.
13
13
  * Allows you to get at the version as of a particular time.
14
+ * Automatically restores the `has_one` associations as they were at the time.
14
15
  * Automatically records who was responsible via your controller. PaperTrail calls `current_user` by default, if it exists, but you can have it call any method you like.
15
16
  * Allows you to set who is responsible at model-level (useful for migrations).
16
17
  * Allows you to store arbitrary model-level metadata with each version (useful for filtering versions).
@@ -212,6 +213,27 @@ To find out who made a `version`'s object look that way, use `version.originator
212
213
  >> last_version.terminator # 'Bob'
213
214
 
214
215
 
216
+ ## Has-One Associations
217
+
218
+ PaperTrail automatically restores `:has_one` associations as they were at the time.
219
+
220
+ class Treasure < ActiveRecord::Base
221
+ has_one :location
222
+ end
223
+
224
+ >> treasure.amount # 100
225
+ >> treasure.location.latitude # 12.345
226
+
227
+ >> treasure.update_attributes :amount => 153
228
+ >> treasure.location.update_attributes :latitude => 54.321
229
+
230
+ >> t = treasure.versions.last.reify
231
+ >> t.amount # 100
232
+ >> t.location.latitude # 12.345
233
+
234
+ Unfortunately PaperTrail doesn't do this for `:has_many` associations (I can't get it to work) or `:belongs_to` (I ran out of time looking at `:has_many`).
235
+
236
+
215
237
  ## Has-Many-Through Associations
216
238
 
217
239
  PaperTrail can track most changes to the join table. Specifically it can track all additions but it can only track removals which fire the `after_destroy` callback on the join table. Here are some examples:
@@ -85,14 +85,10 @@ module PaperTrail
85
85
 
86
86
  # Returns the object (not a Version) as it was at the given timestamp.
87
87
  def version_at(timestamp)
88
- # Short-circuit if the current state is applicable.
89
- return self if self.updated_at <= timestamp
90
- # Look for the first version created after, rather than before, the
91
- # timestamp because a version stores how the object looked before the
92
- # change.
93
- version = versions.first :conditions => ['created_at > ?', timestamp],
94
- :order => 'created_at ASC'
95
- version.try :reify
88
+ # Because a version stores how its object looked *before* the change,
89
+ # we need to look for the first version created *after* the timestamp.
90
+ version = versions.first :conditions => ['created_at > ?', timestamp], :order => 'created_at ASC, id ASC'
91
+ version ? version.reify : self
96
92
  end
97
93
 
98
94
  # Returns the object (not a Version) as it was most recently.
@@ -109,6 +105,17 @@ module PaperTrail
109
105
  subsequent_version.reify if subsequent_version
110
106
  end
111
107
 
108
+ protected
109
+
110
+ # Returns the object (not a Version) as it was until the version record
111
+ # with the given id.
112
+ def version_until(id)
113
+ # Because a version stores how its object looked *before* the change,
114
+ # we need to look for the first version created *on or after* the id.
115
+ version = versions.first :conditions => ['id >= ?', id], :order => 'id ASC'
116
+ version ? version.reify : self
117
+ end
118
+
112
119
  private
113
120
 
114
121
  def merge_metadata(data)
@@ -36,6 +36,10 @@ class Version < ActiveRecord::Base
36
36
  end
37
37
 
38
38
  model.version = self
39
+ # Restore the model's has_one associations as they were when this version was
40
+ # superseded by the next (because that's what the user was looking at when they
41
+ # made the change).
42
+ reify_has_ones model
39
43
  model
40
44
  end
41
45
  end
@@ -66,4 +70,21 @@ class Version < ActiveRecord::Base
66
70
  :order => 'id ASC').index(self)
67
71
  end
68
72
 
73
+ private
74
+
75
+ def reify_has_ones(model)
76
+ model.class.reflect_on_all_associations(:has_one).each do |assoc|
77
+ child = model.send assoc.name
78
+ if child.respond_to? :version_until
79
+ if (version_until = child.version_until(id))
80
+ version_until.attributes.each do |k,v|
81
+ model.send(assoc.name).send "#{k}=", v rescue nil
82
+ end
83
+ else
84
+ model.send "#{assoc.name}=", nil
85
+ end
86
+ end
87
+ end
88
+ end
89
+
69
90
  end
@@ -1,3 +1,3 @@
1
1
  module PaperTrail
2
- VERSION = '1.5.5'
2
+ VERSION = '1.6.0'
3
3
  end
@@ -10,6 +10,7 @@ class FooWidget < Widget
10
10
  end
11
11
 
12
12
  class Wotsit < ActiveRecord::Base
13
+ has_paper_trail
13
14
  belongs_to :widget
14
15
  end
15
16
 
@@ -45,7 +46,8 @@ end
45
46
 
46
47
  class HasPaperTrailModelTest < Test::Unit::TestCase
47
48
  load_schema
48
-
49
+ =begin
50
+ =end
49
51
  context 'A record' do
50
52
  setup { @article = Article.create }
51
53
 
@@ -126,7 +128,8 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
126
128
  end
127
129
 
128
130
  should 'copy the has_one association when reifying' do
129
- assert_equal @wotsit, @reified_widget.wotsit
131
+ assert_nil @reified_widget.wotsit # wotsit wasn't there at the last version
132
+ assert_equal @wotsit, @widget.wotsit # wotsit came into being on the live object
130
133
  end
131
134
  end
132
135
 
@@ -644,4 +647,61 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
644
647
  end
645
648
  end
646
649
 
650
+
651
+ context 'A model with a has_one association' do
652
+ setup { @widget = Widget.create :name => 'widget_0' }
653
+ context 'before the associated was created' do
654
+ setup do
655
+ @widget.update_attributes :name => 'widget_1'
656
+ @wotsit = @widget.create_wotsit :name => 'wotsit_0'
657
+ end
658
+ context 'when reified' do
659
+ setup { @widget_0 = @widget.versions.last.reify }
660
+ should 'see the associated as it was at the time' do
661
+ assert_nil @widget_0.wotsit
662
+ end
663
+ end
664
+ end
665
+
666
+ context 'where the associated is created between model versions' do
667
+ setup do
668
+ @wotsit = @widget.create_wotsit :name => 'wotsit_0'
669
+ @widget.update_attributes :name => 'widget_1'
670
+ end
671
+ context 'when reified' do
672
+ setup { @widget_0 = @widget.versions.last.reify }
673
+ should 'see the associated as it was at the time' do
674
+ assert_equal 'wotsit_0', @widget_0.wotsit.name
675
+ end
676
+ end
677
+
678
+ context 'and then the associated is updated between model versions' do
679
+ setup do
680
+ @wotsit.update_attributes :name => 'wotsit_1'
681
+ @wotsit.update_attributes :name => 'wotsit_2'
682
+ @widget.update_attributes :name => 'widget_2'
683
+ end
684
+ context 'when reified' do
685
+ setup { @widget_1 = @widget.versions.last.reify }
686
+ should 'see the associated as it was at the time' do
687
+ assert_equal 'wotsit_2', @widget_1.wotsit.name
688
+ end
689
+ end
690
+ end
691
+
692
+ context 'and then the associated is destroyed between model versions' do
693
+ setup do
694
+ @wotsit.destroy
695
+ @widget.update_attributes :name => 'widget_3'
696
+ end
697
+ context 'when reified' do
698
+ setup { @widget_2 = @widget.versions.last.reify }
699
+ should 'see the associated as it was at the time' do
700
+ assert_nil @widget_2.wotsit
701
+ end
702
+ end
703
+ end
704
+ end
705
+ end
706
+
647
707
  end
@@ -37,6 +37,7 @@ ActiveRecord::Schema.define(:version => 0) do
37
37
  create_table :wotsits, :force => true do |t|
38
38
  t.integer :widget_id
39
39
  t.string :name
40
+ t.datetime :created_at, :updated_at
40
41
  end
41
42
 
42
43
  create_table :fluxors, :force => true do |t|
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paper_trail
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
- - 5
9
- - 5
10
- version: 1.5.5
8
+ - 6
9
+ - 0
10
+ version: 1.6.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Andy Stewart
@@ -15,13 +15,14 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-10-15 00:00:00 +01:00
18
+ date: 2010-10-20 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: bundler
23
+ type: :development
23
24
  prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
+ version_requirements: &id001 !ruby/object:Gem::Requirement
25
26
  none: false
26
27
  requirements:
27
28
  - - ~>
@@ -31,12 +32,12 @@ dependencies:
31
32
  - 1
32
33
  - 0
33
34
  version: "1.0"
34
- type: :development
35
- version_requirements: *id001
35
+ requirement: *id001
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
+ type: :development
38
39
  prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
40
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
41
  none: false
41
42
  requirements:
42
43
  - - "="
@@ -47,12 +48,12 @@ dependencies:
47
48
  - 8
48
49
  - 7
49
50
  version: 0.8.7
50
- type: :development
51
- version_requirements: *id002
51
+ requirement: *id002
52
52
  - !ruby/object:Gem::Dependency
53
53
  name: shoulda
54
+ type: :development
54
55
  prerelease: false
55
- requirement: &id003 !ruby/object:Gem::Requirement
56
+ version_requirements: &id003 !ruby/object:Gem::Requirement
56
57
  none: false
57
58
  requirements:
58
59
  - - "="
@@ -63,12 +64,12 @@ dependencies:
63
64
  - 10
64
65
  - 3
65
66
  version: 2.10.3
66
- type: :development
67
- version_requirements: *id003
67
+ requirement: *id003
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: activesupport
70
+ type: :development
70
71
  prerelease: false
71
- requirement: &id004 !ruby/object:Gem::Requirement
72
+ version_requirements: &id004 !ruby/object:Gem::Requirement
72
73
  none: false
73
74
  requirements:
74
75
  - - ~>
@@ -78,12 +79,12 @@ dependencies:
78
79
  - 2
79
80
  - 3
80
81
  version: "2.3"
81
- type: :development
82
- version_requirements: *id004
82
+ requirement: *id004
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: sqlite3-ruby
85
+ type: :development
85
86
  prerelease: false
86
- requirement: &id005 !ruby/object:Gem::Requirement
87
+ version_requirements: &id005 !ruby/object:Gem::Requirement
87
88
  none: false
88
89
  requirements:
89
90
  - - ~>
@@ -93,12 +94,12 @@ dependencies:
93
94
  - 1
94
95
  - 2
95
96
  version: "1.2"
96
- type: :development
97
- version_requirements: *id005
97
+ requirement: *id005
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: activerecord
100
+ type: :runtime
100
101
  prerelease: false
101
- requirement: &id006 !ruby/object:Gem::Requirement
102
+ version_requirements: &id006 !ruby/object:Gem::Requirement
102
103
  none: false
103
104
  requirements:
104
105
  - - ">="
@@ -108,12 +109,12 @@ dependencies:
108
109
  - 2
109
110
  - 3
110
111
  version: "2.3"
111
- type: :runtime
112
- version_requirements: *id006
112
+ requirement: *id006
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: actionpack
115
+ type: :runtime
115
116
  prerelease: false
116
- requirement: &id007 !ruby/object:Gem::Requirement
117
+ version_requirements: &id007 !ruby/object:Gem::Requirement
117
118
  none: false
118
119
  requirements:
119
120
  - - ">="
@@ -123,8 +124,7 @@ dependencies:
123
124
  - 2
124
125
  - 3
125
126
  version: "2.3"
126
- type: :runtime
127
- version_requirements: *id007
127
+ requirement: *id007
128
128
  description: Track changes to your models' data. Good for auditing or versioning.
129
129
  email: boss@airbladesoftware.com
130
130
  executables: []