simply_versioned 0.9.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES ADDED
@@ -0,0 +1,51 @@
1
+ 0.9.3.1 - 20-05-2008 Jérôme Lipowicz
2
+
3
+ Pragmatic branch (using dirty parameters, rails 2.1 only)
4
+ New records are not versioned: two versions are created at once upon first record update.
5
+ Unchanged records are not versioned too.
6
+
7
+ 0.9.3 - 28-01-2008
8
+
9
+ Fixed #version_number to versioned instances as a shortcut
10
+
11
+ 0.9.2 - 28-01-2008
12
+
13
+ Fixed :exclude => :column now equivalent to :exclude => [:column]
14
+
15
+ 0.9.1 - 24-01-2008
16
+
17
+ Improved implementation of revert_to_version with further tests.
18
+ Provided clean aliases for methods in VersionProxy.
19
+
20
+ 0.9 - 24-01-2008
21
+
22
+ Added an :exclude option (default: [])
23
+
24
+ 0.8 - 08-01-2008
25
+
26
+ Added an :automatic option (default: true)
27
+
28
+ 0.7 - 04-01-2008
29
+
30
+ The without_versioning method becomes with_versioning( exp )
31
+
32
+ 0.6 - 01-01-2008
33
+
34
+ The default is now to keep all versions, specify :keep if you want to set a limit.
35
+ Dropped the updated_at column from revisions as unnecessary.
36
+
37
+ 0.5 - 30-12-2007
38
+
39
+ The revert_to_version call now takes an :except => [:name,:and,:so,:on] list of attributes whose value will not be reverted. The defautl is [:created_at,:updated_at].
40
+
41
+ 0.4 - 30-12-2007
42
+
43
+ Changed some method names to reduce possibility of conflicts. Added more tests. Changed some method implementations w.r.t. empty versions to try and tease out a bug in production.
44
+
45
+ 0.3 - 30-12-2007
46
+
47
+ Added versioning_enabled? and without_versioning( &block ) to allow save without a version being generated.
48
+
49
+ 0.2 - 30-12-2007
50
+
51
+ Added a test & some documentation, shaved a yak or two
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Matt Mower
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 ADDED
@@ -0,0 +1,140 @@
1
+ SimplyVersioned
2
+ ===============
3
+
4
+ Release: 0.9.3.2
5
+ Date: 05-04-2010
6
+ Author: Matt Mower <self@mattmower.com>
7
+ Fork: Jerôme Lipowicz <yayel.com>
8
+
9
+ SimplyVersioned is a simple, non-invasive, approach to versioning ActiveRecord models.
10
+
11
+ SimplyVersioned does not require any structural change to the models to be versioned and requires only one versions table to be created (a migration generator is supplied with the plugin) regardless of the number of models being versioned.
12
+
13
+ The plugin introduces a 'Version' ActiveRecord model (that reflects changes to model attributes) to which versioned models are polymorphically associated. Version records store the model information as a YAML hash.
14
+
15
+ SimplyVersioned meets a simple need for model versioning. If your needs are more complex maybe try Rick Olsen's acts_as_versioned (http://svn.techno-weenie.net/projects/plugins/acts_as_versioned/).
16
+
17
+ SimplyVersioned is only compatible with Rails 2.1. This pragmatic branch only versions altered records.
18
+
19
+ Usage
20
+ =====
21
+
22
+ 1. Install the lib
23
+
24
+ 1.1 as a plugin:
25
+
26
+ ./script/plugin install git://github.com/jerome/simply_versioned.git
27
+
28
+ 1.2 or as a gem:
29
+
30
+ gem install simply_versioned
31
+
32
+ # environment.rb
33
+ config.gem "simply_versioned"
34
+
35
+ 2. Generate the migration
36
+
37
+ ./script/generate simply_versioned_migration
38
+
39
+ Note that the migration defaults to storing the version info in a TEXT field. On MySQL this will default to a
40
+ limit of 64K. If you are versioning particularly large models you will want to modify the migration to include
41
+ a :limit => n condition to promote the yaml column to a MEDIUMTEXT or (god forbid) a LONGTEXT.
42
+
43
+ 3. Create the versions table
44
+
45
+ rake db:migrate
46
+
47
+ 4. Annotate the models you want to version specifying how many versions to keep
48
+
49
+ class Thing < ActiveRecord::Base
50
+ simply_versioned :keep => 10
51
+ end
52
+
53
+ If you do not specify a limit then old versions are never automatically deleted. You can
54
+ manually delete them like this:
55
+
56
+ thing.versions.purge( 10 )
57
+
58
+ which would delete all the but the last ten versions.
59
+
60
+ If you want fine-grained control over when versions are created you can use:
61
+
62
+ class Thing < ActiveRecord::Base
63
+ simply_versioned :automatic => false
64
+ end
65
+
66
+ and new versions will no longer be created by default. You will then need to use
67
+ the with_versioning method to create a version.
68
+
69
+ Lastly you can control which columns will be versioned by specifying an exclude parameter.
70
+
71
+ class Thing < ActiveRecord::Base
72
+ simply_versioned :exclude => :awkward_column
73
+ end
74
+
75
+ or
76
+
77
+ class Thing < ActiveRecord::Base
78
+ simply_versioned :exclude => [:first_awkward_column,:second_awkward_column,...]
79
+ end
80
+
81
+ This may be helpful if you run into conflicts with other plugins which try to manage columns.
82
+
83
+ 5. Create versions
84
+
85
+ thing = Thing.create!( :foo => bar ) # creates v1
86
+ thing.foo = baz
87
+ thing.save! # creates v2
88
+
89
+ If you need to control whether a version is created or not, use #with_versioning. For example:
90
+
91
+ thing.with_versioning( false ) do |t|
92
+ t.save!
93
+ end
94
+
95
+ or, using the "magic pen" (http://dablog.rubypal.com/2007/2/18/the-magic-pens-of-ruby thanks hmj):
96
+
97
+ thing.with_versioning( false, &:save! )
98
+
99
+ 6. Find versions
100
+
101
+ thing.versions.each do |version| ... end
102
+ render :partial => 'thing_version', :collection => thing.versions
103
+ thing.versions.current
104
+ thing.versions.first
105
+ thing.versions.get( 3 )
106
+
107
+ To find a version number:
108
+
109
+ thing.version_number
110
+
111
+ 7. Revert to a previous version
112
+
113
+ thing.revert_to_version( 5 )
114
+
115
+ If a specific reversion needs to avoid overwriting some column values pass
116
+ an :except option, e.g.
117
+
118
+ thing.revert_to_version( 1, :except => [:name,:age] )
119
+
120
+ The revert_to_version method also takes an existing Version instance, e.g.
121
+
122
+ version = thing.versions.find( ... )
123
+ thing.revert_to_version( version )
124
+
125
+ 8. Traverse versions
126
+
127
+ thing.versions.current.previous
128
+ thing.versions.first.next
129
+
130
+ 9. Obtain a copy of a previous versioned model
131
+
132
+ thing.versions.first.model # => Instantiated Thing with versioned values
133
+
134
+ Thanks to:
135
+
136
+ Josh Susser (http://blog.hasmanythrough.com/) for useful suggestions and feedback
137
+ Rick Olson (http://techno-weenie.net/) for all the many plugins whose code i've read
138
+
139
+ Copyright (c) 2007 Matt Mower <self@mattmower.com> and released under the MIT license
140
+ Copyright (c) 2008 Jérôme Lipowicz <yayel.com> and released under the MIT license
@@ -0,0 +1,31 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the simply_versioned plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the simply_versioned plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'SimplyVersioned'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ desc 'Measures test coverage using rcov'
25
+ task :rcov do
26
+ rm_f "coverage"
27
+ rm_f "coverage.data"
28
+ rcov = "rcov --rails --aggregate coverage.data --text-summary -Ilib"
29
+ system("#{rcov} --html #{Dir.glob('test/**/*_test.rb').join(' ')}")
30
+ system("open coverage/index.html") if PLATFORM['darwin']
31
+ end
@@ -0,0 +1,11 @@
1
+ class SimplyVersionedMigrationGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'migration.rb', 'db/migrate'
5
+ end
6
+ end
7
+
8
+ def file_name
9
+ "simply_versioned_migration"
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ class SimplyVersionedMigration < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :versions do |t|
5
+ t.integer :versionable_id
6
+ t.string :versionable_type
7
+ t.integer :number
8
+ t.text :yaml
9
+ t.datetime :created_at
10
+ end
11
+
12
+ add_index :versions, [:versionable_id, :versionable_type]
13
+ end
14
+
15
+ def self.down
16
+ drop_table :versions
17
+ end
18
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'simply_versioned'
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,229 @@
1
+ # SimplyVersioned 0.9.3
2
+ #
3
+ # Simple ActiveRecord versioning
4
+ # Copyright (c) 2007,2008 Matt Mower <self@mattmower.com>
5
+ # Released under the MIT license (see accompany MIT-LICENSE file)
6
+ #
7
+
8
+ module SoftwareHeretics
9
+
10
+ module ActiveRecord
11
+
12
+ module SimplyVersioned
13
+
14
+ class BadOptions < StandardError
15
+ def initialize( keys )
16
+ super( "Keys: #{keys.join( "," )} are not known by SimplyVersioned" )
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+
22
+ # Marks this ActiveRecord model as being versioned. Calls to +create+ or +save+ will,
23
+ # in future, create a series of associated Version instances that can be accessed via
24
+ # the +versions+ association.
25
+ #
26
+ # Options:
27
+ # +limit+ - specifies the number of old versions to keep (default = nil, never delete old versions)
28
+ # +automatic+ - controls whether versions are created automatically (default = true, save versions)
29
+ # +exclude+ - specify columns that will not be saved (default = [], save all columns)
30
+ #
31
+ # To save the record without creating a version either set +versioning_enabled+ to false
32
+ # on the model before calling save or, alternatively, use +without_versioning+ and save
33
+ # the model from its block.
34
+ #
35
+
36
+ def simply_versioned( options = {} )
37
+ bad_keys = options.keys - [:keep,:automatic,:exclude]
38
+ raise SimplyVersioned::BadOptions.new( bad_keys ) unless bad_keys.empty?
39
+
40
+ options.reverse_merge!( {
41
+ :keep => nil,
42
+ :automatic => true,
43
+ :exclude => [:updated_at, :position]
44
+ })
45
+
46
+ has_many :versions, :order => 'number DESC', :as => :versionable, :dependent => :destroy, :extend => VersionsProxyMethods
47
+
48
+ before_save :save_new_record_status
49
+ before_save :simply_versioned_keep_dirty_version
50
+ after_save :simply_versioned_create_versions
51
+ after_save :unset_new_record_status
52
+
53
+ cattr_accessor :simply_versioned_keep_limit
54
+ self.simply_versioned_keep_limit = options[:keep]
55
+
56
+ cattr_accessor :simply_versioned_save_by_default
57
+ self.simply_versioned_save_by_default = options[:automatic]
58
+
59
+ cattr_accessor :simply_versioned_excluded_columns
60
+ self.simply_versioned_excluded_columns = Array( options[ :exclude ] ).map( &:to_s )
61
+
62
+ class_eval do
63
+ def versioning_enabled=( enabled )
64
+ self.instance_variable_set( :@simply_versioned_enabled, enabled )
65
+ end
66
+
67
+ def versioning_enabled?
68
+ enabled = self.instance_variable_get( :@simply_versioned_enabled )
69
+ if enabled.nil?
70
+ enabled = self.instance_variable_set( :@simply_versioned_enabled, self.simply_versioned_save_by_default )
71
+ end
72
+ enabled
73
+ end
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ # Methods that will be defined on the ActiveRecord model being versioned
80
+ module InstanceMethods
81
+
82
+ # Revert the attributes of this model to their values as of an earlier version.
83
+ #
84
+ # Pass either a Version instance or a version number.
85
+ #
86
+ # options:
87
+ # +except+ specify a list of attributes that are not restored (default: created_at, updated_at)
88
+ #
89
+ def revert_to_version( version, options = {} )
90
+ options.reverse_merge!({
91
+ :except => [:created_at,:updated_at]
92
+ })
93
+
94
+ version = if version.kind_of?( Version )
95
+ version
96
+ elsif version.kind_of?( Fixnum )
97
+ self.versions.find_by_number( version )
98
+ end
99
+
100
+ raise "Invalid version (#{version.inspect}) specified!" unless version
101
+
102
+ options[:except] = options[:except].map( &:to_s )
103
+
104
+ self.update_attributes( YAML::load( version.yaml ).except( *options[:except] ) )
105
+ end
106
+
107
+ # Invoke the supplied block passing the receiver as the sole block argument with
108
+ # versioning enabled or disabled depending upon the value of the +enabled+ parameter
109
+ # for the duration of the block.
110
+ def with_versioning( enabled, &block )
111
+ versioning_was_enabled = self.versioning_enabled?
112
+ self.versioning_enabled = enabled
113
+ begin
114
+ block.call( self )
115
+ ensure
116
+ self.versioning_enabled = versioning_was_enabled
117
+ end
118
+ end
119
+
120
+ def unversioned?
121
+ self.versions.nil? || self.versions.size == 0
122
+ end
123
+
124
+ def versioned?
125
+ !unversioned?
126
+ end
127
+
128
+ def version_number
129
+ if self.versions.empty?
130
+ 0
131
+ else
132
+ self.versions.current.number
133
+ end
134
+ end
135
+
136
+ protected
137
+
138
+ def save_new_record_status
139
+ @was_new_record = self.new_record?
140
+ true
141
+ end
142
+
143
+ def unset_new_record_status
144
+ @was_new_record = false
145
+ true
146
+ end
147
+
148
+ def simply_versioned_keep_dirty_version
149
+ changed_attr = self.changes.except(*simply_versioned_excluded_columns)
150
+ @dirty_attributes = if self.versioning_enabled? && !self.new_record? && !changed_attr.empty?
151
+ changed_attr.each {|k,v| changed_attr[k] = v.first }
152
+ else
153
+ { }
154
+ end
155
+ true
156
+ end
157
+
158
+ def simply_versioned_create_versions
159
+ # new or unchanged records are not versioned while first time
160
+ # versioning will create two versions in the same time
161
+ unless @was_new_record
162
+ if self.versioning_enabled? && !@dirty_attributes.empty?
163
+ if self.unversioned?
164
+ self.versions.create( :yaml => self.attributes.except( *simply_versioned_excluded_columns).merge(@dirty_attributes).to_yaml )
165
+ end
166
+
167
+ if self.versions.create( :yaml => self.attributes.except( *simply_versioned_excluded_columns ).to_yaml )
168
+ self.versions.clean_old_versions( simply_versioned_keep_limit.to_i ) if simply_versioned_keep_limit
169
+ end
170
+ end
171
+ end
172
+ true
173
+ end
174
+
175
+ end
176
+
177
+ module VersionsProxyMethods
178
+
179
+ # Get the Version instance corresponding to this models for the specified version number.
180
+ def get_version( number )
181
+ find_by_number( number )
182
+ end
183
+ alias_method :get, :get_version
184
+
185
+ # Get the first Version corresponding to this model.
186
+ def first_version
187
+ find( :first, :order => 'number ASC' )
188
+ end
189
+ alias_method :first, :first_version
190
+
191
+ # Get the current Version corresponding to this model.
192
+ def current_version
193
+ find( :first, :order => 'number DESC' )
194
+ end
195
+ alias_method :current, :current_version
196
+
197
+ # If the model instance has more versions than the limit specified, delete all excess older versions.
198
+ def clean_old_versions( versions_to_keep )
199
+ find( :all, :conditions => [ 'number <= ?', self.maximum( :number ) - versions_to_keep ] ).each do |version|
200
+ version.destroy
201
+ end
202
+ end
203
+ alias_method :purge, :clean_old_versions
204
+
205
+ # Return the Version for this model with the next higher version
206
+ def next_version( number )
207
+ find( :first, :order => 'number ASC', :conditions => [ "number > ?", number ] )
208
+ end
209
+ alias_method :next, :next_version
210
+
211
+ # Return the Version for this model with the next lower version
212
+ def previous_version( number )
213
+ find( :first, :order => 'number DESC', :conditions => [ "number < ?", number ] )
214
+ end
215
+ alias_method :previous, :previous_version
216
+ end
217
+
218
+ def self.included( receiver )
219
+ receiver.extend ClassMethods
220
+ receiver.send :include, InstanceMethods
221
+ end
222
+
223
+ end
224
+
225
+ end
226
+
227
+ end
228
+
229
+ ActiveRecord::Base.send( :include, SoftwareHeretics::ActiveRecord::SimplyVersioned )