paper_trail 3.0.9 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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