Guest User

Untitled

a guest
Feb 21st, 2018
67
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.95 KB | None | 0 0
  1. Index: test/versioned_test.rb
  2. ===================================================================
  3. --- test/versioned_test.rb (revision 2511)
  4. +++ test/versioned_test.rb (working copy)
  5. @@ -5,6 +5,137 @@
  6. class VersionedTest < Test::Unit::TestCase
  7. fixtures :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions
  8.  
  9. + # Common setup for the multiple_rollbacks_and_saving_of_new_version tests.
  10. + #
  11. + # NOTE: This could probaly be DRYed up by removing the first five of the six blocks of code in this method
  12. + # and adding the relevant data into fixtures instead.
  13. + def common_setup(klass, version_column, property, values)
  14. + # Create a new model.
  15. + m = klass.create property => values[:first]
  16. + assert_equal values[:first], m.send(property)
  17. + assert_equal 1, m.send(version_column)
  18. + assert_equal 1, m.versions.size
  19. + assert_equal 1, m.versions.length
  20. + assert_equal 1, m.versions.count
  21. +
  22. + # Update the name of the model.
  23. + m.send(property.to_s+"=", values[:second])
  24. + assert m.save!
  25. + assert_equal values[:second], m.send(property)
  26. + assert_equal 2, m.send(version_column)
  27. + assert_equal 1, m.versions.size
  28. + assert_equal 1, m.versions.length
  29. + assert_equal 2, m.versions.count
  30. + assert_equal 2, m.versions(true).size # Forcing reloading of the associated versions.
  31. + assert_equal 2, m.versions.length
  32. +
  33. + # Roll back to the original version.
  34. + assert m.revert_to! 1
  35. + assert_equal values[:first], m.send(property)
  36. + assert_equal 1, m.send(version_column)
  37. + assert_equal 2, m.versions.size
  38. + assert_equal 2, m.versions.length
  39. + assert_equal 2, m.versions.count
  40. + assert_equal 2, m.versions(true).size # Forcing reloading of the associated versions.
  41. + assert_equal 2, m.versions.length
  42. +
  43. + # Roll forward to the updated version.
  44. + assert m.revert_to! 2
  45. + assert_equal values[:second], m.send(property)
  46. + assert_equal 2, m.send(version_column)
  47. + assert_equal 2, m.versions.size
  48. + assert_equal 2, m.versions.length
  49. + assert_equal 2, m.versions.count
  50. + assert_equal 2, m.versions(true).size # Forcing reloading of the associated versions.
  51. + assert_equal 2, m.versions.length
  52. +
  53. + # Roll back to the original version.
  54. + assert m.revert_to! 1
  55. + assert_equal values[:first], m.send(property)
  56. + assert_equal 1, m.send(version_column)
  57. + assert_equal 2, m.versions.size
  58. + assert_equal 2, m.versions.length
  59. + assert_equal 2, m.versions.count
  60. + assert_equal 2, m.versions(true).size # Forcing reloading of the associated versions.
  61. + assert_equal 2, m.versions.length
  62. +
  63. + # Update the name of the model.
  64. + m.send(property.to_s+"=", values[:third])
  65. + assert m.save!
  66. + assert_equal values[:third], m.send(property)
  67. + assert_equal 2, m.send(version_column)
  68. + assert_equal 2, m.versions.size
  69. + assert_equal 2, m.versions.length
  70. + assert_equal 2, m.versions.count
  71. + assert_equal 2, m.versions(true).size # Forcing reloading of the associated versions.
  72. + assert_equal 2, m.versions.length
  73. +
  74. + m
  75. + end
  76. +
  77. + # This test exercises multiple rollbacks and saving of a new version from a state where higher version
  78. + # numbers exist in the versions table. The Color model uses ActiveRecord's optimistic locking.
  79. + def test_color_multiple_rollbacks_and_saving_of_new_version
  80. + values = {:first => 'Red', :second =>'Green', :third => 'Blue'}
  81. + c = common_setup(Color, :version, :name, values)
  82. +# These assertions would pass before the patching of acts_as_versioned.rb
  83. +# assert_equal 3, c.versions.count
  84. +# assert_equal 3, c.versions(true).size # Forcing reloading of the associated versions.
  85. +# assert_equal 3, c.versions.length
  86. +
  87. + assert_equal 1, c.versions[0].version
  88. + assert_equal 2, c.versions[1].version
  89. +# This assertion would pass before the patching of acts_as_versioned.rb as we'd have two versions with the same version number.
  90. +# assert_equal 2, c.versions[2].version
  91. +
  92. + # Let's revert back and forth and assert that we have the same version
  93. + assert_equal values[:third], c.name
  94. + c.revert_to! 1
  95. + assert_equal 1, c.version
  96. + c.revert_to! 2
  97. + assert_equal 2, c.version
  98. + assert_equal values[:third], c.name
  99. + end
  100. +
  101. + # This test exercises multiple rollbacks and saving of a new version from a state where higher version
  102. + # numbers exist in the versions table. The Widget model does not use ActiveRecord's optimistic locking.
  103. + def test_widget_multiple_rollbacks_and_saving_of_new_version
  104. + values = {:first => 'Doohickey', :second =>'Thingamabob', :third => 'Whatchamacallit'}
  105. + w = common_setup(Widget, :version, :name, values)
  106. +
  107. + # Widget versions are sorted in reverse order
  108. + assert_equal 1, w.versions[1].version
  109. + assert_equal 2, w.versions[0].version
  110. +
  111. + # Let's revert back and forth and assert that we have the same version
  112. + assert_equal values[:third], w.name
  113. + w.revert_to! 1
  114. + assert_equal 1, w.version
  115. + w.revert_to! 2
  116. + assert_equal 2, w.version
  117. + assert_equal values[:third], w.name
  118. + end
  119. +
  120. + # This test exercises multiple rollbacks and saving of a new version from a state where higher version
  121. + # numbers exist in the versions table. The LockedPage model uses ActiveRecord's optimistic locking.
  122. + def test_locked_page_multiple_rollbacks_and_saving_of_new_version
  123. + values = {:first => 'The best things in life are free',
  124. + :second =>'Do not try this at home',
  125. + :third => 'Objects in mirror are probably behind you'}
  126. + lp = common_setup(LockedPage, :lock_version, :title, values)
  127. +
  128. + assert_equal 1, lp.versions[0].version
  129. + assert_equal 2, lp.versions[1].version
  130. +
  131. + # Let's revert back and forth and assert that we have the same version
  132. + assert_equal values[:third], lp.title
  133. + lp.revert_to! 1
  134. + assert_equal 1, lp.lock_version
  135. + lp.revert_to! 2
  136. + assert_equal 2, lp.lock_version
  137. + assert_equal values[:third], lp.title
  138. + end
  139. +
  140. def test_saves_versioned_copy
  141. p = Page.create :title => 'first title', :body => 'first body'
  142. assert !p.new_record?
  143. Index: test/schema.rb
  144. ===================================================================
  145. --- test/schema.rb (revision 2511)
  146. +++ test/schema.rb (working copy)
  147. @@ -1,4 +1,15 @@
  148. ActiveRecord::Schema.define(:version => 0) do
  149. + create_table :colors, :force => true do |t|
  150. + t.column :lock_version, :integer
  151. + t.column :name, :string, :limit => 255
  152. + end
  153. +
  154. + create_table :color_versions, :force => true do |t|
  155. + t.column :color_id, :integer
  156. + t.column :version, :integer
  157. + t.column :name, :string, :limit => 255
  158. + end
  159. +
  160. create_table :pages, :force => true do |t|
  161. t.column :version, :integer
  162. t.column :title, :string, :limit => 255
  163. Index: test/fixtures/color.rb
  164. ===================================================================
  165. --- test/fixtures/color.rb (revision 0)
  166. +++ test/fixtures/color.rb (revision 0)
  167. @@ -0,0 +1,7 @@
  168. +class Color < ActiveRecord::Base
  169. + acts_as_versioned :version_column => :lock_version
  170. +
  171. + def version
  172. + lock_version
  173. + end
  174. +end
  175. Index: lib/acts_as_versioned.rb
  176. ===================================================================
  177. --- lib/acts_as_versioned.rb (revision 2511)
  178. +++ lib/acts_as_versioned.rb (working copy)
  179. @@ -245,6 +245,7 @@
  180.  
  181. # Saves a version of the model in the versioned table. This is called in the after_save callback by default
  182. def save_version_on_create
  183. + clear_obsolete_higher_versions
  184. rev = self.class.versioned_class.new
  185. self.clone_versioned_model(self, rev)
  186. rev.version = send(self.class.version_column)
  187. @@ -252,6 +253,42 @@
  188. rev.save
  189. end
  190.  
  191. + # Clears obsolete higher versions. When using ActiveRecord's optimistic locking the versions table
  192. + # can end up with multiple records with the same version number if performing a revert_to to a previous
  193. + # version and then saving a new version. Clearing out higher versions before saving a new one will
  194. + # prevent this from happening.
  195. + #
  196. + # NOTE: I noticed that the first time this query is run for a new record, the query reads:
  197. + # DELETE FROM thing_versions WHERE version >= 1 AND thing_id = 1
  198. + # This could potentially have bad consequences if someone has a Thing model, couldn't it? I don't see it
  199. + # in the source code of acts_as_versioned so I guess it is an ActiveRecord default?
  200. + #
  201. + # NOTE: This may break backwards compatibility if someone was relying on this feature. If this fix cannot
  202. + # be accepted, please consider changing the query to delete only the version with a version number equal
  203. + # to the current one. I.e. change ">=" to "=" in the SQL query.
  204. + #
  205. + # Here's an example.
  206. + #
  207. + # Current behavior with optimistic locking: When reverting back one or more versions and then saving a new
  208. + # version the end result will be that there will be two records with the same version number in the versions
  209. + # table. From this state, reverting back one version and forward one version might not be a deterministic
  210. + # operation depending on the database and sorting used as one or the other of the two records with the
  211. + # same version number might be selected for the new version.
  212. + #
  213. + # Current behavior without optimistic locking: When reverting back one or more versions and then saving a
  214. + # new version the end result will be that a new version with a version number one higher than the maximum
  215. + # version number in the versions table will be created. In this case, there will be no duplicate version
  216. + # numbers in the versions table, but the pre-save and post-save version numbers of the model being saved
  217. + # will differ by more than 1, i.e. a jump in version numbers will occur.
  218. + #
  219. + # Patched behavior with or without optimistic locking: When reverting back one or more versions and then
  220. + # saving a new version the end result will be that the new version will have a version number one higher
  221. + # than the previous version number and higher obsolete versions are gone.
  222. + def clear_obsolete_higher_versions
  223. + sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version >= #{send(self.class.version_column)} AND #{self.class.versioned_foreign_key} = #{self.id}"
  224. + self.class.versioned_class.connection.execute sql
  225. + end
  226. +
  227. # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
  228. # Override this method to set your own criteria for clearing old versions.
  229. def clear_old_versions
  230. @@ -383,9 +420,19 @@
  231. end
  232.  
  233. # Gets the next available version for the current record, or 1 for a new record
  234. + #
  235. + # The next available version is calculated as the lesser of the next available version in the model
  236. + # and the next available version in the versions table. The addition of checking the model and not
  237. + # only the versions table was made so that the proper version number would get calculated when using
  238. + # ActiveRecord's optimistic locking.
  239. + #
  240. + # NOTE: See the note in the clear_obsolete_versions methods. Either both this and that method needs
  241. + # to change, or none of them (but then there'd still be a defect).
  242. def next_version
  243. return 1 if new_record?
  244. - (versions.calculate(:max, :version) || 0) + 1
  245. + model_version = self.send("#{self.class.version_column}") + 1
  246. + versions_version = (versions.calculate(:max, :version) || 0) + 1
  247. + model_version < versions_version ? model_version : versions_version
  248. end
  249.  
  250. # clears current changed attributes. Called after save.
Add Comment
Please, Sign In to add comment