paper_trail 3.0.9 → 4.0.0.beta1

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -2
  4. data/.travis.yml +5 -0
  5. data/CHANGELOG.md +37 -23
  6. data/README.md +170 -63
  7. data/gemfiles/3.0.gemfile +10 -4
  8. data/lib/generators/paper_trail/install_generator.rb +19 -3
  9. data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +11 -0
  10. data/lib/generators/paper_trail/templates/create_version_associations.rb +17 -0
  11. data/lib/paper_trail.rb +24 -4
  12. data/lib/paper_trail/cleaner.rb +3 -3
  13. data/lib/paper_trail/config.rb +17 -0
  14. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +7 -0
  15. data/lib/paper_trail/frameworks/rails.rb +1 -0
  16. data/lib/paper_trail/frameworks/rspec.rb +5 -0
  17. data/lib/paper_trail/has_paper_trail.rb +112 -38
  18. data/lib/paper_trail/version_association_concern.rb +13 -0
  19. data/lib/paper_trail/version_concern.rb +145 -38
  20. data/lib/paper_trail/version_number.rb +3 -3
  21. data/paper_trail.gemspec +11 -4
  22. data/spec/generators/install_generator_spec.rb +4 -4
  23. data/spec/models/fluxor_spec.rb +19 -0
  24. data/spec/models/gadget_spec.rb +10 -10
  25. data/spec/models/joined_version_spec.rb +9 -9
  26. data/spec/models/post_with_status_spec.rb +3 -3
  27. data/spec/models/version_spec.rb +49 -71
  28. data/spec/models/widget_spec.rb +124 -71
  29. data/spec/modules/version_concern_spec.rb +8 -8
  30. data/spec/modules/version_number_spec.rb +16 -16
  31. data/spec/paper_trail_spec.rb +17 -17
  32. data/spec/rails_helper.rb +34 -0
  33. data/spec/requests/articles_spec.rb +11 -11
  34. data/spec/spec_helper.rb +77 -36
  35. data/test/dummy/app/models/animal.rb +0 -2
  36. data/test/dummy/app/models/book.rb +4 -0
  37. data/test/dummy/app/models/customer.rb +4 -0
  38. data/test/dummy/app/models/editor.rb +4 -0
  39. data/test/dummy/app/models/editorship.rb +5 -0
  40. data/test/dummy/app/models/line_item.rb +4 -0
  41. data/test/dummy/app/models/order.rb +5 -0
  42. data/test/dummy/app/models/person.rb +1 -1
  43. data/test/dummy/app/models/post.rb +0 -1
  44. data/test/dummy/app/models/song.rb +0 -20
  45. data/test/dummy/app/models/widget.rb +4 -0
  46. data/test/dummy/config/application.rb +3 -0
  47. data/test/dummy/config/initializers/paper_trail.rb +1 -1
  48. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +41 -0
  49. data/test/dummy/db/schema.rb +95 -25
  50. data/test/dummy/public/404.html +26 -0
  51. data/test/dummy/public/422.html +26 -0
  52. data/test/dummy/public/500.html +26 -0
  53. data/test/dummy/public/favicon.ico +0 -0
  54. data/test/dummy/public/javascripts/application.js +2 -0
  55. data/test/dummy/public/javascripts/controls.js +965 -0
  56. data/test/dummy/public/javascripts/dragdrop.js +974 -0
  57. data/test/dummy/public/javascripts/effects.js +1123 -0
  58. data/test/dummy/public/javascripts/rails.js +175 -0
  59. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  60. data/test/test_helper.rb +2 -2
  61. data/test/time_travel_helper.rb +15 -0
  62. data/test/unit/model_test.rb +613 -185
  63. data/test/unit/serializer_test.rb +3 -3
  64. metadata +104 -54
  65. data/spec/models/animal_spec.rb +0 -19
  66. data/test/dummy/public/javascripts/prototype.js +0 -6001
