has_versions 0.3.0 → 0.4.0

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 (42) hide show
  1. data/.gitignore +1 -37
  2. data/Gemfile.lock +17 -45
  3. data/Rakefile +16 -16
  4. data/has_versions.gemspec +6 -6
  5. data/lib/has_versions/apply/simple.rb +1 -1
  6. data/lib/has_versions/attributes.rb +2 -26
  7. data/lib/has_versions/configuration.rb +3 -16
  8. data/lib/has_versions/diff/simple.rb +1 -1
  9. data/lib/has_versions/merge/always_conflicted.rb +9 -0
  10. data/lib/has_versions/merge/base.rb +22 -0
  11. data/lib/has_versions/merge/choose_first.rb +9 -0
  12. data/lib/has_versions/merge/diff3.rb +66 -0
  13. data/lib/has_versions/merge/fast_forward.rb +34 -0
  14. data/lib/has_versions/merge/merge_base.rb +75 -0
  15. data/lib/has_versions/merge/octopus.rb +42 -0
  16. data/lib/has_versions/merge/three_way.rb +53 -0
  17. data/lib/has_versions/merge.rb +16 -45
  18. data/lib/has_versions/orm/cassandra_object.rb +15 -0
  19. data/lib/has_versions/reset/simple.rb +2 -2
  20. data/lib/has_versions/version.rb +1 -1
  21. data/lib/has_versions/versioned.rb +1 -5
  22. data/lib/has_versions/versioning_key.rb +0 -1
  23. data/lib/has_versions.rb +10 -0
  24. data/spec/has_versions/apply_spec.rb +5 -5
  25. data/spec/has_versions/configuration_spec.rb +7 -7
  26. data/spec/has_versions/diff_spec.rb +4 -4
  27. data/spec/has_versions/merge/diff3_spec.rb +29 -0
  28. data/spec/has_versions/merge/merge_base_spec.rb +61 -0
  29. data/spec/has_versions/merge/octopus_spec.rb +74 -0
  30. data/spec/has_versions/merge/three_way_spec.rb +57 -0
  31. data/spec/has_versions/reset_spec.rb +1 -2
  32. data/spec/has_versions/stage_spec.rb +14 -15
  33. data/spec/has_versions/versioned_spec.rb +4 -4
  34. data/spec/has_versions/versioning_key_spec.rb +1 -1
  35. data/spec/spec_helper.rb +7 -8
  36. data/spec/support/matchers/be_a_uuid.rb +0 -1
  37. data/spec/support/version.rb +10 -0
  38. metadata +34 -69
  39. data/lib/has_versions/merge/stupid.rb +0 -44
  40. data/lib/has_versions/stage.rb +0 -121
  41. data/spec/has_versions/merge_spec.rb +0 -67
  42. data/spec/support/matchers/have_key.rb +0 -25
@@ -7,8 +7,8 @@ module HasVersions
7
7
 
8
8
  module Simple
9
9
  def reset!(version)
10
- versioning_configuration.attributes.each do |name, options|
11
- value = versioning_decode_value(version.snapshot[name], options)
10
+ versioning_configuration.attributes.each do |name|
11
+ value = versioning_decode_value(name, version.snapshot[name])
12
12
  __send__("#{name}=", value)
13
13
  end
14
14
  self
@@ -1,4 +1,4 @@
1
1
  module HasVersions
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
4
4
 
@@ -1,5 +1,4 @@
1
1
  module HasVersions
2
-
3
2
  module Versioned
4
3
  extend ActiveSupport::Concern
5
4
 
@@ -12,7 +11,6 @@ module HasVersions
12
11
 
13
12
  module ClassMethods
14
13
  def has_versions(version_class, &block)
15
-
16
14
  self.versioning_configuration = HasVersions::Configuration.new(&block)
17
15
  self.version_class = version_class
18
16
  end
@@ -24,11 +22,9 @@ module HasVersions
24
22
 
25
23
  # generates a version from the current object
26
24
  def to_version
27
- self.class.version_class.new.tap do |version|
25
+ version_class.new.tap do |version|
28
26
  version.snapshot = versioned_attributes
29
27
  end
30
28
  end
31
-
32
29
  end
33
-
34
30
  end
@@ -5,4 +5,3 @@ module HasVersions
5
5
  autoload :UUID
6
6
  end
7
7
  end
