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.
Files changed (69) hide show
  1. data/.gitignore +19 -20
  2. data/.travis.yml +22 -0
  3. data/CHANGELOG.md +7 -0
  4. data/Gemfile +10 -0
  5. data/README.rdoc +63 -36
  6. data/Rakefile +4 -43
  7. data/gemfiles/activerecord_3_0.gemfile +10 -0
  8. data/gemfiles/activerecord_3_1.gemfile +10 -0
  9. data/gemfiles/activerecord_3_2.gemfile +10 -0
  10. data/gemfiles/activerecord_4_0.gemfile +10 -0
  11. data/lib/generators/vestal_versions.rb +11 -0
  12. data/lib/generators/vestal_versions/migration/migration_generator.rb +17 -0
  13. data/{generators/vestal_versions → lib/generators/vestal_versions/migration}/templates/initializer.rb +0 -0
  14. data/{generators/vestal_versions → lib/generators/vestal_versions/migration}/templates/migration.rb +4 -3
  15. data/lib/vestal_versions.rb +39 -12
  16. data/lib/vestal_versions/changes.rb +43 -47
  17. data/lib/vestal_versions/conditions.rb +31 -43
  18. data/lib/vestal_versions/control.rb +162 -138
  19. data/lib/vestal_versions/creation.rb +67 -59
  20. data/lib/vestal_versions/deletion.rb +37 -0
  21. data/lib/vestal_versions/options.rb +6 -10
  22. data/lib/vestal_versions/reload.rb +7 -14
  23. data/lib/vestal_versions/reset.rb +15 -19
  24. data/lib/vestal_versions/reversion.rb +64 -52
  25. data/lib/vestal_versions/users.rb +36 -39
  26. data/lib/vestal_versions/version.rb +57 -2
  27. data/lib/vestal_versions/version_tagging.rb +51 -0
  28. data/lib/vestal_versions/versioned.rb +14 -17
  29. data/lib/vestal_versions/versions.rb +22 -7
  30. data/spec/spec_helper.rb +28 -0
  31. data/spec/support/models.rb +19 -0
  32. data/spec/support/schema.rb +25 -0
  33. data/spec/vestal_versions/changes_spec.rb +134 -0
  34. data/spec/vestal_versions/conditions_spec.rb +103 -0
  35. data/spec/vestal_versions/control_spec.rb +120 -0
  36. data/spec/vestal_versions/creation_spec.rb +90 -0
  37. data/spec/vestal_versions/deletion_spec.rb +86 -0
  38. data/spec/vestal_versions/options_spec.rb +45 -0
  39. data/spec/vestal_versions/reload_spec.rb +18 -0
  40. data/spec/vestal_versions/reset_spec.rb +111 -0
  41. data/spec/vestal_versions/reversion_spec.rb +103 -0
  42. data/spec/vestal_versions/users_spec.rb +21 -0
  43. data/spec/vestal_versions/version_spec.rb +61 -0
  44. data/spec/vestal_versions/version_tagging_spec.rb +39 -0
  45. data/spec/vestal_versions/versioned_spec.rb +16 -0
  46. data/spec/vestal_versions/versions_spec.rb +176 -0
  47. data/vestal_versions.gemspec +18 -100
  48. metadata +153 -102
  49. data/VERSION +0 -1
  50. data/generators/vestal_versions/vestal_versions_generator.rb +0 -10
  51. data/init.rb +0 -1
  52. data/lib/vestal_versions/configuration.rb +0 -40
  53. data/lib/vestal_versions/tagging.rb +0 -50
  54. data/test/changes_test.rb +0 -169
  55. data/test/conditions_test.rb +0 -137
  56. data/test/configuration_test.rb +0 -39
  57. data/test/control_test.rb +0 -152
  58. data/test/creation_test.rb +0 -110
  59. data/test/options_test.rb +0 -52
  60. data/test/reload_test.rb +0 -19
  61. data/test/reset_test.rb +0 -112
  62. data/test/reversion_test.rb +0 -68
  63. data/test/schema.rb +0 -43
  64. data/test/tagging_test.rb +0 -39
  65. data/test/test_helper.rb +0 -11
  66. data/test/users_test.rb +0 -25
  67. data/test/version_test.rb +0 -43
  68. data/test/versioned_test.rb +0 -18
  69. 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
- def self.included(base) # :nodoc:
6
- base.class_eval do
7
- extend ClassMethods
8
- include InstanceMethods
5
+ extend ActiveSupport::Concern
9
6
 
