Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Index: test/versioned_test.rb
- ===================================================================
- --- test/versioned_test.rb (revision 2511)
- +++ test/versioned_test.rb (working copy)
- @@ -3,8 +3,83 @@
- require File.join(File.dirname(__FILE__), 'fixtures/widget')
- class VersionedTest < Test::Unit::TestCase
- - fixtures :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions
- + fixtures :colors, :color_versions, :widgets, :widget_versions, :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions
- + # Common setup for the saving_new_version_after_rollback tests.
- + def common_setup(instance, version_column, property, value)
- + instance.send(property.to_s+"=", value)
- + assert instance.save!
- + assert_equal value, instance.send(property)
- + assert_equal 2, instance.send(version_column)
- + assert_equal 2, instance.versions.size
- + assert_equal 2, instance.versions.length
- + assert_equal 2, instance.versions.count
- + assert_equal 2, instance.versions(true).size # Forcing reloading of the associated versions.
- + assert_equal 2, instance.versions.length
- + instance
- + end
- +
- + # This test exercises multiple rollbacks and saving of a new version from a state where higher version
- + # numbers exist in the versions table. The Color model uses ActiveRecord's optimistic locking.
- + def test_color_saving_new_version_after_rollback
- + new_name = 'Blue'
- + c = common_setup(colors(:red), :version, :name, new_name)
- +# These assertions would pass before the patching of acts_as_versioned.rb
- +# assert_equal 3, c.versions.count
- +# assert_equal 3, c.versions(true).size # Forcing reloading of the associated versions.
- +# assert_equal 3, c.versions.length
- +
- + assert_equal 1, c.versions[0].version
- + assert_equal 2, c.versions[1].version
- +# This assertion would pass before the patching of acts_as_versioned.rb as we'd have two versions with the same version number.
- +# assert_equal 2, c.versions[2].version
- +
- + # Let's revert back and forth and assert that we have the same version
- + assert_equal new_name, c.name
- + c.revert_to! 1
- + assert_equal 1, c.version
- + c.revert_to! 2
- + assert_equal 2, c.version
- + assert_equal new_name, c.name
- + end
- +
- + # This test exercises multiple rollbacks and saving of a new version from a state where higher version
- + # numbers exist in the versions table. The Widget model does not use ActiveRecord's optimistic locking.
- + def test_widget_saving_new_version_after_rollback
- + new_name = 'Whatchamacallit'
- + w = common_setup(widgets(:thingy), :version, :name, new_name)
- +
- + # Widget versions are sorted in reverse order
- + assert_equal 1, w.versions[1].version
- + assert_equal 2, w.versions[0].version
- +
- + # Let's revert back and forth and assert that we have the same version
- + assert_equal new_name, w.name
- + w.revert_to! 1
- + assert_equal 1, w.version
- + w.revert_to! 2
- + assert_equal 2, w.version
- + assert_equal new_name, w.name
- + end
- +
- + # This test exercises multiple rollbacks and saving of a new version from a state where higher version
- + # numbers exist in the versions table. The LockedPage model uses ActiveRecord's optimistic locking.
- + def test_locked_page_saving_new_version_after_rollback
- + new_title = 'Objects in mirror are probably behind you'
- + lp = common_setup(locked_pages(:things), :lock_version, :title, new_title)
- +
- + assert_equal 1, lp.versions[0].version
- + assert_equal 2, lp.versions[1].version
- +
- + # Let's revert back and forth and assert that we have the same version
- + assert_equal new_title, lp.title
- + lp.revert_to! 1
- + assert_equal 1, lp.lock_version
- + lp.revert_to! 2
- + assert_equal 2, lp.lock_version
- + assert_equal new_title, lp.title
- + end
- +
- def test_saves_versioned_copy
- p = Page.create :title => 'first title', :body => 'first body'
- assert !p.new_record?
- @@ -257,8 +332,8 @@
- Widget.create :name => 'new widget'
- Widget.create :name => 'new widget'
- Widget.create :name => 'new widget'
- - assert_equal 3, Widget.count
- - assert_equal 3, Widget.versioned_class.count
- + assert_equal 4, Widget.count
- + assert_equal 5, Widget.versioned_class.count
- end
- def test_has_many_through
- @@ -286,13 +361,13 @@
- assert_nil options[:dependent]
- assert_equal 'version desc', options[:order]
- assert_equal 'widget_id', options[:foreign_key]
- -
- +
- widget = Widget.create :name => 'new widget'
- + assert_equal 2, Widget.count
- + assert_equal 3, Widget.versioned_class.count
- + widget.destroy
- assert_equal 1, Widget.count
- - assert_equal 1, Widget.versioned_class.count
- - widget.destroy
- - assert_equal 0, Widget.count
- - assert_equal 1, Widget.versioned_class.count
- + assert_equal 3, Widget.versioned_class.count
- end
- def test_versioned_records_should_belong_to_parent
- Index: test/schema.rb
- ===================================================================
- --- test/schema.rb (revision 2511)
- +++ test/schema.rb (working copy)
- @@ -1,4 +1,15 @@
- ActiveRecord::Schema.define(:version => 0) do
- + create_table :colors, :force => true do |t|
- + t.column :lock_version, :integer
- + t.column :name, :string, :limit => 255
- + end
- +
- + create_table :color_versions, :force => true do |t|
- + t.column :color_id, :integer
- + t.column :version, :integer
- + t.column :name, :string, :limit => 255
- + end
- +
- create_table :pages, :force => true do |t|
- t.column :version, :integer
- t.column :title, :string, :limit => 255
- Index: test/fixtures/color.rb
- ===================================================================
- --- test/fixtures/color.rb (revision 0)
- +++ test/fixtures/color.rb (revision 0)
- @@ -0,0 +1,7 @@
- +class Color < ActiveRecord::Base
- + acts_as_versioned :version_column => :lock_version
- +
- + def version
- + lock_version
- + end
- +end
- Index: test/fixtures/locked_pages.yml
- ===================================================================
- --- test/fixtures/locked_pages.yml (revision 2511)
- +++ test/fixtures/locked_pages.yml (working copy)
- @@ -8,3 +8,8 @@
- title: So I was thinking
- lock_version: 24
- type: SpecialLockedPage
- +things:
- + id: 3
- + title: The best things in life are free
- + lock_version: 1
- + type: LockedPage
- \ No newline at end of file
- Index: test/fixtures/locked_pages_revisions.yml
- ===================================================================
- --- test/fixtures/locked_pages_revisions.yml (revision 2511)
- +++ test/fixtures/locked_pages_revisions.yml (working copy)
- @@ -25,3 +25,17 @@
- title: So I was thinking
- version: 24
- version_type: SpecialLockedPage
- +
- +things_1:
- + id: 5
- + page_id: 3
- + title: The best things in life are free
- + version: 1
- + version_type: LockedPage
- +
- +things_2:
- + id: 6
- + page_id: 3
- + title: Do not try this at home
- + version: 2
- + version_type: LockedPage
- Index: test/fixtures/colors.yml
- ===================================================================
- --- test/fixtures/colors.yml (revision 0)
- +++ test/fixtures/colors.yml (revision 0)
- @@ -0,0 +1,4 @@
- +red:
- + id: 1
- + name: Red
- + lock_version: 1
- Index: test/fixtures/widgets.yml
- ===================================================================
- --- test/fixtures/widgets.yml (revision 0)
- +++ test/fixtures/widgets.yml (revision 0)
- @@ -0,0 +1,5 @@
- +thingy:
- + id: 1
- + name: Doohickey
- + foo: bar
- + version: 1
- Index: test/fixtures/color_versions.yml
- ===================================================================
- --- test/fixtures/color_versions.yml (revision 0)
- +++ test/fixtures/color_versions.yml (revision 0)
- @@ -0,0 +1,10 @@
- +red_1:
- + id: 1
- + color_id: 1
- + name: Red
- + version: 1
- +red_2:
- + id: 2
- + color_id: 1
- + name: Green
- + version: 2
- Index: test/fixtures/widget_versions.yml
- ===================================================================
- --- test/fixtures/widget_versions.yml (revision 0)
- +++ test/fixtures/widget_versions.yml (revision 0)
- @@ -0,0 +1,10 @@
- +thingy_1:
- + id: 1
- + widget_id: 1
- + name: Doohickey
- + version: 1
- +thingy_2:
- + id: 2
- + widget_id: 1
- + name: Thingamabob
- + version: 2
- Index: lib/acts_as_versioned.rb
- ===================================================================
- --- lib/acts_as_versioned.rb (revision 2511)
- +++ lib/acts_as_versioned.rb (working copy)
- @@ -245,6 +245,7 @@
- # Saves a version of the model in the versioned table. This is called in the after_save callback by default
- def save_version_on_create
- + clear_obsolete_higher_versions
- rev = self.class.versioned_class.new
- self.clone_versioned_model(self, rev)
- rev.version = send(self.class.version_column)
- @@ -252,6 +253,37 @@
- rev.save
- end
- + # Clears obsolete higher versions. When using ActiveRecord's optimistic locking the versions table
- + # can end up with multiple records with the same version number if performing a revert_to to a previous
- + # version and then saving a new version. Clearing out higher versions before saving a new one will
- + # prevent this from happening.
- + #
- + # NOTE: This may break backwards compatibility if someone was relying on this feature. If this fix cannot
- + # be accepted, please consider changing the query to delete only the version with a version number equal
- + # to the current one. I.e. change ">=" to "=" in the SQL query.
- + #
- + # Here's an example.
- + #
- + # Current behavior with optimistic locking: When reverting back one or more versions and then saving a new
- + # version the end result will be that there will be two records with the same version number in the versions
- + # table. From this state, reverting back one version and forward one version might not be a deterministic
- + # operation depending on the database and sorting used as one or the other of the two records with the
- + # same version number might be selected for the new version.
- + #
- + # Current behavior without optimistic locking: When reverting back one or more versions and then saving a
- + # new version the end result will be that a new version with a version number one higher than the maximum
- + # version number in the versions table will be created. In this case, there will be no duplicate version
- + # numbers in the versions table, but the pre-save and post-save version numbers of the model being saved
- + # will differ by more than 1, i.e. a jump in version number will occur.
- + #
- + # Patched behavior with or without optimistic locking: When reverting back one or more versions and then
- + # saving a new version the end result will be that the new version will have a version number one higher
- + # than the previous version number and higher obsolete versions are gone.
- + def clear_obsolete_higher_versions
- + sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version >= #{send(self.class.version_column)} AND #{self.class.versioned_foreign_key} = #{self.id}"
- + self.class.versioned_class.connection.execute sql
- + end
- +
- # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
- # Override this method to set your own criteria for clearing old versions.
- def clear_old_versions
- @@ -383,9 +415,19 @@
- end
- # Gets the next available version for the current record, or 1 for a new record
- + #
- + # The next available version is calculated as the lesser of the next available version in the model
- + # and the next available version in the versions table. The addition of checking the model and not
- + # only the versions table was made so that the proper version number would get calculated when using
- + # ActiveRecord's optimistic locking.
- + #
- + # NOTE: See the note in the clear_obsolete_versions methods. Either both this and that method needs
- + # to change, or none of them (but then there'd still be a defect).
- def next_version
- return 1 if new_record?
- - (versions.calculate(:max, :version) || 0) + 1
- + model_version = self.send("#{self.class.version_column}") + 1
- + versions_version = (versions.calculate(:max, :version) || 0) + 1
- + model_version < versions_version ? model_version : versions_version
- end
- # clears current changed attributes. Called after save.
Add Comment
Please, Sign In to add comment