8
-
data/lib/has_versions.rb CHANGED
@@ -17,5 +17,15 @@ module HasVersions
17
17
  autoload :Reset
18
18
  autoload :VersioningKey
19
19
 
20
+ module Orm
21
+ extend ActiveSupport::Autoload
22
+
23
+ autoload :CassandraObject
24
+ end
25
+
20
26
  class VersioningError < StandardError; end
21
27
  end
28
+
29
+ ActiveSupport.on_load :cassandra_object do
30
+ include HasVersions::Orm::CassandraObject
31
+ end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class ApplyingVersion < Version; end
4
4
 
@@ -15,7 +15,7 @@ describe ApplyingVersion do
15
15
  let (:patch3) { {name: ['foo2', 'foo3']}.with_indifferent_access }
16
16
 
17
17
  it 'should patch the snapshot' do
18
- foo1.apply(patch1).snapshot.should have_key(:name).with_value('foo2')
18
+ foo1.apply(patch1).snapshot.should include(name: 'foo2')
19
19
  end
20
20
 
21
21
  it 'should raise on non-applying patch' do
@@ -23,11 +23,11 @@ describe ApplyingVersion do
23
23
  end
24
24
 
25
25
  it 'should apply patches adding attributes', :focus => true do
26
- foo2.apply(patch2).snapshot.should have_key(:name).with_value('foo1')
27
- foo2.apply(patch2).snapshot.should have_key(:stripey).with_value('socks')
26
+ foo2.apply(patch2).snapshot.should include(name: 'foo1')
27
+ foo2.apply(patch2).snapshot.should include(stripey: 'socks')
28
28
  end
29
29
 
30
30
  it 'should apply multiple patches' do
31
- foo1.apply(patch1, patch3).snapshot.should have_key(:name).with_value('foo3')
31
+ foo1.apply(patch1, patch3).snapshot.should include(name: 'foo3')
32
32
  end
33
33
  end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe 'Configuration' do
4
4
  subject do
@@ -11,23 +11,23 @@ describe 'Configuration' do
11
11
  end
12
12
 
13
13
  it 'should include specified attributes' do
14
- subject.attributes.should have_key(:foo)
15
- subject.attributes.should have_key(:bar)
14
+ subject.attributes.should include(:foo)
15
+ subject.attributes.should include(:bar)
16
16
  end
17
17
 
18
18
  it 'should set options on attributes' do
19
- subject.attributes[:baz].should have_key(:type).with_value(:baz)
19
+ subject.attributes[:baz].should include(type: :baz)
20
20
  end
21
21
 
22
22
  it 'should allow indifferent access to attributes' do
23
- subject.attributes.should have_key("foo")
23
+ subject.attributes.should include("foo")
24
24
  end
25
25
 
26
26
  it 'should include specified types' do
27
- subject.types.should have_key(:baz)
27
+ subject.types.should include(:baz)
28
28
  end
29
29
 
30
30
  it 'should propagate type options to typed attributes' do
31
- subject.attributes[:baz].should have_key(:expected).with_value(Hash)
31
+ subject.attributes[:baz].should include(expected: Hash)
32
32
  end
33
33
  end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class DiffingVersion < Version; end
4
4
 
@@ -15,14 +15,14 @@ describe DiffingVersion do
15
15
  end
16
16
 
17
17
  it 'should return a patch when diffed with another version' do
18
- foo.diff(foo2).should have_key(:name).with_value(['foo', 'foo2'])
18
+ foo.diff(foo2).should include(name: ['foo', 'foo2'])
19
19
  end
20
20
 
21
21
  it 'should include patches for attributes added' do
22
- foo.diff(foo2).should have_key(:stripey).with_value([nil, 'socks'])
22
+ foo.diff(foo2).should include(stripey: [nil, 'socks'])
23
23
  end
24
24
 
25
25
  it 'should include patches for attributes deleted' do
26
- foo.diff(foo2).should have_key(:argyle).with_value(['socks', nil])
26
+ foo.diff(foo2).should include(argyle: ['socks', nil])
27
27
  end