10
- after_update :create_version, :if => :create_version?
11
- after_update :update_version, :if => :update_version?
12
-
13
- class << self
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>:except</tt> options into +vestal_versions_options+.
23
- def prepare_versioned_options_with_creation(options)
24
- result = prepare_versioned_options_without_creation(options)
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
- module InstanceMethods
35
- private
36
- # Returns whether a new version should be created upon updating the parent record.
37
- def create_version?
38
- !version_changes.blank?
39
- end
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
- # Creates a new version upon updating the parent record.
42
- def create_version
43
- versions.create(version_attributes)
44
- reset_version_changes
45
- reset_version
46
- end
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
- # Returns whether the last version should be updated upon updating the parent record.
49
- # This method is overridden in VestalVersions::Control to account for a control block that
50
- # merges changes onto the previous version.
51
- def update_version?
52
- false
53
- end
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
- # Updates the last version's changes by appending the current version changes.
56
- def update_version
57
- return create_version unless v = versions.last
58
- v.changes_will_change!
59
- v.update_attribute(:changes, v.changes.append_changes(version_changes))
60
- reset_version_changes
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
- # Returns an array of column names that should be included in the changes of created
65
- # versions. If <tt>vestal_versions_options[:only]</tt> is specified, only those columns
66
- # will be versioned. Otherwise, if <tt>vestal_versions_options[:except]</tt> is specified,
67
- # all columns will be versioned other than those specified. Without either option, the
68
- # default is to version all columns. At any rate, the four "automagic" timestamp columns
69
- # maintained by Rails are never versioned.
70
- def versioned_columns
71
- case
72
- when vestal_versions_options[:only] then self.class.column_names & vestal_versions_options[:only]
73
- when vestal_versions_options[:except] then self.class.column_names - vestal_versions_options[:except]
74
- else self.class.column_names
75
- end - %w(created_at created_on updated_at updated_on)
76
- end
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
- # Specifies the attributes used during version creation. This is separated into its own
79
- # method so that it can be overridden by the VestalVersions::Users feature.
80
- def version_attributes
81
- {:changes => version_changes, :number => last_version + 1}
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
- def self.included(base) # :nodoc:
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!(Configuration.options)
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
- :order => "#{options[:class_name].constantize.table_name}.number ASC"
33
- )
27
+ # options.reverse_merge!(
28
+ # :order => "#{options[:class_name].constantize.table_name}.#{connection.quote_column_name('number')} ASC"
29
+ # )
34
30
 
35
- class_inheritable_accessor :vestal_versions_options
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
- def self.included(base) # :nodoc:
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
- module InstanceMethods
15
- # Overrides ActiveRecord::Base#reload, resetting the instance-variable-cached version number
16
- # before performing the original +reload+ method.
17
- def reload_with_versions(*args)
18
- reset_version
19
- reload_without_versions(*args)
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
- def self.included(base) # :nodoc:
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
- module InstanceMethods
12
- # Similar to +revert_to!+, the +reset_to!+ method reverts an object to a previous version,
13
- # only instead of creating a new record in the version history, +reset_to!+ deletes all of
14
- # the version history that occurs after the version reverted to.
15
- #
16
- # The action taken on each version record after the point of reversion is determined by the
17
- # <tt>:dependent</tt> option given to the +versioned+ method. See the +versioned+ method
18
- # documentation for more details.
19
- def reset_to!(value)
20
- if saved = skip_version{ revert_to!(value) }
21
- versions.send(:delete_records, versions.after(value))
22
- reset_version
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
- def self.included(base) # :nodoc:
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
- module InstanceMethods
12
- # Returns the current version number for the versioned object.
13
- def version
14
- @version ||= last_version
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
- # Accepts a value corresponding to a specific version record, builds a history of changes
18
- # between that version and the current version, and then iterates over that history updating
19
- # the object's attributes until the it's reverted to its prior state.
20
- #
21
- # The single argument should adhere to one of the formats as documented in the +at+ method of
22
- # VestalVersions::Versions.
23
- #
24
- # After the object is reverted to the target version, it is not saved. In order to save the
25
- # object after the reversion, use the +revert_to!+ method.
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
- changes_between(version, to_number).each do |attribute, change|
33
- write_attribute(attribute, change.last)
34
- end
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
- reset_version(to_number)
37
- end
49
+ private
38
50
 
39
- # Behaves similarly to the +revert_to+ method except that it automatically saves the record
40
- # after the reversion. The return value is the success of the save.
41
- def revert_to!(value)
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
- # Returns a boolean specifying whether the object has been reverted to a previous version or
48
- # if the object represents the latest version in the version history.
49
- def reverted?
50
- version != last_version
55
+ if @reverted_from.nil?
56
+ attributes
57
+ else
58
+ attributes.merge(:reverted_from => @reverted_from)
51
59
  end
60
+ end
52
61
 
53
- private
54
- # Returns the number of the last created version in the object's version history.
55
- #
56
- # If no associated versions exist, the object is considered at version 1.
57
- def last_version
58
- @last_version ||= versions.maximum(:number) || 1
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
- # Clears the cached version number instance variables so that they can be recalculated.
62
- # Useful after a new version is created.
63
- def reset_version(version = nil)
64
- @last_version = nil if version.nil?
65
- @version = version
66
- end
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
- def self.included(base) # :nodoc:
6
- Version.send(:include, VersionMethods)
5
+ extend ActiveSupport::Concern
7
6
 
8
- base.class_eval do
9
- include InstanceMethods
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
- module InstanceMethods
19
- private
20
- # Overrides the +version_attributes+ method to include user information passed into the
21
- # parent object, by way of a +updated_by+ attr_accessor.
22
- def version_attributes_with_user
23
- version_attributes_without_user.merge(:user => updated_by)
24
- end
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
- # Instance methods added to VestalVersions::Version to accomodate incoming user information.
28
- module VersionMethods
29
- def self.included(base) # :nodoc:
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
- alias_method_chain :user, :name
34
- alias_method_chain :user=, :name
35
- end
36
- end
28
+ included do
29
+ belongs_to :user, :polymorphic => true
37
30
 
38
- # Overrides the +user+ method created by the polymorphic +belongs_to+ user association. If
39
- # the association is absent, defaults to the +user_name+ string column. This allows
40
- # VestalVersions::Version#user to either return an ActiveRecord::Base object or a string,
41
- # depending on what is sent to the +user_with_name=+ method.
42
- def user_with_name
43
- user_without_name || user_name
44
- end
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
- # Overrides the +user=+ method created by the polymorphic +belongs_to+ user association.
47
- # Based on the class of the object given, either the +user+ association columns or the
48
- # +user_name+ string column is populated.
49
- def user_with_name=(value)
50
- case value
51
- when ActiveRecord::Base then self.user_without_name = value
52
- else self.user_name = value
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