Guest User

Untitled

a guest
Feb 21st, 2018
59
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.63 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. @@ -3,8 +3,83 @@
  6. require File.join(File.dirname(__FILE__), 'fixtures/widget')
  7.  
  8. class VersionedTest < Test::Unit::TestCase
  9. - fixtures :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions
  10. + fixtures :colors, :color_versions, :widgets, :widget_versions, :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions
  11.  
  12. + # Common setup for the saving_new_version_after_rollback tests.
  13. + def common_setup(instance, version_column, property, value)
  14. + instance.send(property.to_s+"=", value)
  15. + assert instance.save!
  16. + assert_equal value, instance.send(property)
  17. + assert_equal 2, instance.send(version_column)
  18. + assert_equal 2, instance.versions.size
  19. + assert_equal 2, instance.versions.length
  20. + assert_equal 2, instance.versions.count
  21. + assert_equal 2, instance.versions(true).size # Forcing reloading of the associated versions.
  22. + assert_equal 2, instance.versions.length
  23. + instance
  24. + end
  25. +
  26. + # This test exercises multiple rollbacks and saving of a new version from a state where higher version
  27. + # numbers exist in the versions table. The Color model uses ActiveRecord's optimistic locking.
  28. + def test_color_saving_new_version_after_rollback
  29. + new_name = 'Blue'
  30. + c = common_setup(colors(:red), :version, :name, new_name)
  31. +# These assertions would pass before the patching of acts_as_versioned.rb
  32. +# assert_equal 3, c.versions.count
  33. +# assert_equal 3, c.versions(true).size # Forcing reloading of the associated versions.
  34. +# assert_equal 3, c.versions.length
  35. +
  36. + assert_equal 1, c.versions[0].version
  37. + assert_equal 2, c.versions[1].version
  38. +# This assertion would pass before the patching of acts_as_versioned.rb as we'd have two versions with the same version number.
  39. +# assert_equal 2, c.versions[2].version
  40. +
  41. + # Let's revert back and forth and assert that we have the same version
  42. + assert_equal new_name, c.name
  43. + c.revert_to! 1
  44. + assert_equal 1, c.version
  45. + c.revert_to! 2
  46. + assert_equal 2, c.version
  47. + assert_equal new_name, c.name
  48. + end
  49. +
  50. + # This test exercises multiple rollbacks and saving of a new version from a state where higher version
  51. + # numbers exist in the versions table. The Widget model does not use ActiveRecord's optimistic locking.
  52. + def test_widget_saving_new_version_after_rollback
  53. + new_name = 'Whatchamacallit'
  54. + w = common_setup(widgets(:thingy), :version, :name, new_name)
  55. +
  56. + # Widget versions are sorted in reverse order
  57. + assert_equal 1, w.versions[1].version
  58. + assert_equal 2, w.versions[0].version
  59. +
  60. + # Let's revert back and forth and assert that we have the same version
  61. + assert_equal new_name, w.name
  62. + w.revert_to! 1
  63. + assert_equal 1, w.version
  64. + w.revert_to! 2
  65. + assert_equal 2, w.version
  66. + assert_equal new_name, w.name
  67. + end
  68. +
  69. + # This test exercises multiple rollbacks and saving of a new version from a state where higher version
  70. + # numbers exist in the versions table. The LockedPage model uses ActiveRecord's optimistic locking.
  71. + def test_locked_page_saving_new_version_after_rollback
  72. + new_title = 'Objects in mirror are probably behind you'
  73. + lp = common_setup(locked_pages(:things), :lock_version, :title, new_title)
  74. +
  75. + assert_equal 1, lp.versions[0].version
  76. + assert_equal 2, lp.versions[1].version
  77. +
  78. + # Let's revert back and forth and assert that we have the same version
  79. + assert_equal new_title, lp.title
  80. + lp.revert_to! 1
  81. + assert_equal 1, lp.lock_version
  82. + lp.revert_to! 2
  83. + assert_equal 2, lp.lock_version
  84. + assert_equal new_title, lp.title
  85. + end
  86. +
  87. def test_saves_versioned_copy
  88. p = Page.create :title => 'first title', :body => 'first body'
  89. assert !p.new_record?
  90. @@ -257,8 +332,8 @@
  91. Widget.create :name => 'new widget'
  92. Widget.create :name => 'new widget'
  93. Widget.create :name => 'new widget'
  94. - assert_equal 3, Widget.count
  95. - assert_equal 3, Widget.versioned_class.count
  96. + assert_equal 4, Widget.count
  97. + assert_equal 5, Widget.versioned_class.count
  98. end
  99.  
  100. def test_has_many_through
  101. @@ -286,13 +361,13 @@
  102. assert_nil options[:dependent]
  103. assert_equal 'version desc', options[:order]
  104. assert_equal 'widget_id', options[:foreign_key]
  105. -
  106. +
  107. widget = Widget.create :name => 'new widget'
  108. + assert_equal 2, Widget.count
  109. + assert_equal 3, Widget.versioned_class.count
  110. + widget.destroy
  111. assert_equal 1, Widget.count
  112. - assert_equal 1, Widget.versioned_class.count
  113. - widget.destroy
  114. - assert_equal 0, Widget.count
  115. - assert_equal 1, Widget.versioned_class.count
  116. + assert_equal 3, Widget.versioned_class.count
  117. end
  118.  
  119. def test_versioned_records_should_belong_to_parent
  120. Index: test/schema.rb
  121. ===================================================================
  122. --- test/schema.rb (revision 2511)
  123. +++ test/schema.rb (working copy)
  124. @@ -1,4 +1,15 @@
  125. ActiveRecord::Schema.define(:version => 0) do
  126. + create_table :colors, :force => true do |t|
  127. + t.column :lock_version, :integer
  128. + t.column :name, :string, :limit => 255
  129. + end
  130. +
  131. + create_table :color_versions, :force => true do |t|
  132. + t.column :color_id, :integer
  133. + t.column :version, :integer
  134. + t.column :name, :string, :limit => 255
  135. + end
  136. +
  137. create_table :pages, :force => true do |t|
  138. t.column :version, :integer
  139. t.column :title, :string, :limit => 255
  140. Index: test/fixtures/color.rb
  141. ===================================================================
  142. --- test/fixtures/color.rb (revision 0)
  143. +++ test/fixtures/color.rb (revision 0)
  144. @@ -0,0 +1,7 @@
  145. +class Color < ActiveRecord::Base
  146. + acts_as_versioned :version_column => :lock_version
  147. +
  148. + def version
  149. + lock_version
  150. + end
  151. +end
  152. Index: test/fixtures/locked_pages.yml
  153. ===================================================================
  154. --- test/fixtures/locked_pages.yml (revision 2511)
  155. +++ test/fixtures/locked_pages.yml (working copy)
  156. @@ -8,3 +8,8 @@
  157. title: So I was thinking
  158. lock_version: 24
  159. type: SpecialLockedPage
  160. +things:
  161. + id: 3
  162. + title: The best things in life are free
  163. + lock_version: 1
  164. + type: LockedPage
  165. \ No newline at end of file
  166. Index: test/fixtures/locked_pages_revisions.yml
  167. ===================================================================
  168. --- test/fixtures/locked_pages_revisions.yml (revision 2511)
  169. +++ test/fixtures/locked_pages_revisions.yml (working copy)
  170. @@ -25,3 +25,17 @@
  171. title: So I was thinking
  172. version: 24
  173. version_type: SpecialLockedPage
  174. +
  175. +things_1:
  176. + id: 5
  177. + page_id: 3
  178. + title: The best things in life are free
  179. + version: 1
  180. + version_type: LockedPage
  181. +
  182. +things_2:
  183. + id: 6
  184. + page_id: 3
  185. + title: Do not try this at home
  186. + version: 2
  187. + version_type: LockedPage
  188. Index: test/fixtures/colors.yml
  189. ===================================================================
  190. --- test/fixtures/colors.yml (revision 0)
  191. +++ test/fixtures/colors.yml (revision 0)
  192. @@ -0,0 +1,4 @@
  193. +red:
  194. + id: 1
  195. + name: Red
  196. + lock_version: 1
  197. Index: test/fixtures/widgets.yml
  198. ===================================================================
  199. --- test/fixtures/widgets.yml (revision 0)
  200. +++ test/fixtures/widgets.yml (revision 0)
  201. @@ -0,0 +1,5 @@
  202. +thingy:
  203. + id: 1
  204. + name: Doohickey
  205. + foo: bar
  206. + version: 1
  207. Index: test/fixtures/color_versions.yml
  208. ===================================================================
  209. --- test/fixtures/color_versions.yml (revision 0)
  210. +++ test/fixtures/color_versions.yml (revision 0)
  211. @@ -0,0 +1,10 @@
  212. +red_1:
  213. + id: 1
  214. + color_id: 1
  215. + name: Red
  216. + version: 1
  217. +red_2:
  218. + id: 2
  219. + color_id: 1
  220. + name: Green
  221. + version: 2
  222. Index: test/fixtures/widget_versions.yml
  223. ===================================================================
  224. --- test/fixtures/widget_versions.yml (revision 0)
  225. +++ test/fixtures/widget_versions.yml (revision 0)
  226. @@ -0,0 +1,10 @@
  227. +thingy_1:
  228. + id: 1
  229. + widget_id: 1
  230. + name: Doohickey
  231. + version: 1
  232. +thingy_2:
  233. + id: 2
  234. + widget_id: 1
  235. + name: Thingamabob
  236. + version: 2
  237. Index: lib/acts_as_versioned.rb
  238. ===================================================================
  239. --- lib/acts_as_versioned.rb (revision 2511)
  240. +++ lib/acts_as_versioned.rb (working copy)
  241. @@ -245,6 +245,7 @@
  242.  
  243. # Saves a version of the model in the versioned table. This is called in the after_save callback by default
  244. def save_version_on_create
  245. + clear_obsolete_higher_versions
  246. rev = self.class.versioned_class.new
  247. self.clone_versioned_model(self, rev)
  248. rev.version = send(self.class.version_column)
  249. @@ -252,6 +253,37 @@
  250. rev.save
  251. end
  252.  
  253. + # Clears obsolete higher versions. When using ActiveRecord's optimistic locking the versions table
  254. + # can end up with multiple records with the same version number if performing a revert_to to a previous
  255. + # version and then saving a new version. Clearing out higher versions before saving a new one will
  256. + # prevent this from happening.
  257. + #
  258. + # NOTE: This may break backwards compatibility if someone was relying on this feature. If this fix cannot
  259. + # be accepted, please consider changing the query to delete only the version with a version number equal
  260. + # to the current one. I.e. change ">=" to "=" in the SQL query.
  261. + #
  262. + # Here's an example.
  263. + #
  264. + # Current behavior with optimistic locking: When reverting back one or more versions and then saving a new
  265. + # version the end result will be that there will be two records with the same version number in the versions
  266. + # table. From this state, reverting back one version and forward one version might not be a deterministic
  267. + # operation depending on the database and sorting used as one or the other of the two records with the
  268. + # same version number might be selected for the new version.
  269. + #
  270. + # Current behavior without optimistic locking: When reverting back one or more versions and then saving a
  271. + # new version the end result will be that a new version with a version number one higher than the maximum
  272. + # version number in the versions table will be created. In this case, there will be no duplicate version
  273. + # numbers in the versions table, but the pre-save and post-save version numbers of the model being saved
  274. + # will differ by more than 1, i.e. a jump in version number will occur.
  275. + #
  276. + # Patched behavior with or without optimistic locking: When reverting back one or more versions and then
  277. + # saving a new version the end result will be that the new version will have a version number one higher
  278. + # than the previous version number and higher obsolete versions are gone.
  279. + def clear_obsolete_higher_versions
  280. + sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version >= #{send(self.class.version_column)} AND #{self.class.versioned_foreign_key} = #{self.id}"
  281. + self.class.versioned_class.connection.execute sql
  282. + end
  283. +
  284. # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
  285. # Override this method to set your own criteria for clearing old versions.
  286. def clear_old_versions
  287. @@ -383,9 +415,19 @@
  288. end
  289.  
  290. # Gets the next available version for the current record, or 1 for a new record
  291. + #
  292. + # The next available version is calculated as the lesser of the next available version in the model
  293. + # and the next available version in the versions table. The addition of checking the model and not
  294. + # only the versions table was made so that the proper version number would get calculated when using
  295. + # ActiveRecord's optimistic locking.
  296. + #
  297. + # NOTE: See the note in the clear_obsolete_versions methods. Either both this and that method needs
  298. + # to change, or none of them (but then there'd still be a defect).
  299. def next_version
  300. return 1 if new_record?
  301. - (versions.calculate(:max, :version) || 0) + 1
  302. + model_version = self.send("#{self.class.version_column}") + 1
  303. + versions_version = (versions.calculate(:max, :version) || 0) + 1
  304. + model_version < versions_version ? model_version : versions_version
  305. end
  306.  
  307. # clears current changed attributes. Called after save.
Add Comment
Please, Sign In to add comment