28
28
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Merge" do
4
+ describe "Diff3" do
5
+
6
+ it 'should behave like a 3-way merge' do
7
+ extend HasVersions::Merge::Diff3
8
+
9
+ diff3('A','A','A').should == [true, 'A']
10
+ diff3('A','B','A').should == [true, 'B']
11
+ diff3('A','A','B').should == [true, 'B']
12
+ diff3('B','A','A').should == [true, 'A']
13
+ diff3('B','A','C').should == [false, ['A', 'C']]
14
+ end
15
+
16
+ it 'should take multiple theirs' do
17
+ extend HasVersions::Merge::Diff3
18
+
19
+ diff3('A','A',*['A','A']).should == [true, 'A']
20
+ diff3('A','A',*['B','B']).should == [true, 'B']
21
+ diff3('A','A',*['B','B']).should == [true, 'B']
22
+ diff3('A','A',*['A','B']).should == [false, ['A','B']]
23
+ diff3('A','A',*['B','C']).should == [false, ['B','C']]
24
+ diff3('A','B',*['C','D']).should == [false, ['C','D','B']]
25
+ end
26
+
27
+ it 'should handle _missing values'
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Merge" do
4
+ describe "MergeBase" do
5
+ include HasVersions::Merge::MergeBase
6
+
7
+ it 'should find no bases for one version' do
8
+ v = MergingVersion.new
9
+ merge_bases([v]).should == []
10
+ end
11
+
12
+ it 'should find merge base for a version and its parent' do
13
+ p1 = MergingVersion.new
14
+ v1 = MergingVersion.new
15
+ v1.parents = p1
16
+ merge_bases([v1, p1]).should == [p1]
17
+ end
18
+
19
+ it 'should find merge base for versions with multiple ancestors' do
20
+ p1 = MergingVersion.new
21
+ p2 = MergingVersion.new
22
+ v1 = MergingVersion.new
23
+ v1.parents = p1
24
+ p1.parents = p2
25
+ merge_bases([v1, p1]).should == [p1]
26
+ end
27
+
28
+ it 'should find merge base for two versions with the same parent' do
29
+ p1 = MergingVersion.new
30
+ v1 = MergingVersion.new
31
+ v2 = MergingVersion.new
32
+ v1.parents = v2.parents = [p1]
33
+ merge_bases([v1, v2]).should == [p1]
34
+ end
35
+
36
+ it 'should find merge base for multiple versions with different parents' do
37
+ p1, p2, v1, v2, v3 = *5.times.collect { MergingVersion.new }
38
+ p2.parents = [p1]
39
+ v1.parents = [p1]
40
+ v2.parents = [p2]
41
+ v3.parents = [v2]
42
+ merge_bases([v1, v2, v3]).should == [p1]
43
+ end
44
+
45
+ it 'should find no base for versions with no common ancestry' do
46
+ v1 = MergingVersion.new
47
+ v2 = MergingVersion.new
48
+ merge_bases([v1, v2]).should == []
49
+ end
50
+
51
+ #TODO this is broken
52
+ it 'should find multiple bases for versions with criss-cross ancestry' do
53
+ p1, p2, p3, v1, v2 = *5.times.collect { MergingVersion.new }
54
+ p2.parents = [p1]
55
+ p3.parents = [p1]
56
+ v1.parents = [p3, p2]
57
+ v2.parents = [p2, p3]
58
+ merge_bases([v1, v2]).should == [p2, p3]
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe HasVersions::Merge::Octopus do
4
+
5
+ it 'should only accept 3+ versions' do
6
+ expect { merge([MergingVersion.new]) }.to raise_error(HasVersions::MergeFailed)
7
+ expect { merge([MergingVersion.new]*2) }.to raise_error(HasVersions::MergeFailed)
8
+ end
9
+
10
+ it 'should merge identical versions' do
11
+ v1 = MergingVersion.new(name: 'foo1')
12
+ v2 = MergingVersion.new(name: 'foo1')
13
+ v3 = MergingVersion.new(name: 'foo1')
14
+ merge([v1, v2, v3]).resolution.snapshot.should include(name: 'foo1')
15
+ end
16
+
17
+ it 'should merge many versions' do
18
+ v1 = MergingVersion.new(name: 'foo1')
19
+ v2 = MergingVersion.new(name: 'foo1')
20
+ v3 = MergingVersion.new(name: 'foo1')
21
+ v4 = MergingVersion.new(name: 'foo1')
22
+ v5 = MergingVersion.new(name: 'foo1')
23
+ v6 = MergingVersion.new(name: 'foo1')
24
+ merge([v1, v2, v3, v4, v5, v6]).resolution.snapshot.should include(name: 'foo1')
25
+ end
26
+
27
+ it 'should detect conflicts' do
28
+ v1 = MergingVersion.new(name: 'foo1')
29
+ v2 = MergingVersion.new(name: 'foo2')
30
+ v3 = MergingVersion.new(name: 'foo3')
31
+ merge([v1, v2, v3]).conflicts.should include(name: ['foo1', 'foo2', 'foo3'])
32
+ end
33
+
34
+ it 'should merge ancestors cleanly' do
35
+ v1 = MergingVersion.new(name: 'foo1')
36
+ v2 = MergingVersion.new(name: 'foo2')
37
+ v3 = MergingVersion.new(name: 'foo3')
38
+
39
+ v3.parents = v2
40
+ v2.parents = v1
41
+ merge([v1, v2, v3]).conflicts.should be_empty
42
+ merge([v1, v2, v3]).resolution.snapshot.should include(name: 'foo3')
43
+ end
44
+
45
+ it 'should merge missing attributes' do
46
+ v1 = MergingVersion.new(name: 'foo1')
47
+ v2 = MergingVersion.new(name: 'foo1', socks: 'stripey')
48
+ v3 = MergingVersion.new(hat: 'silly')
49
+ merge([v1, v2, v3]).resolution.snapshot.should include(name: 'foo1', socks: 'stripey', hat: 'silly')
50
+ end
51
+
52
+ it 'should save the partial merge result' do
53
+ v1 = MergingVersion.new(name: 'foo1', socks: 'stripey')
54
+ v2 = MergingVersion.new(name: 'foo2', socks: 'argyle')
55
+ v3 = MergingVersion.new(name: 'foo3', socks: 'argyle')
56
+ p1 = MergingVersion.new(name: 'foo', socks: 'argyle')
57
+ v1.parents = v2.parents = v3.parents = p1
58
+ merge([v1, v2, v3]).conflicts.should include(name: ['foo1', 'foo2', 'foo3'])
59
+ merge([v1, v2, v3]).resolution.snapshot.should include(socks: 'stripey')
60
+ end
61
+
62
+ it 'should set parents' do
63
+ v1 = MergingVersion.new(name: 'foo1', socks: 'stripey')
64
+ v2 = MergingVersion.new(name: 'foo2', socks: 'argyle')
65
+ v3 = MergingVersion.new(name: 'foo3', socks: 'argyle')
66
+ merge([v1, v2, v3]).resolution.parents.should == [v1, v2, v3]
67
+ end
68
+
69
+ def merge(versions, options={})
70
+ s = HasVersions::Merge::Octopus.new(versions, options)
71
+ s.merge
72
+ s
73
+ end
74
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe HasVersions::Merge::ThreeWay do
4
+
5
+ it 'should only accept 2 versions' do
6
+ expect { merge([MergingVersion.new]) }.to raise_error(HasVersions::MergeFailed)
7
+ expect { merge([MergingVersion.new]*3) }.to raise_error(HasVersions::MergeFailed)
8
+ end
9
+
10
+ it 'should merge identical versions' do
11
+ v1 = MergingVersion.new(name: 'foo1')
12
+ v2 = MergingVersion.new(name: 'foo1')
13
+ merge([v1, v2]).resolution.snapshot.should include(name: 'foo1')
14
+ end
15
+
16
+ it 'should detect conflicts' do
17
+ v1 = MergingVersion.new(name: 'foo1')
18
+ v2 = MergingVersion.new(name: 'foo2')
19
+ merge([v1, v2]).conflicts.should include(name: ['foo1', 'foo2'])
20
+ end
21
+
22
+ it 'should merge ancestors cleanly' do
23
+ v1 = MergingVersion.new(name: 'foo1')
24
+ v2 = MergingVersion.new(name: 'foo2')
25
+
26
+ v2.parents = [v1]
27
+ merge([v1, v2]).conflicts.should be_empty
28
+ merge([v1, v2]).resolution.snapshot.should include(name: 'foo2')
29
+ end
30
+
31
+ it 'should merge missing attributes' do
32
+ v1 = MergingVersion.new(name: 'foo1')
33
+ v2 = MergingVersion.new(name: 'foo1', socks: 'stripey')
34
+ merge([v1, v2]).resolution.snapshot.should include(name: 'foo1', socks: 'stripey')
35
+ end
36
+
37
+ it 'should save the partial merge result' do
38
+ v1 = MergingVersion.new(name: 'foo1', socks: 'stripey')
39
+ v2 = MergingVersion.new(name: 'foo2', socks: 'argyle')
40
+ p1 = MergingVersion.new(name: 'foo', socks: 'argyle')
41
+ v1.parents = v2.parents = p1
42
+ merge([v1, v2]).conflicts.should include(name: ['foo1', 'foo2'])
43
+ merge([v1, v2]).resolution.snapshot.should include(socks: 'stripey')
44
+ end
45
+
46
+ it 'should set parents' do
47
+ v1 = MergingVersion.new(name: 'foo1', socks: 'stripey')
48
+ v2 = MergingVersion.new(name: 'foo2', socks: 'argyle')
49
+ merge([v1, v2]).resolution.parents.should == [v1, v2]
50
+ end
51
+
52
+ def merge(versions, options={})
53
+ s = HasVersions::Merge::ThreeWay.new(versions, options)
54
+ s.merge
55
+ s
56
+ end
57
+ end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class ResetObjectVersion < Version; end
4
4
 