@@ -0,0 +1,175 @@
1
+ (function() {
2
+ // Technique from Juriy Zaytsev
3
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
+ function isEventSupported(eventName) {
5
+ var el = document.createElement('div');
6
+ eventName = 'on' + eventName;
7
+ var isSupported = (eventName in el);
8
+ if (!isSupported) {
9
+ el.setAttribute(eventName, 'return;');
10
+ isSupported = typeof el[eventName] == 'function';
11
+ }
12
+ el = null;
13
+ return isSupported;
14
+ }
15
+
16
+ function isForm(element) {
17
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
+ }
19
+
20
+ function isInput(element) {
21
+ if (Object.isElement(element)) {
22
+ var name = element.nodeName.toUpperCase()
23
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
+ }
25
+ else return false
26
+ }
27
+
28
+ var submitBubbles = isEventSupported('submit'),
29
+ changeBubbles = isEventSupported('change')
30
+
31
+ if (!submitBubbles || !changeBubbles) {
32
+ // augment the Event.Handler class to observe custom events when needed
33
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
+ function(init, element, eventName, selector, callback) {
35
+ init(element, eventName, selector, callback)
36
+ // is the handler being attached to an element that doesn't support this event?
37
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
+ // "submit" => "emulated:submit"
40
+ this.eventName = 'emulated:' + this.eventName
41
+ }
42
+ }
43
+ )
44
+ }
45
+
46
+ if (!submitBubbles) {
47
+ // discover forms on the page by observing focus events which always bubble
48
+ document.on('focusin', 'form', function(focusEvent, form) {
49
+ // special handler for the real "submit" event (one-time operation)
50
+ if (!form.retrieve('emulated:submit')) {
51
+ form.on('submit', function(submitEvent) {
52
+ var emulated = form.fire('emulated:submit', submitEvent, true)
53
+ // if custom event received preventDefault, cancel the real one too
54
+ if (emulated.returnValue === false) submitEvent.preventDefault()
55
+ })
56
+ form.store('emulated:submit', true)
57
+ }
58
+ })
59
+ }
60
+
61
+ if (!changeBubbles) {
62
+ // discover form inputs on the page
63
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
+ // special handler for real "change" events
65
+ if (!input.retrieve('emulated:change')) {
66
+ input.on('change', function(changeEvent) {
67
+ input.fire('emulated:change', changeEvent, true)
68
+ })
69
+ input.store('emulated:change', true)
70
+ }
71
+ })
72
+ }
73
+
74
+ function handleRemote(element) {
75
+ var method, url, params;
76
+
77
+ var event = element.fire("ajax:before");
78
+ if (event.stopped) return false;
79
+
80
+ if (element.tagName.toLowerCase() === 'form') {
81
+ method = element.readAttribute('method') || 'post';
82
+ url = element.readAttribute('action');
83
+ params = element.serialize();
84
+ } else {
85
+ method = element.readAttribute('data-method') || 'get';
86
+ url = element.readAttribute('href');
87
+ params = {};
88
+ }
89
+
90
+ new Ajax.Request(url, {
91
+ method: method,
92
+ parameters: params,
93
+ evalScripts: true,
94
+
95
+ onComplete: function(request) { element.fire("ajax:complete", request); },
96
+ onSuccess: function(request) { element.fire("ajax:success", request); },
97
+ onFailure: function(request) { element.fire("ajax:failure", request); }
98
+ });
99
+
100
+ element.fire("ajax:after");
101
+ }
102
+
103
+ function handleMethod(element) {
104
+ var method = element.readAttribute('data-method'),
105
+ url = element.readAttribute('href'),
106
+ csrf_param = $$('meta[name=csrf-param]')[0],
107
+ csrf_token = $$('meta[name=csrf-token]')[0];
108
+
109
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
+ element.parentNode.insert(form);
111
+
112
+ if (method !== 'post') {
113
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
+ form.insert(field);
115
+ }
116
+
117
+ if (csrf_param) {
118
+ var param = csrf_param.readAttribute('content'),
119
+ token = csrf_token.readAttribute('content'),
120
+ field = new Element('input', { type: 'hidden', name: param, value: token });
121
+ form.insert(field);
122
+ }
123
+
124
+ form.submit();
125
+ }
126
+
127
+
128
+ document.on("click", "*[data-confirm]", function(event, element) {
129
+ var message = element.readAttribute('data-confirm');
130
+ if (!confirm(message)) event.stop();
131
+ });
132
+
133
+ document.on("click", "a[data-remote]", function(event, element) {
134
+ if (event.stopped) return;
135
+ handleRemote(element);
136
+ event.stop();
137
+ });
138
+
139
+ document.on("click", "a[data-method]", function(event, element) {
140
+ if (event.stopped) return;
141
+ handleMethod(element);
142
+ event.stop();
143
+ });
144
+
145
+ document.on("submit", function(event) {
146
+ var element = event.findElement(),
147
+ message = element.readAttribute('data-confirm');
148
+ if (message && !confirm(message)) {
149
+ event.stop();
150
+ return false;
151
+ }
152
+
153
+ var inputs = element.select("input[type=submit][data-disable-with]");
154
+ inputs.each(function(input) {
155
+ input.disabled = true;
156
+ input.writeAttribute('data-original-value', input.value);
157
+ input.value = input.readAttribute('data-disable-with');
158
+ });
159
+
160
+ var element = event.findElement("form[data-remote]");
161
+ if (element) {
162
+ handleRemote(element);
163
+ event.stop();
164
+ }
165
+ });
166
+
167
+ document.on("ajax:after", "form", function(event, element) {
168
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
+ inputs.each(function(input) {
170
+ input.value = input.readAttribute('data-original-value');
171
+ input.removeAttribute('data-original-value');
172
+ input.disabled = false;
173
+ });
174
+ });
175
+ })();
File without changes
data/test/test_helper.rb CHANGED
@@ -13,7 +13,7 @@ require File.expand_path("../dummy/config/environment.rb", __FILE__)
13
13
  require "rails/test_help"
14
14
  require 'shoulda'
15
15
  require 'ffaker'
16
- require 'database_cleaner' if using_mysql?
16
+ require 'database_cleaner'
17
17
 
18
18
  Rails.backtrace_cleaner.remove_silencers!
19
19
 
@@ -24,7 +24,7 @@ ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__
24
24
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
25
25
 
26
26
  # DatabaseCleaner is apparently necessary for doing proper transactions within MySQL (ugh)
27
- DatabaseCleaner.strategy = :truncation if using_mysql?
27
+ DatabaseCleaner.strategy = :truncation
28
28
 
29
29
  # global setup block resetting Thread.current
30
30
  class ActiveSupport::TestCase
@@ -0,0 +1,15 @@
1
+ if RUBY_VERSION < "1.9.2"
2
+ require 'delorean'
3
+
4
+ class Timecop
5
+ def self.travel(t)
6
+ Delorean.time_travel_to t
7
+ end
8
+
9
+ def self.return
10
+ Delorean.back_to_the_present
11
+ end
12
+ end
13
+ else
14
+ require 'timecop'
15
+ end
@@ -1,4 +1,5 @@
1
1
  require 'test_helper'
2
+ require 'time_travel_helper'
2
3
 
3
4
  class HasPaperTrailModelTest < ActiveSupport::TestCase
4
5
 
@@ -191,9 +192,8 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
191
192
  assert @widget.live?
192
193
  end
193
194
 
194
-
195
195
  context 'which is then created' do
196
- setup { @widget.update_attributes :name => 'Henry' }
196
+ setup { @widget.update_attributes :name => 'Henry', :created_at => Time.now - 1.day }
197
197
 
198
198
  should 'have one previous version' do
199
199
  assert_equal 1, @widget.versions.length
@@ -212,6 +212,10 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
212
212
  assert @widget.live?
213
213
  end
214
214
 
215
+ should 'use the widget created_at' do
216
+ assert_equal @widget.created_at.to_i, @widget.versions.first.created_at.to_i
217
+ end
218
+
215
219
  should 'have changes' do
216
220
 
217
221
  #TODO Postgres does not appear to pass back ActiveSupport::TimeWithZone,
@@ -301,13 +305,13 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
301
305
  should 'not copy the has_one association by default when reifying' do
302
306
  reified_widget = @widget.versions.last.reify
303
307
  assert_equal @wotsit, reified_widget.wotsit # association hasn't been affected by reifying
304
- assert_equal @wotsit, @widget.wotsit # confirm that the association is correct
308
+ assert_equal @wotsit, @widget.wotsit(true) # confirm that the association is correct
305
309
  end
306
310
 
307
311
  should 'copy the has_one association when reifying with :has_one => true' do
308
312
  reified_widget = @widget.versions.last.reify(:has_one => true)
309
313
  assert_nil reified_widget.wotsit # wotsit wasn't there at the last version
