vestal_versions 1.0.2 → 2.0.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.
- data/.gitignore +19 -20
- data/.travis.yml +22 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +10 -0
- data/README.rdoc +63 -36
- data/Rakefile +4 -43
- data/gemfiles/activerecord_3_0.gemfile +10 -0
- data/gemfiles/activerecord_3_1.gemfile +10 -0
- data/gemfiles/activerecord_3_2.gemfile +10 -0
- data/gemfiles/activerecord_4_0.gemfile +10 -0
- data/lib/generators/vestal_versions.rb +11 -0
- data/lib/generators/vestal_versions/migration/migration_generator.rb +17 -0
- data/{generators/vestal_versions → lib/generators/vestal_versions/migration}/templates/initializer.rb +0 -0
- data/{generators/vestal_versions → lib/generators/vestal_versions/migration}/templates/migration.rb +4 -3
- data/lib/vestal_versions.rb +39 -12
- data/lib/vestal_versions/changes.rb +43 -47
- data/lib/vestal_versions/conditions.rb +31 -43
- data/lib/vestal_versions/control.rb +162 -138
- data/lib/vestal_versions/creation.rb +67 -59
- data/lib/vestal_versions/deletion.rb +37 -0
- data/lib/vestal_versions/options.rb +6 -10
- data/lib/vestal_versions/reload.rb +7 -14
- data/lib/vestal_versions/reset.rb +15 -19
- data/lib/vestal_versions/reversion.rb +64 -52
- data/lib/vestal_versions/users.rb +36 -39
- data/lib/vestal_versions/version.rb +57 -2
- data/lib/vestal_versions/version_tagging.rb +51 -0
- data/lib/vestal_versions/versioned.rb +14 -17
- data/lib/vestal_versions/versions.rb +22 -7
- data/spec/spec_helper.rb +28 -0
- data/spec/support/models.rb +19 -0
- data/spec/support/schema.rb +25 -0
- data/spec/vestal_versions/changes_spec.rb +134 -0
- data/spec/vestal_versions/conditions_spec.rb +103 -0
- data/spec/vestal_versions/control_spec.rb +120 -0
- data/spec/vestal_versions/creation_spec.rb +90 -0
- data/spec/vestal_versions/deletion_spec.rb +86 -0
- data/spec/vestal_versions/options_spec.rb +45 -0
- data/spec/vestal_versions/reload_spec.rb +18 -0
- data/spec/vestal_versions/reset_spec.rb +111 -0
- data/spec/vestal_versions/reversion_spec.rb +103 -0
- data/spec/vestal_versions/users_spec.rb +21 -0
- data/spec/vestal_versions/version_spec.rb +61 -0
- data/spec/vestal_versions/version_tagging_spec.rb +39 -0
- data/spec/vestal_versions/versioned_spec.rb +16 -0
- data/spec/vestal_versions/versions_spec.rb +176 -0
- data/vestal_versions.gemspec +18 -100
- metadata +153 -102
- data/VERSION +0 -1
- data/generators/vestal_versions/vestal_versions_generator.rb +0 -10
- data/init.rb +0 -1
- data/lib/vestal_versions/configuration.rb +0 -40
- data/lib/vestal_versions/tagging.rb +0 -50
- data/test/changes_test.rb +0 -169
- data/test/conditions_test.rb +0 -137
- data/test/configuration_test.rb +0 -39
- data/test/control_test.rb +0 -152
- data/test/creation_test.rb +0 -110
- data/test/options_test.rb +0 -52
- data/test/reload_test.rb +0 -19
- data/test/reset_test.rb +0 -112
- data/test/reversion_test.rb +0 -68
- data/test/schema.rb +0 -43
- data/test/tagging_test.rb +0 -39
- data/test/test_helper.rb +0 -11
- data/test/users_test.rb +0 -25
- data/test/version_test.rb +0 -43
- data/test/versioned_test.rb +0 -18
- data/test/versions_test.rb +0 -172
@@ -2,84 +2,92 @@ module VestalVersions
|
|
2
2
|
# Adds the functionality necessary to control version creation on a versioned instance of
|
3
3
|
# ActiveRecord::Base.
|
4
4
|
module Creation
|
5
|
-
|
6
|
-
base.class_eval do
|
7
|
-
extend ClassMethods
|
8
|
-
include InstanceMethods
|
5
|
+
extend ActiveSupport::Concern
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
alias_method_chain :prepare_versioned_options, :creation
|
15
|
-
end
|
16
|
-
end
|
7
|
+
included do
|
8
|
+
after_create :create_initial_version, :if => :create_initial_version?
|
9
|
+
after_update :create_version, :if => :create_version?
|
10
|
+
after_update :update_version, :if => :update_version?
|
17
11
|
end
|
18
12
|
|
19
13
|
# Class methods added to ActiveRecord::Base to facilitate the creation of new versions.
|
20
14
|
module ClassMethods
|
21
15
|
# Overrides the basal +prepare_versioned_options+ method defined in VestalVersions::Options
|
22
|
-
# to extract the <tt>:only</tt> and <tt>:
|
23
|
-
|
24
|
-
|
16
|
+
# to extract the <tt>:only</tt>, <tt>:except</tt> and <tt>:initial_version</tt> options
|
17
|
+
# into +vestal_versions_options+.
|
18
|
+
def prepare_versioned_options(options)
|
19
|
+
result = super(options)
|
25
20
|
|
26
21
|
self.vestal_versions_options[:only] = Array(options.delete(:only)).map(&:to_s).uniq if options[:only]
|
27
22
|
self.vestal_versions_options[:except] = Array(options.delete(:except)).map(&:to_s).uniq if options[:except]
|
28
|
-
|
23
|
+
self.vestal_versions_options[:initial_version] = options.delete(:initial_version)
|
24
|
+
|
29
25
|
result
|
30
26
|
end
|
31
27
|
end
|
32
28
|
|
33
29
|
# Instance methods that determine whether to save a version and actually perform the save.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Returns whether an initial version should be created upon creation of the parent record.
|
34
|
+
def create_initial_version?
|
35
|
+
vestal_versions_options[:initial_version] == true
|
36
|
+
end
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
38
|
+
# Creates an initial version upon creation of the parent record.
|
39
|
+
def create_initial_version
|
40
|
+
versions.create(version_attributes.merge(:number => 1))
|
41
|
+
reset_version_changes
|
42
|
+
reset_version
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns whether a new version should be created upon updating the parent record.
|
46
|
+
def create_version?
|
47
|
+
!version_changes.blank?
|
48
|
+
end
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
# Creates a new version upon updating the parent record.
|
51
|
+
def create_version(attributes = nil)
|
52
|
+
versions.create(attributes || version_attributes)
|
53
|
+
reset_version_changes
|
54
|
+
reset_version
|
55
|
+
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
reset_version
|
62
|
-
end
|
57
|
+
# Returns whether the last version should be updated upon updating the parent record.
|
58
|
+
# This method is overridden in VestalVersions::Control to account for a control block that
|
59
|
+
# merges changes onto the previous version.
|
60
|
+
def update_version?
|
61
|
+
false
|
62
|
+
end
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
64
|
+
# Updates the last version's changes by appending the current version changes.
|
65
|
+
def update_version
|
66
|
+
return create_version unless v = versions.last
|
67
|
+
v.modifications_will_change!
|
68
|
+
v.update_attribute(:modifications, v.changes.append_changes(version_changes))
|
69
|
+
reset_version_changes
|
70
|
+
reset_version
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns an array of column names that should be included in the changes of created
|
74
|
+
# versions. If <tt>vestal_versions_options[:only]</tt> is specified, only those columns
|
75
|
+
# will be versioned. Otherwise, if <tt>vestal_versions_options[:except]</tt> is specified,
|
76
|
+
# all columns will be versioned other than those specified. Without either option, the
|
77
|
+
# default is to version all columns. At any rate, the four "automagic" timestamp columns
|
78
|
+
# maintained by Rails are never versioned.
|
79
|
+
def versioned_columns
|
80
|
+
case
|
81
|
+
when vestal_versions_options[:only] then self.class.column_names & vestal_versions_options[:only]
|
82
|
+
when vestal_versions_options[:except] then self.class.column_names - vestal_versions_options[:except]
|
83
|
+
else self.class.column_names
|
84
|
+
end - %w(created_at created_on updated_at updated_on)
|
85
|
+
end
|
77
86
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
87
|
+
# Specifies the attributes used during version creation. This is separated into its own
|
88
|
+
# method so that it can be overridden by the VestalVersions::Users feature.
|
89
|
+
def version_attributes
|
90
|
+
{:modifications => version_changes, :number => last_version + 1}
|
83
91
|
end
|
84
92
|
end
|
85
93
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module VestalVersions
|
2
|
+
# Allows version creation to occur conditionally based on given <tt>:if</tt> and/or
|
3
|
+
# <tt>:unless</tt> options.
|
4
|
+
module Deletion
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
before_destroy :create_destroyed_version, :if => :delete_version?
|
9
|
+
end
|
10
|
+
|
11
|
+
# Class methods on ActiveRecord::Base
|
12
|
+
module ClassMethods
|
13
|
+
# After the original +prepare_versioned_options+ method cleans the given options, this alias
|
14
|
+
# also extracts the <tt>:depedent</tt> if it set to <tt>:tracking</tt>
|
15
|
+
def prepare_versioned_options(options)
|
16
|
+
result = super(options)
|
17
|
+
if result[:dependent] == :tracking
|
18
|
+
self.vestal_versions_options[:track_destroy] = true
|
19
|
+
options.delete(:dependent)
|
20
|
+
end
|
21
|
+
|
22
|
+
result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def delete_version?
|
29
|
+
vestal_versions_options[:track_destroy]
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_destroyed_version
|
33
|
+
create_version({:modifications => attributes, :number => last_version + 1, :tag => 'deleted'})
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -1,11 +1,7 @@
|
|
1
1
|
module VestalVersions
|
2
2
|
# Provides +versioned+ options conversion and cleanup.
|
3
3
|
module Options
|
4
|
-
|
5
|
-
base.class_eval do
|
6
|
-
extend ClassMethods
|
7
|
-
end
|
8
|
-
end
|
4
|
+
extend ActiveSupport::Concern
|
9
5
|
|
10
6
|
# Class methods that provide preparation of options passed to the +versioned+ method.
|
11
7
|
module ClassMethods
|
@@ -23,16 +19,16 @@ module VestalVersions
|
|
23
19
|
# standard +has_many+ associations.
|
24
20
|
def prepare_versioned_options(options)
|
25
21
|
options.symbolize_keys!
|
26
|
-
options.reverse_merge!(
|
22
|
+
options.reverse_merge!(VestalVersions.config)
|
27
23
|
options.reverse_merge!(
|
28
24
|
:class_name => 'VestalVersions::Version',
|
29
25
|
:dependent => :delete_all
|
30
26
|
)
|
31
|
-
options.reverse_merge!(
|
32
|
-
|
33
|
-
)
|
27
|
+
# options.reverse_merge!(
|
28
|
+
# :order => "#{options[:class_name].constantize.table_name}.#{connection.quote_column_name('number')} ASC"
|
29
|
+
# )
|
34
30
|
|
35
|
-
|
31
|
+
class_attribute :vestal_versions_options
|
36
32
|
self.vestal_versions_options = options.dup
|
37
33
|
|
38
34
|
options.merge!(
|
@@ -2,22 +2,15 @@ module VestalVersions
|
|
2
2
|
# Ties into the existing ActiveRecord::Base#reload method to ensure that version information
|
3
3
|
# is properly reset.
|
4
4
|
module Reload
|
5
|
-
|
6
|
-
base.class_eval do
|
7
|
-
include InstanceMethods
|
8
|
-
|
9
|
-
alias_method_chain :reload, :versions
|
10
|
-
end
|
11
|
-
end
|
5
|
+
extend ActiveSupport::Concern
|
12
6
|
|
13
7
|
# Adds instance methods into ActiveRecord::Base to tap into the +reload+ method.
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
8
|
+
|
9
|
+
# Overrides ActiveRecord::Base#reload, resetting the instance-variable-cached version number
|
10
|
+
# before performing the original +reload+ method.
|
11
|
+
def reload(*args)
|
12
|
+
reset_version
|
13
|
+
super
|
21
14
|
end
|
22
15
|
end
|
23
16
|
end
|
@@ -1,28 +1,24 @@
|
|
1
1
|
module VestalVersions
|
2
2
|
# Adds the ability to "reset" (or hard revert) a versioned ActiveRecord::Base instance.
|
3
3
|
module Reset
|
4
|
-
|
5
|
-
base.class_eval do
|
6
|
-
include InstanceMethods
|
7
|
-
end
|
8
|
-
end
|
4
|
+
extend ActiveSupport::Concern
|
9
5
|
|
10
6
|
# Adds the instance methods required to reset an object to a previous version.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
saved
|
7
|
+
|
8
|
+
# Similar to +revert_to!+, the +reset_to!+ method reverts an object to a previous version,
|
9
|
+
# only instead of creating a new record in the version history, +reset_to!+ deletes all of
|
10
|
+
# the version history that occurs after the version reverted to.
|
11
|
+
#
|
12
|
+
# The action taken on each version record after the point of reversion is determined by the
|
13
|
+
# <tt>:dependent</tt> option given to the +versioned+ method. See the +versioned+ method
|
14
|
+
# documentation for more details.
|
15
|
+
def reset_to!(value)
|
16
|
+
if saved = skip_version{ revert_to!(value) }
|
17
|
+
versions.send(:delete, versions.after(value))
|
18
|
+
reset_version
|
25
19
|
end
|
20
|
+
saved
|
26
21
|
end
|
22
|
+
|
27
23
|
end
|
28
24
|
end
|
@@ -1,69 +1,81 @@
|
|
1
1
|
module VestalVersions
|
2
2
|
# Enables versioned ActiveRecord::Base instances to revert to a previously saved version.
|
3
3
|
module Reversion
|
4
|
-
|
5
|
-
base.class_eval do
|
6
|
-
include InstanceMethods
|
7
|
-
end
|
8
|
-
end
|
4
|
+
extend ActiveSupport::Concern
|
9
5
|
|
10
6
|
# Provides the base instance methods required to revert a versioned instance.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
|
8
|
+
# Returns the current version number for the versioned object.
|
9
|
+
def version
|
10
|
+
@version ||= last_version
|
11
|
+
end
|
12
|
+
|
13
|
+
# Accepts a value corresponding to a specific version record, builds a history of changes
|
14
|
+
# between that version and the current version, and then iterates over that history updating
|
15
|
+
# the object's attributes until the it's reverted to its prior state.
|
16
|
+
#
|
17
|
+
# The single argument should adhere to one of the formats as documented in the +at+ method of
|
18
|
+
# VestalVersions::Versions.
|
19
|
+
#
|
20
|
+
# After the object is reverted to the target version, it is not saved. In order to save the
|
21
|
+
# object after the reversion, use the +revert_to!+ method.
|
22
|
+
#
|
23
|
+
# The version number of the object will reflect whatever version has been reverted to, and
|
24
|
+
# the return value of the +revert_to+ method is also the target version number.
|
25
|
+
def revert_to(value)
|
26
|
+
to_number = versions.number_at(value)
|
27
|
+
|
28
|
+
changes_between(version, to_number).each do |attribute, change|
|
29
|
+
write_attribute(attribute, change.last)
|
15
30
|
end
|
16
31
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
# The version number of the object will reflect whatever version has been reverted to, and
|
28
|
-
# the return value of the +revert_to+ method is also the target version number.
|
29
|
-
def revert_to(value)
|
30
|
-
to_number = versions.number_at(value)
|
32
|
+
reset_version(to_number)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Behaves similarly to the +revert_to+ method except that it automatically saves the record
|
36
|
+
# after the reversion. The return value is the success of the save.
|
37
|
+
def revert_to!(value)
|
38
|
+
revert_to(value)
|
39
|
+
reset_version if saved = save
|
40
|
+
saved
|
41
|
+
end
|
31
42
|
|
32
|
-
|
33
|
-
|
34
|
-
|
43
|
+
# Returns a boolean specifying whether the object has been reverted to a previous version or
|
44
|
+
# if the object represents the latest version in the version history.
|
45
|
+
def reverted?
|
46
|
+
version != last_version
|
47
|
+
end
|
35
48
|
|
36
|
-
|
37
|
-
end
|
49
|
+
private
|
38
50
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
revert_to(value)
|
43
|
-
reset_version if saved = save
|
44
|
-
saved
|
45
|
-
end
|
51
|
+
# Mixes in the reverted_from value if it is currently within a revert
|
52
|
+
def version_attributes
|
53
|
+
attributes = super
|
46
54
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
55
|
+
if @reverted_from.nil?
|
56
|
+
attributes
|
57
|
+
else
|
58
|
+
attributes.merge(:reverted_from => @reverted_from)
|
51
59
|
end
|
60
|
+
end
|
52
61
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
62
|
+
# Returns the number of the last created version in the object's version history.
|
63
|
+
#
|
64
|
+
# If no associated versions exist, the object is considered at version 1.
|
65
|
+
def last_version
|
66
|
+
@last_version ||= versions.maximum(:number) || 1
|
67
|
+
end
|
60
68
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
69
|
+
# Clears the cached version number instance variables so that they can be recalculated.
|
70
|
+
# Useful after a new version is created.
|
71
|
+
def reset_version(version = nil)
|
72
|
+
if version.nil?
|
73
|
+
@last_version = nil
|
74
|
+
@reverted_from = nil
|
75
|
+
else
|
76
|
+
@reverted_from = version
|
77
|
+
end
|
78
|
+
@version = version
|
67
79
|
end
|
68
80
|
end
|
69
81
|
end
|
@@ -2,56 +2,53 @@ module VestalVersions
|
|
2
2
|
# Provides a way for information to be associated with specific versions as to who was
|
3
3
|
# responsible for the associated update to the parent.
|
4
4
|
module Users
|
5
|
-
|
6
|
-
Version.send(:include, VersionMethods)
|
5
|
+
extend ActiveSupport::Concern
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
attr_accessor :updated_by
|
12
|
-
alias_method_chain :version_attributes, :user
|
13
|
-
end
|
7
|
+
included do
|
8
|
+
attr_accessor :updated_by
|
9
|
+
Version.class_eval{ include VersionMethods }
|
14
10
|
end
|
15
11
|
|
16
12
|
# Methods added to versioned ActiveRecord::Base instances to enable versioning with additional
|
17
13
|
# user information.
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
14
|
+
|
15
|
+
|
16
|
+
private
|
17
|
+
# Overrides the +version_attributes+ method to include user information passed into the
|
18
|
+
# parent object, by way of a +updated_by+ attr_accessor.
|
19
|
+
def version_attributes
|
20
|
+
super.merge(:user => updated_by)
|
25
21
|
end
|
22
|
+
end
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
base.class_eval do
|
31
|
-
belongs_to :user, :polymorphic => true
|
24
|
+
# Instance methods added to VestalVersions::Version to accomodate incoming user information.
|
25
|
+
module VersionMethods
|
26
|
+
extend ActiveSupport::Concern
|
32
27
|
|
33
|
-
|
34
|
-
|
35
|
-
end
|
36
|
-
end
|
28
|
+
included do
|
29
|
+
belongs_to :user, :polymorphic => true
|
37
30
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
31
|
+
alias_method_chain :user, :name
|
32
|
+
alias_method_chain :user=, :name
|
33
|
+
end
|
34
|
+
|
35
|
+
# Overrides the +user+ method created by the polymorphic +belongs_to+ user association. If
|
36
|
+
# the association is absent, defaults to the +user_name+ string column. This allows
|
37
|
+
# VestalVersions::Version#user to either return an ActiveRecord::Base object or a string,
|
38
|
+
# depending on what is sent to the +user_with_name=+ method.
|
39
|
+
def user_with_name
|
40
|
+
user_without_name || user_name
|
41
|
+
end
|
45
42
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
43
|
+
# Overrides the +user=+ method created by the polymorphic +belongs_to+ user association.
|
44
|
+
# Based on the class of the object given, either the +user+ association columns or the
|
45
|
+
# +user_name+ string column is populated.
|
46
|
+
def user_with_name=(value)
|
47
|
+
case value
|
48
|
+
when ActiveRecord::Base then self.user_without_name = value
|
49
|
+
else self.user_name = value
|
54
50
|
end
|
55
51
|
end
|
52
|
+
|
56
53
|
end
|
57
54
|
end
|