@@ -12,7 +12,6 @@ class ResetObject
12
12
 
13
13
  has_versions(ResetObjectVersion) do
14
14
  attribute :name
15
- attribute :set, expected: Set, decoder: lambda { |value| Set.new(value) }
16
15
  end
17
16
  end
18
17
 
@@ -1,18 +1,17 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class StagingObjectVersion < Version; end
4
4
 
5
- class StagingObject
6
- include HasVersions::Versioned
7
- include HasVersions::Stage
8
-
9
- attr_accessor :name
10
-
11
- has_versions(StagingObjectVersion) do
12
- attribute :name
13
- end
14
- end
15
-
16
- describe StagingObject do
17
- end
18
-
5
+ # class StagingObject
6
+ # include HasVersions::Versioned
7
+ # include HasVersions::Stage
8
+ #
9
+ # attr_accessor :name
10
+ #
11
+ # has_versions(StagingObjectVersion) do
12
+ # attribute :name
13
+ # end
14
+ # end
15
+ #
16
+ # describe StagingObject do
17
+ # end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class VersionedObject
4
4
  include HasVersions::Versioned
@@ -44,16 +44,16 @@ describe VersionedObject do
44
44
  v
45
45
  end
46
46
 
47
- its(:versioned_attributes) { should have_key(:name).with_value('foo') }
47
+ its(:versioned_attributes) { should include(name: 'foo') }
48
48
 