310
- assert_equal @wotsit, @widget.wotsit # wotsit came into being on the live object
314
+ assert_equal @wotsit, @widget.wotsit(true) # wotsit should still exist on live object
311
315
  end
312
316
  end
313
317
 
@@ -567,9 +571,9 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
567
571
 
568
572
  should 'track who made the change' do
569
573
  assert_equal 'Alice', @version.whodunnit
570
- assert_nil @version.paper_trail_originator
574
+ assert_nil @version.originator
571
575
  assert_equal 'Alice', @version.terminator
572
- assert_equal 'Alice', @widget.paper_trail_originator
576
+ assert_equal 'Alice', @widget.originator
573
577
  end
574
578
 
575
579
  context 'when a record is updated' do
@@ -581,9 +585,9 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
581
585
 
582
586
  should 'track who made the change' do
583
587
  assert_equal 'Bob', @version.whodunnit
584
- assert_equal 'Alice', @version.paper_trail_originator
588
+ assert_equal 'Alice', @version.originator
585
589
  assert_equal 'Bob', @version.terminator
586
- assert_equal 'Bob', @widget.paper_trail_originator
590
+ assert_equal 'Bob', @widget.originator
587
591
  end
588
592
 
589
593
  context 'when a record is destroyed' do
@@ -595,9 +599,9 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
595
599
 
596
600
  should 'track who made the change' do
597
601
  assert_equal 'Charlie', @version.whodunnit
598
- assert_equal 'Bob', @version.paper_trail_originator
602
+ assert_equal 'Bob', @version.originator
599
603
  assert_equal 'Charlie', @version.terminator
600
- assert_equal 'Charlie', @widget.paper_trail_originator
604
+ assert_equal 'Charlie', @widget.originator
601
605
  end
602
606
  end
603
607
  end
@@ -617,17 +621,15 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
617
621
  end
618
622
 
619
623
  # Currently the gem generates a bunch of deprecation warnings about serialized attributes on AR 4.2
