has_versions 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.autotest +12 -0
  2. data/.document +5 -0
  3. data/.gitignore +42 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +63 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.rdoc +19 -0
  9. data/Rakefile +29 -0
  10. data/features/has_versions.feature +9 -0
  11. data/features/step_definitions/has_versions_steps.rb +0 -0
  12. data/features/support/env.rb +13 -0
  13. data/has_versions.gemspec +40 -0
  14. data/lib/has_versions/apply/simple.rb +34 -0
  15. data/lib/has_versions/apply.rb +9 -0
  16. data/lib/has_versions/attributes.rb +34 -0
  17. data/lib/has_versions/configuration.rb +28 -0
  18. data/lib/has_versions/diff/simple.rb +24 -0
  19. data/lib/has_versions/diff.rb +7 -0
  20. data/lib/has_versions/merge/stupid.rb +44 -0
  21. data/lib/has_versions/merge.rb +61 -0
  22. data/lib/has_versions/reset/simple.rb +19 -0
  23. data/lib/has_versions/reset.rb +15 -0
  24. data/lib/has_versions/stage.rb +121 -0
  25. data/lib/has_versions/version.rb +4 -0
  26. data/lib/has_versions/versioned.rb +34 -0
  27. data/lib/has_versions/versioning_key/uuid.rb +23 -0
  28. data/lib/has_versions/versioning_key.rb +8 -0
  29. data/lib/has_versions.rb +21 -0
  30. data/spec/has_versions/apply_spec.rb +33 -0
  31. data/spec/has_versions/configuration_spec.rb +33 -0
  32. data/spec/has_versions/diff_spec.rb +28 -0
  33. data/spec/has_versions/merge_spec.rb +67 -0
  34. data/spec/has_versions/reset_spec.rb +41 -0
  35. data/spec/has_versions/stage_spec.rb +18 -0
  36. data/spec/has_versions/versioned_spec.rb +59 -0
  37. data/spec/has_versions/versioning_key_spec.rb +31 -0
  38. data/spec/spec_helper.rb +18 -0
  39. data/spec/support/matchers/be_a_uuid.rb +8 -0
  40. data/spec/support/matchers/have_key.rb +25 -0
  41. data/spec/support/version.rb +7 -0
  42. metadata +216 -0