49
49
  its(:to_version) { should be_a(VersionedObjectVersion) }
50
50
 
51
51
  it 'should set attributes in to_version' do
52
- subject.to_version.snapshot.should have_key(:name).with_value('foo')
52
+ subject.to_version.snapshot.should include(name: 'foo')
53
53
  end
54
54
 
55
55
  it 'should encode attributes' do
56
- subject.to_version.snapshot.should have_key(:time).with_value("02:43AM")
56
+ subject.to_version.snapshot.should include(time: "02:43AM")
57
57
  end
58
58
  end
59
59
  end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe 'VersioningKey' do
4
4
  describe 'UUID' do
data/spec/spec_helper.rb CHANGED
@@ -1,17 +1,16 @@
1
- require 'simplecov'
2
- SimpleCov.start do
3
- add_filter '/spec/'
4
- add_filter '/features/'
5
- end
1
+ # $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ # $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ # p "#{$LOAD_PATH.inspect}"
6
5
 
7
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
- $LOAD_PATH.unshift(File.dirname(__FILE__))
9
6
  require 'rspec'
10
7
  require 'has_versions'
11
8
 
12
9
  # Requires supporting files with custom matchers and macros, etc,
13
10
  # in ./support/ and its subdirectories.
14
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
11
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each do |f|
12
+ require f
13
+ end
15
14
 
16
15
  RSpec.configure do |config|
17
16
  config.mock_with :rspec
@@ -5,4 +5,3 @@ RSpec::Matchers.define :be_a_uuid do |expected|
5
5
  actual =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ # c14ba018-2f15-11e0-9a06-b6e5e5c60f13
6
6
  end
7
7
  end
8
-
@@ -5,3 +5,13 @@ class Version
5
5
  @snapshot = (snapshot || {}).with_indifferent_access
6
6
  end
7
7
  end
8
+
9
+ class MergingVersion < Version
10
+ def parents
11
+ @parents || []
12
+ end
13
+
14
+ def parents=(parents)
15
+ @parents = Array(parents)
16
+ end
17
+ end