620
- if ActiveRecord::VERSION::STRING < '4.2'
621
- should 'not generate warning' do
622
- # Tests that it doesn't try to write created_on as an attribute just because a created_on
623
- # method exists.
624
- warnings = capture(:stderr) { # Deprecation warning in Rails 3.2
625
- assert_nothing_raised { # ActiveModel::MissingAttributeError in Rails 4
626
- @wotsit.update_attributes! :name => 'changed'
627
- }
624
+ should 'not generate warning' do
625
+ # Tests that it doesn't try to write created_on as an attribute just because a created_on
626
+ # method exists.
627
+ warnings = capture(:stderr) { # Deprecation warning in Rails 3.2
628
+ assert_nothing_raised { # ActiveModel::MissingAttributeError in Rails 4
629
+ @wotsit.update_attributes! :name => 'changed'
628
630
  }
629
- assert_equal '', warnings
630
- end
631
+ }
632
+ assert_equal '', warnings
631
633
  end
632
634
 
633
635
  end
@@ -649,7 +651,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
649
651
  should 'should return the correct originator' do
650
652
  PaperTrail.whodunnit = 'Ben'
651
653
  @foo.update_attribute(:name, 'Geoffrey')
652
- assert_equal PaperTrail.whodunnit, @foo.paper_trail_originator
654
+ assert_equal PaperTrail.whodunnit, @foo.originator
653
655
  end
654
656
 
655
657
  context 'when destroyed' do
@@ -734,7 +736,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
734
736
  should 'return versions in the time period' do
735
737
  assert_equal ['Fidget'], @widget.versions_between(20.days.ago, 10.days.ago).map(&:name)
736
738
  assert_equal ['Widget', 'Fidget'], @widget.versions_between(45.days.ago, 10.days.ago).map(&:name)
737
- assert_equal ['Fidget', 'Digit'], @widget.versions_between(16.days.ago, 1.minute.ago).map(&:name)
739
+ assert_equal ['Fidget', 'Digit', 'Digit'], @widget.versions_between(16.days.ago, 1.minute.ago).map(&:name)
738
740
  assert_equal [], @widget.versions_between(60.days.ago, 45.days.ago).map(&:name)
739
741
  end
740
742
  end
@@ -930,7 +932,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
930
932
  count = PaperTrail::Version.count
931
933
  @book.authors.create :name => 'Tolstoy'
932
934
  assert_equal 2, PaperTrail::Version.count - count
933
- assert_same_elements [Person.last, Authorship.last], [PaperTrail::Version.all[-2].item, PaperTrail::Version.last.item]
935
+ assert_same_elements [Person.last, Authorship.last], [PaperTrail::Version.order(:id).to_a[-2].item, PaperTrail::Version.last.item]
934
936
  end
935
937
 
936
938
  should 'store version on join destroy' do
@@ -952,174 +954,100 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
952
954
  end
953
955
  end
954
956
 
955
-
956
- context 'A model with a has_one association' do
957
- setup { @widget = Widget.create :name => 'widget_0' }
958
-
959
- context 'before the associated was created' do
960
- setup do
961
- @widget.update_attributes :name => 'widget_1'
962
- @wotsit = @widget.create_wotsit :name => 'wotsit_0'
963
- end
964
-
965
- context 'when reified' do
966
- setup { @widget_0 = @widget.versions.last.reify(:has_one => 1) }
967
-
968
- should 'see the associated as it was at the time' do
969
- assert_nil @widget_0.wotsit
970
- end
971
- end
972
- end
973
-
974
- context 'where the association is created between model versions' do
975
- setup do
976
- @wotsit = @widget.create_wotsit :name => 'wotsit_0'
977
- make_last_version_earlier @wotsit
978
-
979
- @widget.update_attributes :name => 'widget_1'
980
- end
981
-
982
- context 'when reified' do
983
- setup { @widget_0 = @widget.versions.last.reify(:has_one => 1) }
984
-
985
- should 'see the associated as it was at the time' do
986
- assert_equal 'wotsit_0', @widget_0.wotsit.name
987
- end
988
- end
989
-
990
- context 'and then the associated is updated between model versions' do
991
- setup do
992
- @wotsit.update_attributes :name => 'wotsit_1'
993
- make_last_version_earlier @wotsit
994
- @wotsit.update_attributes :name => 'wotsit_2'
995
- make_last_version_earlier @wotsit
996
-
997
- @widget.update_attributes :name => 'widget_2'
998
- @wotsit.update_attributes :name => 'wotsit_3'
999
- end
1000
-
1001
- context 'when reified' do
1002
- setup { @widget_1 = @widget.versions.last.reify(:has_one => 1) }
1003
-
1004
- should 'see the associated as it was at the time' do
1005
- assert_equal 'wotsit_2', @widget_1.wotsit.name
1006
- end
1007
- end
1008
-
1009
- context 'when reified opting out of has_one reification' do
1010
- setup { @widget_1 = @widget.versions.last.reify(:has_one => false) }
1011
-
1012
- should 'see the associated as it is live' do
1013
- assert_equal 'wotsit_3', @widget_1.wotsit.name
1014
- end
1015
- end
1016
- end
1017
-
1018
- context 'and then the associated is destroyed between model versions' do
1019
- setup do
1020
- @wotsit.destroy
1021
- make_last_version_earlier @wotsit
1022
-
1023
- @widget.update_attributes :name => 'widget_3'
1024
- end
1025
-
1026
- context 'when reified' do
1027
- setup { @widget_2 = @widget.versions.last.reify(:has_one => 1) }
1028
-
1029
- should 'see the associated as it was at the time' do
1030
- assert_nil @widget_2.wotsit
1031
- end
1032
- end
1033
- end
1034
- end
1035
- end
1036
-
1037
- context 'When an attribute has a custom serializer' do
1038
- setup { @person = Person.new(:time_zone => "Samoa") }
1039
-
1040
- should "be an instance of ActiveSupport::TimeZone" do
1041
- assert_equal ActiveSupport::TimeZone, @person.time_zone.class
1042
- end
1043
-
1044
- context 'when the model is saved' do
957
+ # `serialized_attributes` is deprecated in ActiveRecord 5.0
958
+ if ::ActiveRecord::VERSION::MAJOR < 5
959
+ context 'When an attribute has a custom serializer' do
1045
960
  setup do
1046
- @changes_before_save = @person.changes.dup
1047
- @person.save!
961
+ PaperTrail.config.serialized_attributes = true
962
+ @person = Person.new(:time_zone => "Samoa")
1048
963
  end
1049
964
 
1050
- # Test for serialization:
1051
- should 'version.object_changes should not have stored the default, ridiculously long (to_yaml) serialization of the TimeZone object' do
1052
- assert @person.versions.last.object_changes.length < 105, "object_changes length was #{@person.versions.last.object_changes.length}"
1053
- end
1054
- # It should store the serialized value.
1055
- should 'version.object_changes attribute should have stored the value returned by the attribute serializer' do
1056
- as_stored_in_version = HashWithIndifferentAccess[YAML::load(@person.versions.last.object_changes)]
1057
- assert_equal [nil, 'Samoa'], as_stored_in_version[:time_zone]
1058
- serialized_value = Person::TimeZoneSerializer.dump(@person.time_zone)
1059
- assert_equal serialized_value, as_stored_in_version[:time_zone].last
1060
- end
965
+ teardown { PaperTrail.config.serialized_attributes = false }
1061
966
 
1062
- # Tests for unserialization:
1063
- should 'version.changeset should convert the attribute value back to its original, unserialized value' do
1064
- unserialized_value = Person::TimeZoneSerializer.load(@person.time_zone)
1065
- assert_equal unserialized_value, @person.versions.last.changeset[:time_zone].last
1066
- end
1067
- should "record.changes (before save) returns the original, unserialized values" do
1068
- assert_equal [NilClass, ActiveSupport::TimeZone], @changes_before_save[:time_zone].map(&:class)
1069
- end
1070
- should 'version.changeset should be the same as record.changes was before the save' do
1071
- assert_equal @changes_before_save, @person.versions.last.changeset.delete_if { |key, val| key.to_sym == :id }
1072
- assert_equal [NilClass, ActiveSupport::TimeZone], @person.versions.last.changeset[:time_zone].map(&:class)
967
+ should "be an instance of ActiveSupport::TimeZone" do
968
+ assert_equal ActiveSupport::TimeZone, @person.time_zone.class
1073
969
  end
1074
970
 
1075
- context 'when that attribute is updated' do
971
+ context 'when the model is saved' do
1076
972
  setup do
1077
- @attribute_value_before_change = @person.time_zone
1078
- @person.assign_attributes({ :time_zone => 'Pacific Time (US & Canada)' })
1079
973
  @changes_before_save = @person.changes.dup
1080
974
  @person.save!
1081
975
  end
1082
976
 
1083
- # Tests for serialization:
1084
- # Before the serialized attributes fix, the object/object_changes value that was stored was ridiculously long (58723).
1085
- should 'version.object should not have stored the default, ridiculously long (to_yaml) serialization of the TimeZone object' do
1086
- assert @person.versions.last.object.length < 105, "object length was #{@person.versions.last.object.length}"
1087
- end
1088
- # Need an additional clause to detect what version of ActiveRecord is being used for this test because AR4 injects the `updated_at` column into the changeset for updates to models
977
+ # Test for serialization:
1089
978
  should 'version.object_changes should not have stored the default, ridiculously long (to_yaml) serialization of the TimeZone object' do
1090
- assert @person.versions.last.object_changes.length < (ActiveRecord::VERSION::MAJOR < 4 ? 105 : 118), "object_changes length was #{@person.versions.last.object_changes.length}"
1091
- end
1092
- # But now it stores the short, serialized value.
1093
- should 'version.object attribute should have stored the value returned by the attribute serializer' do
1094
- as_stored_in_version = HashWithIndifferentAccess[YAML::load(@person.versions.last.object)]
1095
- assert_equal 'Samoa', as_stored_in_version[:time_zone]
1096
- serialized_value = Person::TimeZoneSerializer.dump(@attribute_value_before_change)
1097
- assert_equal serialized_value, as_stored_in_version[:time_zone]
979
+ assert @person.versions.last.object_changes.length < 105, "object_changes length was #{@person.versions.last.object_changes.length}"
1098
980
  end
981
+ # It should store the serialized value.
1099
982
  should 'version.object_changes attribute should have stored the value returned by the attribute serializer' do
1100
983
  as_stored_in_version = HashWithIndifferentAccess[YAML::load(@person.versions.last.object_changes)]
1101
- assert_equal ['Samoa', 'Pacific Time (US & Canada)'], as_stored_in_version[:time_zone]
984
+ assert_equal [nil, 'Samoa'], as_stored_in_version[:time_zone]
1102
985
  serialized_value = Person::TimeZoneSerializer.dump(@person.time_zone)
1103
986
  assert_equal serialized_value, as_stored_in_version[:time_zone].last
1104
987
  end
1105
988
 
1106
989
  # Tests for unserialization:
1107
- should 'version.reify should convert the attribute value back to its original, unserialized value' do
1108
- unserialized_value = Person::TimeZoneSerializer.load(@attribute_value_before_change)
1109
- assert_equal unserialized_value, @person.versions.last.reify.time_zone
1110
- end
1111
990
  should 'version.changeset should convert the attribute value back to its original, unserialized value' do
1112
991
  unserialized_value = Person::TimeZoneSerializer.load(@person.time_zone)
1113
992
  assert_equal unserialized_value, @person.versions.last.changeset[:time_zone].last
1114
993
  end
1115
994
  should "record.changes (before save) returns the original, unserialized values" do
1116
- assert_equal [ActiveSupport::TimeZone, ActiveSupport::TimeZone], @changes_before_save[:time_zone].map(&:class)
995
+ assert_equal [NilClass, ActiveSupport::TimeZone], @changes_before_save[:time_zone].map(&:class)
1117
996
  end
1118
997
  should 'version.changeset should be the same as record.changes was before the save' do
1119
- assert_equal @changes_before_save, @person.versions.last.changeset
1120
- assert_equal [ActiveSupport::TimeZone, ActiveSupport::TimeZone], @person.versions.last.changeset[:time_zone].map(&:class)
998
+ assert_equal @changes_before_save, @person.versions.last.changeset.delete_if { |key, val| key.to_sym == :id }
999
+ assert_equal [NilClass, ActiveSupport::TimeZone], @person.versions.last.changeset[:time_zone].map(&:class)
1121
1000
  end
1122
1001
 
1002
+ context 'when that attribute is updated' do
1003
+ setup do
1004
+ @attribute_value_before_change = @person.time_zone
1005
+ @person.assign_attributes({ :time_zone => 'Pacific Time (US & Canada)' })
1006
+ @changes_before_save = @person.changes.dup
1007
+ @person.save!
1008
+ end
1009
+
1010
+ # Tests for serialization:
1011
+ # Before the serialized attributes fix, the object/object_changes value that was stored was ridiculously long (58723).
1012
+ should 'version.object should not have stored the default, ridiculously long (to_yaml) serialization of the TimeZone object' do
1013
+ assert @person.versions.last.object.length < 105, "object length was #{@person.versions.last.object.length}"
1014
+ end
1015
+ # Need an additional clause to detect what version of ActiveRecord is being used for this test because AR4 injects the `updated_at` column into the changeset for updates to models
1016
+ should 'version.object_changes should not have stored the default, ridiculously long (to_yaml) serialization of the TimeZone object' do
1017
+ assert @person.versions.last.object_changes.length < (ActiveRecord::VERSION::MAJOR < 4 ? 105 : 118), "object_changes length was #{@person.versions.last.object_changes.length}"
1018
+ end
1019
+ # But now it stores the short, serialized value.
1020
+ should 'version.object attribute should have stored the value returned by the attribute serializer' do
1021
+ as_stored_in_version = HashWithIndifferentAccess[YAML::load(@person.versions.last.object)]
1022
+ assert_equal 'Samoa', as_stored_in_version[:time_zone]
1023
+ serialized_value = Person::TimeZoneSerializer.dump(@attribute_value_before_change)
1024
+ assert_equal serialized_value, as_stored_in_version[:time_zone]
1025
+ end
1026
+ should 'version.object_changes attribute should have stored the value returned by the attribute serializer' do
1027
+ as_stored_in_version = HashWithIndifferentAccess[YAML::load(@person.versions.last.object_changes)]
1028
+ assert_equal ['Samoa', 'Pacific Time (US & Canada)'], as_stored_in_version[:time_zone]
1029
+ serialized_value = Person::TimeZoneSerializer.dump(@person.time_zone)
1030
+ assert_equal serialized_value, as_stored_in_version[:time_zone].last
1031
+ end
1032
+
1033
+ # Tests for unserialization:
1034
+ should 'version.reify should convert the attribute value back to its original, unserialized value' do
1035
+ unserialized_value = Person::TimeZoneSerializer.load(@attribute_value_before_change)
1036
+ assert_equal unserialized_value, @person.versions.last.reify.time_zone
1037
+ end
1038
+ should 'version.changeset should convert the attribute value back to its original, unserialized value' do
1039
+ unserialized_value = Person::TimeZoneSerializer.load(@person.time_zone)
1040
+ assert_equal unserialized_value, @person.versions.last.changeset[:time_zone].last
1041
+ end
1042
+ should "record.changes (before save) returns the original, unserialized values" do
1043
+ assert_equal [ActiveSupport::TimeZone, ActiveSupport::TimeZone], @changes_before_save[:time_zone].map(&:class)
1044
+ end
1045
+ should 'version.changeset should be the same as record.changes was before the save' do
1046
+ assert_equal @changes_before_save, @person.versions.last.changeset
1047
+ assert_equal [ActiveSupport::TimeZone, ActiveSupport::TimeZone], @person.versions.last.changeset[:time_zone].map(&:class)
1048
+ end
1049
+
1050
+ end
1123
1051
  end
1124
1052
  end
1125
1053
  end
@@ -1174,21 +1102,6 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
1174
1102
  should 'return "overwritten" value on reified instance' do
1175
1103
  assert_equal 4, @song.versions.last.reify.length
1176
1104
  end
1177
-
1178
- context 'Has a virtual attribute injected into the ActiveModel::Dirty changes' do
1179
- setup do
1180
- @song.name = 'Good Vibrations'
1181
- @song.save
1182
- @song.name = nil
1183
- end
1184
-
1185
- should 'return persist the changes on the live instance properly' do
1186
- assert_equal nil, @song.name
1187
- end
1188
- should 'return "overwritten" virtual attribute on the reified instance' do
1189
- assert_equal 'Good Vibrations', @song.versions.last.reify.name
1190
- end
1191
- end
1192
1105
  end
1193
1106
 
1194
1107
 
@@ -1399,15 +1312,530 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
1399
1312
  assert_equal 3, @widget.versions.size
1400
1313
  end
1401
1314
  end
1315
+ end
1402
1316
 
1403
- private
1404
1317
 
1405
- # Updates `model`'s last version so it looks like the version was
1406
- # created 2 seconds ago.
1407
- def make_last_version_earlier(model)
1408
- PaperTrail::Version.record_timestamps = false
1409
- model.versions.last.update_attributes :created_at => 2.seconds.ago
1410
- PaperTrail::Version.record_timestamps = true
1318
+ class HasPaperTrailModelTransactionalTest < ActiveSupport::TestCase
1319
+ # These would have been done in test_helper.rb if using_mysql? is true
1320
+ unless using_mysql?
1321
+ self.use_transactional_fixtures = false
1322
+ setup { DatabaseCleaner.start }
1411
1323
  end
1412
1324
 
1413
- end
1325
+ teardown do
1326
+ Timecop.return
1327
+ # This would have been done in test_helper.rb if using_mysql? is true
1328
+ DatabaseCleaner.clean unless using_mysql?
1329
+ end
1330
+
1331
+ context 'A model with a has_one association' do
1332
+ setup { @widget = Widget.create :name => 'widget_0' }
1333
+
1334
+ context 'before the associated was created' do
1335
+ setup do
1336
+ @widget.update_attributes :name => 'widget_1'
1337
+ @wotsit = @widget.create_wotsit :name => 'wotsit_0'
1338
+ end
1339
+
1340
+ context 'when reified' do
1341
+ setup { @widget_0 = @widget.versions.last.reify(:has_one => true) }
1342
+
1343
+ should 'see the associated as it was at the time' do
1344
+ assert_nil @widget_0.wotsit
1345
+ end
1346
+
1347
+ should 'not persist changes to the live association' do
1348
+ assert_equal @wotsit, @widget.wotsit(true)
1349
+ end
1350
+ end
1351
+ end
1352
+
1353
+ context 'where the association is created between model versions' do
1354
+ setup do
1355
+ @wotsit = @widget.create_wotsit :name => 'wotsit_0'
1356
+ Timecop.travel 1.second.since
1357
+ @widget.update_attributes :name => 'widget_1'
1358
+ end
1359
+
1360
+ context 'when reified' do
1361
+ setup { @widget_0 = @widget.versions.last.reify(:has_one => true) }
1362
+
1363
+ should 'see the associated as it was at the time' do
1364
+ assert_equal 'wotsit_0', @widget_0.wotsit.name
1365
+ end
1366
+
1367
+ should 'not persist changes to the live association' do
1368
+ assert_equal @wotsit, @widget.wotsit(true)
1369
+ end
1370
+ end
1371
+
1372
+ context 'and then the associated is updated between model versions' do
1373
+ setup do
1374
+ @wotsit.update_attributes :name => 'wotsit_1'
1375
+ @wotsit.update_attributes :name => 'wotsit_2'
1376
+ Timecop.travel 1.second.since
1377
+ @widget.update_attributes :name => 'widget_2'
1378
+ @wotsit.update_attributes :name => 'wotsit_3'
1379
+ end
1380
+
1381
+ context 'when reified' do
1382
+ setup { @widget_1 = @widget.versions.last.reify(:has_one => true) }
1383
+
1384
+ should 'see the associated as it was at the time' do
1385
+ assert_equal 'wotsit_2', @widget_1.wotsit.name
1386
+ end
1387
+
1388
+ should 'not persist changes to the live association' do
1389
+ assert_equal 'wotsit_3', @widget.wotsit(true).name
1390
+ end
1391
+ end
1392
+
1393
+ context 'when reified opting out of has_one reification' do
1394
+ setup { @widget_1 = @widget.versions.last.reify(:has_one => false) }
1395
+
1396
+ should 'see the associated as it is live' do
1397
+ assert_equal 'wotsit_3', @widget_1.wotsit.name
1398
+ end
1399
+ end
1400
+ end
1401
+
1402
+ context 'and then the associated is destroyed' do
1403
+ setup do
1404
+ @wotsit.destroy
1405
+ end
1406
+
1407
+ context 'when reify' do
1408
+ setup { @widget_1 = @widget.versions.last.reify(:has_one => true) }
1409
+
1410
+ should 'see the associated as it was at the time' do
1411
+ assert_equal @wotsit, @widget_1.wotsit
1412
+ end
1413
+
1414
+ should 'not persist changes to the live association' do
1415
+ assert_nil @widget.wotsit(true)
1416
+ end
1417
+ end
1418
+
1419
+ context 'and then the model is updated' do
1420
+ setup do
1421
+ Timecop.travel 1.second.since
1422
+ @widget.update_attributes :name => 'widget_3'
1423
+ end
1424
+
1425
+ context 'when reified' do
1426
+ setup { @widget_2 = @widget.versions.last.reify(:has_one => true) }
1427
+
1428
+ should 'see the associated as it was at the time' do
1429
+ assert_nil @widget_2.wotsit
1430
+ end
1431
+ end
1432
+ end
1433
+ end
1434
+ end
1435
+ end
1436
+
1437
+ context 'A model with a has_many association' do
1438
+ setup { @customer = Customer.create :name => 'customer_0' }
1439
+
1440
+ context 'updated before the associated was created' do
1441
+ setup do
1442
+ @customer.update_attributes! :name => 'customer_1'
1443
+ @customer.orders.create! :order_date => Date.today
1444
+ end
1445
+
1446
+ context 'when reified' do
1447
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
1448
+
1449
+ should 'see the associated as it was at the time' do
1450
+ assert_equal [], @customer_0.orders
1451
+ end
1452
+
1453
+ should 'not persist changes to the live association' do
1454
+ assert_not_equal [], @customer.orders(true)
1455
+ end
1456
+ end
1457
+
1458
+ context 'when reified with option mark_for_destruction' do
1459
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
1460
+
1461
+ should 'mark the associated for destruction' do
1462
+ assert_equal [true], @customer_0.orders.map(&:marked_for_destruction?)
1463
+ end
1464
+ end
1465
+ end
1466
+
1467
+ context 'where the association is created between model versions' do
1468
+ setup do
1469
+ @order = @customer.orders.create! :order_date => 'order_date_0'
1470
+ Timecop.travel 1.second.since
1471
+ @customer.update_attributes :name => 'customer_1'
1472
+ end
1473
+
1474
+ context 'when reified' do
1475
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
1476
+
1477
+ should 'see the associated as it was at the time' do
1478
+ assert_equal ['order_date_0'], @customer_0.orders.map(&:order_date)
1479
+ end
1480
+ end
1481
+
1482
+ context 'and then a nested has_many association is created' do
1483
+ setup do
1484
+ @order.line_items.create! :product => 'product_0'
1485
+ end
1486
+
1487
+ context 'when reified' do
1488
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
1489
+
1490
+ should 'see the live version of the nested association' do
1491
+ assert_equal ['product_0'], @customer_0.orders.first.line_items.map(&:product)
1492
+ end
1493
+ end
1494
+ end
1495
+
1496
+ context 'and then the associated is updated between model versions' do
1497
+ setup do
1498
+ @order.update_attributes :order_date => 'order_date_1'
1499
+ @order.update_attributes :order_date => 'order_date_2'
1500
+ Timecop.travel 1.second.since
1501
+ @customer.update_attributes :name => 'customer_2'
1502
+ @order.update_attributes :order_date => 'order_date_3'
1503
+ end
1504
+
1505
+ context 'when reified' do
1506
+ setup { @customer_1 = @customer.versions.last.reify(:has_many => true) }
1507
+
1508
+ should 'see the associated as it was at the time' do
1509
+ assert_equal ['order_date_2'], @customer_1.orders.map(&:order_date)
1510
+ end
1511
+
1512
+ should 'not persist changes to the live association' do
1513
+ assert_equal ['order_date_3'], @customer.orders(true).map(&:order_date)
1514
+ end
1515
+ end
1516
+
1517
+ context 'when reified opting out of has_many reification' do
1518
+ setup { @customer_1 = @customer.versions.last.reify(:has_many => false) }
1519
+
1520
+ should 'see the associated as it is live' do
1521
+ assert_equal ['order_date_3'], @customer_1.orders.map(&:order_date)
1522
+ end
1523
+ end
1524
+
1525
+ context 'and then the associated is destroyed' do
1526
+ setup do
1527
+ @order.destroy
1528
+ end
1529
+
1530
+ context 'when reified' do
1531
+ setup { @customer_1 = @customer.versions.last.reify(:has_many => true) }
1532
+
1533
+ should 'see the associated as it was at the time' do
1534
+ assert_equal ['order_date_2'], @customer_1.orders.map(&:order_date)
1535
+ end
1536
+
1537
+ should 'not persist changes to the live association' do
1538
+ assert_equal [], @customer.orders(true)
1539
+ end
1540
+ end
1541
+ end
1542
+ end
1543
+
1544
+ context 'and then the associated is destroyed' do
1545
+ setup do
1546
+ @order.destroy
1547
+ end
1548
+
1549
+ context 'when reified' do
1550
+ setup { @customer_1 = @customer.versions.last.reify(:has_many => true) }
1551
+
1552
+ should 'see the associated as it was at the time' do
1553
+ assert_equal [@order.order_date], @customer_1.orders.map(&:order_date)
1554
+ end
1555
+
1556
+ should 'not persist changes to the live association' do
1557
+ assert_equal [], @customer.orders(true)
1558
+ end
1559
+ end
1560
+ end
1561
+
1562
+ context 'and then the associated is destroyed between model versions' do
1563
+ setup do
1564
+ @order.destroy
1565
+ Timecop.travel 1.second.since
1566
+ @customer.update_attributes :name => 'customer_2'
1567
+ end
1568
+
1569
+ context 'when reified' do
1570
+ setup { @customer_1 = @customer.versions.last.reify(:has_many => true) }
1571
+
1572
+ should 'see the associated as it was at the time' do
1573
+ assert_equal [], @customer_1.orders
1574
+ end
1575
+ end
1576
+ end
1577
+
1578
+ context 'and then another association is added' do
1579
+ setup do
1580
+ @customer.orders.create! :order_date => 'order_date_1'
1581
+ end
1582
+
1583
+ context 'when reified' do
1584
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
1585
+
1586
+ should 'see the associated as it was at the time' do
1587
+ assert_equal ['order_date_0'], @customer_0.orders.map(&:order_date)
1588
+ end
1589
+
1590
+ should 'not persist changes to the live association' do
1591
+ assert_equal ['order_date_0', 'order_date_1'], @customer.orders(true).map(&:order_date).sort
1592
+ end
1593
+ end
1594
+
1595
+ context 'when reified with option mark_for_destruction' do
1596
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
1597
+
1598
+ should 'mark the newly associated for destruction' do
1599
+ assert @customer_0.orders.detect { |o| o.order_date == 'order_date_1'}.marked_for_destruction?
1600
+ end
1601
+ end
1602
+ end
1603
+ end
1604
+ end
1605
+
1606
+ context 'A model with a has_many through association' do
1607
+ setup { @book = Book.create :title => 'book_0' }
1608
+
1609
+ context 'updated before the associated was created' do
1610
+ setup do
1611
+ @book.update_attributes! :title => 'book_1'
1612
+ @book.authors.create! :name => 'author_0'
1613
+ end
1614
+
1615
+ context 'when reified' do
1616
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
1617
+
1618
+ should 'see the associated as it was at the time' do
1619
+ assert_equal [], @book_0.authors
1620
+ end
1621
+
1622
+ should 'not persist changes to the live association' do
1623
+ assert_equal ['author_0'], @book.authors(true).map(&:name)
1624
+ end
1625
+ end
1626
+
1627
+ context 'when reified with option mark_for_destruction' do
1628
+ setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
1629
+
1630
+ should 'mark the associated for destruction' do
1631
+ assert_equal [true], @book_0.authors.map(&:marked_for_destruction?)
1632
+ end
1633
+
1634
+ should 'mark the associated-through for destruction' do
1635
+ assert_equal [true], @book_0.authorships.map(&:marked_for_destruction?)
1636
+ end
1637
+ end
1638
+ end
1639
+
1640
+ context 'updated before it is associated with an existing one' do
1641
+ setup do
1642
+ person_existing = Person.create(:name => 'person_existing')
1643
+ Timecop.travel 1.second.since
1644
+ @book.update_attributes! :title => 'book_1'
1645
+ @book.authors << person_existing
1646
+ end
1647
+
1648
+ context 'when reified' do
1649
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
1650
+
1651
+ should 'see the associated as it was at the time' do
1652
+ assert_equal [], @book_0.authors
1653
+ end
1654
+ end
1655
+
1656
+ context 'when reified with option mark_for_destruction' do
1657
+ setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
1658
+
1659
+ should 'not mark the associated for destruction' do
1660
+ assert_equal [false], @book_0.authors.map(&:marked_for_destruction?)
1661
+ end
1662
+
1663
+ should 'mark the associated-through for destruction' do
1664
+ assert_equal [true], @book_0.authorships.map(&:marked_for_destruction?)
1665
+ end
1666
+ end
1667
+ end
1668
+
1669
+ context 'where the association is created between model versions' do
1670
+ setup do
1671
+ @author = @book.authors.create! :name => 'author_0'
1672
+ @person_existing = Person.create(:name => 'person_existing')
1673
+ Timecop.travel 1.second.since
1674
+ @book.update_attributes! :title => 'book_1'
1675
+ end
1676
+
1677
+ context 'when reified' do
1678
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
1679
+
1680
+ should 'see the associated as it was at the time' do
1681
+ assert_equal ['author_0'], @book_0.authors.map(&:name)
1682
+ end
1683
+ end
1684
+
1685
+ context 'and then the associated is updated between model versions' do
1686
+ setup do
1687
+ @author.update_attributes :name => 'author_1'
1688
+ @author.update_attributes :name => 'author_2'
1689
+ Timecop.travel 1.second.since
1690
+ @book.update_attributes :title => 'book_2'
1691
+ @author.update_attributes :name => 'author_3'
1692
+ end
1693
+
1694
+ context 'when reified' do
1695
+ setup { @book_1 = @book.versions.last.reify(:has_many => true) }
1696
+
1697
+ should 'see the associated as it was at the time' do
1698
+ assert_equal ['author_2'], @book_1.authors.map(&:name)
1699
+ end
1700
+
1701
+ should 'not persist changes to the live association' do
1702
+ assert_equal ['author_3'], @book.authors(true).map(&:name)
1703
+ end
1704
+ end
1705
+
1706
+ context 'when reified opting out of has_many reification' do
1707
+ setup { @book_1 = @book.versions.last.reify(:has_many => false) }
1708
+
1709
+ should 'see the associated as it is live' do
1710
+ assert_equal ['author_3'], @book_1.authors.map(&:name)
1711
+ end
1712
+ end
1713
+ end
1714
+
1715
+ context 'and then the associated is destroyed' do
1716
+ setup do
1717
+ @author.destroy
1718
+ end
1719
+
1720
+ context 'when reified' do
1721
+ setup { @book_1 = @book.versions.last.reify(:has_many => true) }
1722
+
1723
+ should 'see the associated as it was at the time' do
1724
+ assert_equal [@author.name], @book_1.authors.map(&:name)
1725
+ end
1726
+
1727
+ should 'not persist changes to the live association' do
1728
+ assert_equal [], @book.authors(true)
1729
+ end
1730
+ end
1731
+ end
1732
+
1733
+ context 'and then the associated is destroyed between model versions' do
1734
+ setup do
1735
+ @author.destroy
1736
+ Timecop.travel 1.second.since
1737
+ @book.update_attributes :title => 'book_2'
1738
+ end
1739
+
1740
+ context 'when reified' do
1741
+ setup { @book_1 = @book.versions.last.reify(:has_many => true) }
1742
+
1743
+ should 'see the associated as it was at the time' do
1744
+ assert_equal [], @book_1.authors
1745
+ end
1746
+ end
1747
+ end
1748
+
1749
+ context 'and then the associated is dissociated between model versions' do
1750
+ setup do
1751
+ @book.authors = []
1752
+ Timecop.travel 1.second.since
1753
+ @book.update_attributes :title => 'book_2'
1754
+ end
1755
+
1756
+ context 'when reified' do
1757
+ setup { @book_1 = @book.versions.last.reify(:has_many => true) }
1758
+
1759
+ should 'see the associated as it was at the time' do
1760
+ assert_equal [], @book_1.authors
1761
+ end
1762
+ end
1763
+ end
1764
+
1765
+ context 'and then another associated is created' do
1766
+ setup do
1767
+ @book.authors.create! :name => 'author_1'
1768
+ end
1769
+
1770
+ context 'when reified' do
1771
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
1772
+
1773
+ should 'only see the first associated' do
1774
+ assert_equal ['author_0'], @book_0.authors.map(&:name)
1775
+ end
1776
+
1777
+ should 'not persist changes to the live association' do
1778
+ assert_equal ['author_0', 'author_1'], @book.authors(true).map(&:name)
1779
+ end
1780
+ end
1781
+
1782
+ context 'when reified with option mark_for_destruction' do
1783
+ setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
1784
+
1785
+ should 'mark the newly associated for destruction' do
1786
+ assert @book_0.authors.detect { |a| a.name == 'author_1' }.marked_for_destruction?
1787
+ end
1788
+
1789
+ should 'mark the newly associated-through for destruction' do
1790
+ assert @book_0.authorships.detect { |as| as.person.name == 'author_1' }.marked_for_destruction?
1791
+ end
1792
+ end
1793
+ end
1794
+
1795
+ context 'and then an existing one is associated' do
1796
+ setup do
1797
+ @book.authors << @person_existing
1798
+ end
1799
+
1800
+ context 'when reified' do
1801
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
1802
+
1803
+ should 'only see the first associated' do
1804
+ assert_equal ['author_0'], @book_0.authors.map(&:name)
1805
+ end
1806
+
1807
+ should 'not persist changes to the live association' do
1808
+ assert_equal ['author_0', 'person_existing'], @book.authors(true).map(&:name).sort
1809
+ end
1810
+ end
1811
+
1812
+ context 'when reified with option mark_for_destruction' do
1813
+ setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
1814
+
1815
+ should 'not mark the newly associated for destruction' do
1816
+ assert !@book_0.authors.detect { |a| a.name == 'person_existing' }.marked_for_destruction?
1817
+ end
1818
+
1819
+ should 'mark the newly associated-through for destruction' do
1820
+ assert @book_0.authorships.detect { |as| as.person.name == 'person_existing' }.marked_for_destruction?
1821
+ end
1822
+ end
1823
+ end
1824
+ end
1825
+
1826
+ context 'updated before the associated without paper_trail was created' do
1827
+ setup do
1828
+ @book.update_attributes! :title => 'book_1'
1829
+ @book.editors.create! :name => 'editor_0'
1830
+ end
1831
+
1832
+ context 'when reified' do
1833
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
1834
+
1835
+ should 'see the live association' do
1836
+ assert_equal ['editor_0'], @book_0.editors.map(&:name)
1837
+ end
1838
+ end
1839
+ end
1840
+ end
1841
+ end