data/.autotest ADDED
@@ -0,0 +1,12 @@
1
+ require "test_notifier/runner/autotest"
2
+
3
+ Autotest.add_hook(:initialize) {|at|
4
+ at.add_exception %r{^\.git} # ignore Version Control System
5
+ at.add_exception %r{^./tmp} # ignore temp files, lest autotest will run again, and again...
6
+ # at.clear_mappings # take out the default (test/test*rb)
7
+ at.add_mapping(%r{^lib/.*\.rb$}) {|f, _|
8
+ Dir['spec/**/*.rb']
9
+ }
10
+ nil
11
+ }
12
+
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,42 @@
1
+ # simplecov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # bundler
12
+ .bundle
13
+
14
+ # jeweler generated
15
+ pkg
16
+
17
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
18
+ #
19
+ # * Create a file at ~/.gitignore
20
+ # * Include files you want ignored
21
+ # * Run: git config --global core.excludesfile ~/.gitignore
22
+ #
23
+ # After doing this, these files will be ignored in all your git projects,
24
+ # saving you from having to 'pollute' every project you touch with them
25
+ #
26
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
27
+ #
28
+ # For MacOS:
29
+ #
30
+ #.DS_Store
31
+ #
32
+ # For TextMate
33
+ #*.tmproj
34
+ #tmtags
35
+ #
36
+ # For emacs:
37
+ #*~
38
+ #\#*
39
+ #.\#*
40
+ #
41
+ # For vim:
42
+ #*.swp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format nested
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ has_versions (0.3.0)
5
+ activesupport (~> 3)
6
+ i18n
7
+ simple_uuid
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ activesupport (3.0.5)
13
+ builder (3.0.0)
14
+ cucumber (0.10.0)
15
+ builder (>= 2.1.2)
16
+ diff-lcs (~> 1.1.2)
17
+ gherkin (~> 2.3.2)
18
+ json (~> 1.4.6)
19
+ term-ansicolor (~> 1.0.5)
20
+ diff-lcs (1.1.2)
21
+ gherkin (2.3.4)
22
+ json (~> 1.4.6)
23
+ i18n (0.5.0)
24
+ json (1.4.6)
25
+ reek (1.2.8)
26
+ ruby2ruby (~> 1.2)
27
+ ruby_parser (~> 2.0)
28
+ sexp_processor (~> 3.0)
29
+ roodi (2.1.0)
30
+ ruby_parser
31
+ rspec (2.5.0)
32
+ rspec-core (~> 2.5.0)
33
+ rspec-expectations (~> 2.5.0)
34
+ rspec-mocks (~> 2.5.0)
35
+ rspec-core (2.5.1)
36
+ rspec-expectations (2.5.0)
37
+ diff-lcs (~> 1.1.2)
38
+ rspec-mocks (2.5.0)
39
+ ruby2ruby (1.2.5)
40
+ ruby_parser (~> 2.0)
41
+ sexp_processor (~> 3.0)
42
+ ruby_parser (2.0.6)
43
+ sexp_processor (~> 3.0)
44
+ sexp_processor (3.0.5)
45
+ simple_uuid (0.1.1)
46
+ simplecov (0.4.1)
47
+ simplecov-html (~> 0.4.3)
48
+ simplecov-html (0.4.3)
49
+ term-ansicolor (1.0.5)
50
+ yard (0.6.5)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ bundler (~> 1.0.0)
57
+ cucumber
58
+ has_versions!
59
+ reek (~> 1.2.8)
60
+ roodi (~> 2.1.0)
61
+ rspec (~> 2)
62
+ simplecov (>= 0.3.8)
63
+ yard (~> 0.6.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Grant Rodgers
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = has_versions
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to has_versions
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Grant Rodgers. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = FileList['spec/**/*_spec.rb']
8
+ end
9
+
10
+ require 'cucumber/rake/task'
11
+ Cucumber::Rake::Task.new(:features)
12
+
13
+ require 'reek/rake/task'
14
+ Reek::Rake::Task.new do |t|
15
+ t.fail_on_error = true
16
+ t.verbose = false
17
+ t.source_files = 'lib/**/*.rb'
18
+ end
19
+
20
+ require 'roodi'
21
+ require 'roodi_task'
22
+ RoodiTask.new do |t|
23
+ t.verbose = false
24
+ end
25
+
26
+ task :default => :spec
27
+
28
+ require 'yard'
29
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
File without changes
@@ -0,0 +1,13 @@
1
+ require 'bundler'
2
+ begin
3
+ Bundler.setup(:default, :development)
4
+ rescue Bundler::BundlerError => e
5
+ $stderr.puts e.message
6
+ $stderr.puts "Run `bundle install` to install missing gems"
7
+ exit e.status_code
8
+ end
9
+
10
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
11
+ require 'has_versions'
12
+
13
+ require 'rspec/expectations'
@@ -0,0 +1,40 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "has_versions/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "has_versions"
7
+ s.version = HasVersions::VERSION
8
+
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Grant Rodgers"]
11
+ s.email = ["grantr@gmail.com"]
12
+ s.homepage = ""
13
+ s.summary = %q{ActiveModel versioning}
14
+ s.description = %q{ActiveModel versioning}
15
+
16
+ s.rubyforge_project = "has_versions"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ s.extra_rdoc_files = [
24
+ "LICENSE.txt",
25
+ "README.rdoc"
26
+ ]
27
+ s.licenses = ["MIT"]
28
+
29
+ s.add_runtime_dependency("i18n", [">= 0"])
30
+ s.add_runtime_dependency("activesupport", ["~> 3"])
31
+ s.add_runtime_dependency("simple_uuid", [">= 0"])
32
+ s.add_development_dependency("rspec", ["~> 2"])
33
+ s.add_development_dependency("yard", ["~> 0.6.0"])
34
+ s.add_development_dependency("cucumber", [">= 0"])
35
+ s.add_development_dependency("bundler", ["~> 1.0.0"])
36
+ s.add_development_dependency("simplecov", [">= 0.3.8"])
37
+ s.add_development_dependency("reek", ["~> 1.2.8"])
38
+ s.add_development_dependency("roodi", ["~> 2.1.0"])
39
+ end
40
+
@@ -0,0 +1,34 @@
1
+ module HasVersions
2
+ module Apply
3
+
4
+ # takes a list of diff (patch) hashes and returns a copy of self with the patches applied in order.
5
+ # raises ApplyFailed if the patch does not apply
6
+ # diff format is:
7
+ # {
8
+ # attribute_name: [a, b]
9
+ # }
10
+ #
11
+ #TODO should this be allowed to change the schema?
12
+ # it will if the hash returns nil for unset keys
13
+
14
+ module Simple
15
+ def apply(*patches)
16
+ self.class.new.tap do |version| #TODO is new the right thing here? it doe scall initialize (allocate does not)
17
+ ours = snapshot.with_indifferent_access #TEST is it necessary to deep dup the snapshot?
18
+
19
+ patches.each do |patch|
20
+ patch.each do |key, (a, b)|
21
+ if ours[key] == a
22
+ ours[key] = b #TEST is it necessary to dup the value?
23
+ else
24
+ raise ApplyFailed, "Expected #{key} to be #{a.inspect}, was #{ours[key].inspect}" #TODO should this contain more information about what failed?
25
+ end
26
+ end
27
+ end
28
+
29
+ version.snapshot = ours
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ module HasVersions
2
+ class ApplyFailed < VersioningError; end
3
+
4
+ module Apply
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Simple
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ module HasVersions
2
+ module Attributes
3
+
4
+ def versioned_attributes
5
+ versioning_configuration.attributes.inject(HashWithIndifferentAccess.new) do |memo, (key, options)|
6
+
7
+ memo[key] = versioning_encode_value(__send__(key), options)
8
+ memo
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def versioning_decode_value(value, options)
15
+ return value if value.nil?
16
+
17
+ if options[:expected] && !value.kind_of?(options[:expected]) && options[:decoder]
18
+ options[:decoder].call(value)
19
+ else
20
+ value
21
+ end
22
+ end
23
+
24
+ def versioning_encode_value(value, options)
25
+ return value if value.nil?
26
+
27
+ if options[:encoder]
28
+ options[:encoder].call(value)
29
+ else
30
+ value
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ module HasVersions
2
+
3
+ class Configuration
4
+ attr_accessor :attributes
5
+ attr_accessor :types
6
+
7
+ DEFAULT_TYPES = {
8
+ set: { expected: Set, decoder: ->(value) { Set.new(value) } },
9
+ time_with_zone: { expected: ActiveSupport::TimeWithZone, encoder: ->(value) { value.utc.xmlschema(6) }, decoder: ->(value) { Time.xmlschema(value).in_time_zone } }
10
+ }.freeze
11
+
12
+ def initialize(&block)
13
+ @attributes = {}.with_indifferent_access
14
+ @types = DEFAULT_TYPES.with_indifferent_access
15
+ instance_eval(&block)
16
+ end
17
+
18
+ def type(name, options={})
19
+ @types[name] = options.with_indifferent_access
20
+ end
21
+
22
+ def attribute(name, options={})
23
+ options.merge!(@types[options[:type]]) if @types[options[:type]]
24
+ @attributes[name] = options.with_indifferent_access
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,24 @@
1
+ module HasVersions
2
+ module Diff
3
+ # takes a version and returns a diff (patch) from self
4
+ # version must respond to #snapshot with an attributes hash
5
+ # diff format is:
6
+ # {
7
+ # attribute_name: [a, b]
8
+ # }
9
+
10
+ module Simple
11
+ def diff(other)
12
+ ours = snapshot
13
+ theirs = other.snapshot
14
+ all_keys = (ours.keys + theirs.keys).uniq
15
+
16
+ output = {}.with_indifferent_access
17
+ all_keys.each do |key|
18
+ output[key] = [ours[key], theirs[key]] if ours[key] != theirs[key] #TEST is it necessary to dup the values?
19
+ end
20
+ output
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ module HasVersions
2
+ module Diff
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Simple
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ module HasVersions
2
+ module Merge
3
+
4
+ # takes a list of versions and attempts to merge them with the current version.
5
+ # returns the merged version if successful
6
+ # raises MergeConflict if failed
7
+ #
8
+ # Doesn't attempt to determine common ancestry. The merge base is always the current version (self).
9
+ # Since there is no ancestry, conflict resolution is conservative. Any conflicting values generate a conflict.
10
+ #
11
+ # In general merging more than 1 version will generate a conflict.
12
+ module Stupid
13
+ include Diff3
14
+
15
+ def merge(*versions)
16
+ merged_version = self.class.new
17
+ merged_version.snapshot = snapshot.dup
18
+
19
+ # get the full list of attributes in each version and the merge base (self)
20
+ attributes = (versions.collect { |v| v.snapshot.keys }.flatten + snapshot.keys).uniq
21
+ conflicts = HashWithIndifferentAccess.new
22
+
23
+ # do 3-way merge on each attribute
24
+ attributes.each do |key|
25
+ theirs = versions.collect { |v| v.snapshot[key] }
26
+ clean, result = diff3(snapshot[key], snapshot[key], *theirs)
27
+
28
+ if clean
29
+ puts "clean: #{result.inspect}"
30
+ merged_version.snapshot[key] = result
31
+ else
32
+ conflicts[key] = result
33
+ end
34
+ end
35
+
36
+ # raise if conflicts is not empty
37
+ raise MergeConflict.new(conflicts, merged_version), conflicts.inspect unless conflicts.empty?
38
+ merged_version
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,61 @@
1
+ module HasVersions
2
+
3
+ class MergeConflict < VersioningError
4
+ attr_accessor :conflicts
5
+ attr_accessor :result
6
+
7
+ def initialize(conflicts=nil, result=nil)
8
+ @conflicts = conflicts
9
+ @result = result
10
+ end
11
+ end
12
+
13
+ module Merge
14
+ extend ActiveSupport::Autoload
15
+
16
+ autoload :Stupid
17
+
18
+ module Diff3
19
+
20
+ # 3-way merge, extended to support more than one other
21
+ # base ours *theirs.uniq take
22
+ # A A [A] A (ours)
23
+ # A B [A] B (ours)
24
+ # A A [B] B (theirs)
25
+ # B A [A] A (ours)
26
+ # B A [A,B] conflict
27
+ # B A [C] conflict
28
+ #
29
+ # returns an array [clean, result]
30
+ # where clean is a boolean
31
+ # and result is an array in the conflicts case, or a single value in the clean case
32
+
33
+ def diff3(base, ours, *theirs)
34
+ theirs = theirs.uniq
35
+
36
+ # if there is more than one possible value, it is already a conflict
37
+ if theirs.size > 1
38
+ # add ours to conflicts if it is different from base
39
+ if ours != base && !theirs.include?(ours)
40
+ theirs << ours
41
+ end
42
+ return [false, theirs]
43
+ end
44
+
45
+ theirs = theirs.first
46
+
47
+ if ours == base && base == theirs
48
+ [true, ours]
49
+ elsif base == theirs
50
+ [true, ours]
51
+ elsif ours == base
52
+ [true, theirs]
53
+ elsif ours == theirs
54
+ [true, ours]
55
+ else
56
+ [false, [ours, theirs]]
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,19 @@
1
+ module HasVersions
2
+ module Reset
3
+ # reset should:
4
+ # take a version
5
+ # update self with the attributes from the version
6
+ # return updated self
7
+
8
+ module Simple
9
+ def reset!(version)
10
+ versioning_configuration.attributes.each do |name, options|
11
+ value = versioning_decode_value(version.snapshot[name], options)
12
+ __send__("#{name}=", value)
13
+ end
14
+ self
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module HasVersions
2
+ module Reset
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Simple
6
+
7
+ module FromVersion
8
+ def from_version(version)
9
+ new.tap do |object|
10
+ object.reset!(version)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,121 @@
1
+ #TODO this should move to a contrib gem
2
+ module HasVersions
3
+ module Stage
4
+ # stage versions onto an object, returning true or false if there are conflicts.
5
+ # extends the object and each versioned attribute with methods to retrieve status, conflicts, and original value.
6
+ # extends the object with a resolution and conflicted/modified attribute lists
7
+ #
8
+ #TODO should this be part of the plugin?
9
+
10
+ def stage(*versions)
11
+ original = to_version
12
+
13
+ # try merging
14
+ begin
15
+ merge_result = original.merge(*versions)
16
+ conflicts = HashWithIndifferentAccess.new
17
+ # if merge fails, get the conflicts and result
18
+ rescue MergeConflict => e
19
+ conflicts = e.conflicts
20
+ merge_result = e.result
21
+ end
22
+
23
+ reset!(merge_result)
24
+
25
+ stage_extend_self(versions, conflicts, original, merge_result)
26
+ stage_extend_attributes(conflicts, original)
27
+
28
+ conflicts.empty?
29
+ end
30
+ # devang also wants an api that is similar to the old one, with pending objects, and old values in the attributes.
31
+ # we can implement something that attaches a non-updated object in pending, with all the old apis
32
+
33
+ private
34
+
35
+ def stage_extend_self(staged, conflicts, original, merge_result)
36
+ # extend self with StagedAttribute and set conflicts and original
37
+ self.extend(StagedAttribute)
38
+ self.conflicts = conflicts
39
+ self.original = self.class.new.reset!(original) #TODO should this be a clone or new? should it inherit the key or id or other attributes?
40
+
41
+ # extend self with Staged and set the proper values
42
+ self.extend(Staged)
43
+ self.staged = staged
44
+ self.resolution = merge_result
45
+ # modified attributes is the conflicted keys plus the modified keys
46
+ self.modified_attributes = (conflicts.keys + original.diff(merge_result).keys).uniq
47
+ end
48
+
49
+ def stage_extend_attributes(conflicts, original)
50
+ # for each attribute, extend with StagedAttribute and set proper values
51
+ versioned_attributes.each do |key, value|
52
+ v.extend(StagedAttribute)
53
+ v.conflicts = conflicts[key] || []
54
+ v.original = original.snapshot[key]
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ # included in attributes of staged objects
61
+ module StagedAttribute
62
+
63
+ def conflicted?
64
+ !@conflicts.empty?
65
+ end
66
+
67
+ def modified?
68
+ @original != self
69
+ end
70
+
71
+ def conflicts
72
+ @conflicts
73
+ end
74
+
75
+ def conflicts=(conflicts)
76
+ @conflicts = conflicts
77
+ end
78
+
79
+ def original
80
+ @original
81
+ end
82
+
83
+ def original=(original)
84
+ @original = original
85
+ end
86
+ end
87
+
88
+ # included in staged objects
89
+ # TODO maybe this should be included in the class up front
90
+ module Staged
91
+
92
+ def staged
93
+ @staged
94
+ end
95
+
96
+ def staged=(staged)
97
+ @staged = staged
98
+ end
99
+
100
+ def resolution
101
+ @resolution
102
+ end
103
+
104
+ def resolution=(resolution)
105
+ @resolution = resolution
106
+ end
107
+
108
+ def modified_attributes
109
+ @modified_attributes
110
+ end
111
+
112
+ def modified_attributes=(modified_attributes)
113
+ @modified_attributes = modified_attributes
114
+ end
115
+
116
+ def conflicted_attributes
117
+ @conflicts.keys
118
+ end
119
+ end
120
+
121
+ end
@@ -0,0 +1,4 @@
1
+ module HasVersions
2
+ VERSION = "0.3.0"
3
+ end
4
+