decisiv-acts_as_versioned 0.6.1

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/CHANGELOG ADDED
@@ -0,0 +1,102 @@
1
+
2
+ [master]
3
+
4
+ *
5
+
6
+ [0.6.1]
7
+
8
+ * (29 Dec 2008) Remove #versions_count really. [Ken Collins]
9
+ * (30 Dec 2008) Add assert_sql support in boot.rb. Also changed core lib to order by the has many versions
10
+ association with the version_column, support with tests. [Ken Collins]
11
+
12
+ [0.6]
13
+
14
+ * (23 Dec 2008) Clean up old old code and start move towards new gem [Ken Collins]
15
+ * Starting a .gitignore file.
16
+ * Moving to a test/lib based directory structure with a boot and AAV test case based from ActiveRecord's case.
17
+ * Create rake test and test_dbs tasks.
18
+ * Create a gemspec for publishing as a gem.
19
+
20
+ [0.5.2]
21
+
22
+ * (16 Jun 2008) Backwards Compatibility is overrated (big updates for rails 2.1)
23
+ * Use ActiveRecord 2.1's dirty attribute checking instead [Asa Calow]
24
+ * Remove last traces of #non_versioned_fields
25
+ * Remove AR::Base.find_version and AR::Base.find_versions, rely on AR association proxies and named_scope
26
+ * Remove #versions_count, rely on AR association counter caching.
27
+ * Remove #versioned_attributes, basically the same as AR::Base.versioned_columns
28
+
29
+ * (5 Oct 2006) Allow customization of #versions association options [Dan Peterson]
30
+
31
+ [0.5.1]
32
+
33
+ * (8 Aug 2006) Versioned models now belong to the unversioned model. @article_version.article.class => Article [Aslak Hellesoy]
34
+
35
+ [0.5]
36
+
37
+ * (21 Apr 2006) Added without_locking and without_revision methods.
38
+ Foo.without_revision do
39
+ @foo.update_attributes ...
40
+ end
41
+
42
+ [0.4]
43
+
44
+ * (28 March 2006) Rename non_versioned_fields to non_versioned_columns (old one is kept for compatibility).
45
+ * (28 March 2006) Made explicit documentation note that string column names are required for non_versioned_columns.
46
+
47
+ [0.3.1]
48
+
49
+ * (7 Jan 2006) explicitly set :foreign_key option for the versioned model's belongs_to assocation for STI [Caged]
50
+ * (7 Jan 2006) added tests to prove has_many :through joins work
51
+
52
+ [0.3]
53
+
54
+ * (2 Jan 2006) added ability to share a mixin with versioned class
55
+ * (2 Jan 2006) changed the dynamic version model to MyModel::Version
56
+
57
+ [0.2.4]
58
+
59
+ * (27 Nov 2005) added note about possible destructive behavior of if_changed? [Michael Schuerig]
60
+
61
+ [0.2.3]
62
+
63
+ * (12 Nov 2005) fixed bug with old behavior of #blank? [Michael Schuerig]
64
+ * (12 Nov 2005) updated tests to use ActiveRecord Schema
65
+
66
+ [0.2.2]
67
+
68
+ * (3 Nov 2005) added documentation note to #acts_as_versioned [Martin Jul]
69
+
70
+ [0.2.1]
71
+
72
+ * (6 Oct 2005) renamed dirty? to changed? to keep it uniform. it was aliased to keep it backwards compatible.
73
+
74
+ [0.2]
75
+
76
+ * (6 Oct 2005) added find_versions and find_version class methods.
77
+
78
+ * (6 Oct 2005) removed transaction from create_versioned_table().
79
+ this way you can specify your own transaction around a group of operations.
80
+
81
+ * (30 Sep 2005) fixed bug where find_versions() would order by 'version' twice. (found by Joe Clark)
82
+
83
+ * (26 Sep 2005) added :sequence_name option to acts_as_versioned to set the sequence name on the versioned model
84
+
85
+ [0.1.3]
86
+
87
+ * (18 Sep 2005) First RubyForge release
88
+
89
+ [0.1.2]
90
+
91
+ * check if module is already included when acts_as_versioned is called
92
+
93
+ [0.1.1]
94
+
95
+ * Adding tests and rdocs
96
+
97
+ [0.1]
98
+
99
+ * Initial transfer from Rails ticket: http://dev.rubyonrails.com/ticket/1974
100
+
101
+
102
+
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2005 Rick Olson
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,24 @@
1
+
2
+ = MetaSkills ActsAsVersioned
3
+
4
+ A fork of Rick Olson's ActsAsVersioned plugin that has been gem'ized along with a few features I need. This library
5
+ adds simple versioning to an ActiveRecord class which of course requires ActiveRecord for use.
6
+
7
+ Has tests for many DBs in ActiveRecord version 2.2.2
8
+
9
+
10
+ == Resources
11
+
12
+ Install the gem manually.
13
+
14
+ $ sudo gem install metaskills-acts_as_versioned
15
+
16
+ Or with a rails config.gem dependency.
17
+
18
+ config.gem 'metaskills-acts_as_versioned', :lib => 'acts_as_versioned', :source => 'http://gems.github.com/'
19
+
20
+
21
+ The git repository is located here:
22
+
23
+ * http://github.com/metaskills/acts_as_versioned/tree/master
24
+
@@ -0,0 +1,21 @@
1
+
2
+ == Creating the test database
3
+
4
+ The default name for the test databases is can be found in the test/lib/database.yml files. By
5
+ default the tests will run on sqlite3 but can be changed using and ENV['DB'] variable like postresql,
6
+ mysql or even sqlserver.
7
+
8
+ == Running with Rake
9
+
10
+ The easiest way to run the unit tests is through Rake. The default task runs all the test cases.
11
+ There is even a rake task called test_dbs that will run all test cases for each DB.
12
+
13
+ == Running by hand
14
+
15
+ Unit tests are located in test directory. If you only want to run a single test suite,
16
+ or don't want to bother with Rake, you can do so with something like:
17
+
18
+ cd test ; ruby -I "test" versioned_test.rb
19
+
20
+ Likewise you can just hit Command-R if you are using TextMate.
21
+
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'rake/testtask'
3
+
4
+ desc 'Default: Test ActsAsVersioned with default ActiveRecord version.'
5
+ task :default => :test
6
+
7
+ desc 'Test ActsAsVersioned with default ActiveRecord version.'
8
+ Rake::TestTask.new(:test) do |t|
9
+ t.libs << 'lib' << 'test'
10
+ t.pattern = 'test/**/*_test.rb'
11
+ t.verbose = true
12
+ end
13
+
14
+ desc 'Test ActsAsVersioned with all databases.'
15
+ task :test_dbs do
16
+ test = Rake::Task['test']
17
+ dbs = ['sqlite3','postgresql','mysql','sqlserver']
18
+ dbs.each do |db|
19
+ ENV['DB'] = db
20
+ test.invoke
21
+ test.reenable
22
+ end
23
+ end
24
+
@@ -0,0 +1,492 @@
1
+ # Copyright (c) 2005 Rick Olson
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.
21
+
22
+ module ActiveRecord #:nodoc:
23
+ module Acts #:nodoc:
24
+ # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a
25
+ # versioned table ready and that your model has a version field. This works with optimistic locking if the
26
+ # lock_version column is present as well.
27
+ #
28
+ # The class for the versioned model is derived the first time it is seen. Therefore, if you change your
29
+ # database schema you have to restart your container for the changes to be reflected. In development mode
30
+ # this usually means restarting WEBrick.
31
+ #
32
+ # class Page < ActiveRecord::Base
33
+ # # assumes pages_versions table
34
+ # acts_as_versioned
35
+ # end
36
+ #
37
+ # Example:
38
+ #
39
+ # page = Page.create(:title => 'hello world!')
40
+ # page.version # => 1
41
+ #
42
+ # page.title = 'hello world'
43
+ # page.save
44
+ # page.version # => 2
45
+ # page.versions.size # => 2
46
+ #
47
+ # page.revert_to(1) # using version number
48
+ # page.title # => 'hello world!'
49
+ #
50
+ # page.revert_to(page.versions.last) # using versioned instance
51
+ # page.title # => 'hello world'
52
+ #
53
+ # page.versions.earliest # efficient query to find the first version
54
+ # page.versions.latest # efficient query to find the most recently created version
55
+ #
56
+ #
57
+ # Simple Queries to page between versions
58
+ #
59
+ # page.versions.before(version)
60
+ # page.versions.after(version)
61
+ #
62
+ # Access the previous/next versions from the versioned model itself
63
+ #
64
+ # version = page.versions.latest
65
+ # version.previous # go back one version
66
+ # version.next # go forward one version
67
+ #
68
+ # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options
69
+ module Versioned
70
+ CALLBACKS = [:set_new_version, :save_version, :save_version?]
71
+ def self.included(base) # :nodoc:
72
+ base.extend ClassMethods
73
+ end
74
+
75
+ module ClassMethods
76
+ # == Configuration options
77
+ #
78
+ # * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
79
+ # * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
80
+ # * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
81
+ # * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
82
+ # * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
83
+ # * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
84
+ # * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
85
+ # * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved.
86
+ # For finer control, pass either a Proc or modify Model#version_condition_met?
87
+ #
88
+ # acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
89
+ #
90
+ # or...
91
+ #
92
+ # class Auction
93
+ # def version_condition_met? # totally bypasses the <tt>:if</tt> option
94
+ # !expired?
95
+ # end
96
+ # end
97
+ #
98
+ # * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
99
+ # either a symbol or array of symbols.
100
+ #
101
+ # * <tt>extend</tt> - Lets you specify a module to be mixed in both the original and versioned models. You can also just pass a block
102
+ # to create an anonymous mixin:
103
+ #
104
+ # class Auction
105
+ # acts_as_versioned do
106
+ # def started?
107
+ # !started_at.nil?
108
+ # end
109
+ # end
110
+ # end
111
+ #
112
+ # or...
113
+ #
114
+ # module AuctionExtension
115
+ # def started?
116
+ # !started_at.nil?
117
+ # end
118
+ # end
119
+ # class Auction
120
+ # acts_as_versioned :extend => AuctionExtension
121
+ # end
122
+ #
123
+ # Example code:
124
+ #
125
+ # @auction = Auction.find(1)
126
+ # @auction.started?
127
+ # @auction.versions.first.started?
128
+ #
129
+ # == Database Schema
130
+ #
131
+ # The model that you're versioning needs to have a 'version' attribute. The model is versioned into a
132
+ # table called #{model}_versions where the model name is singlular. The _versions table should contain
133
+ # all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
134
+ #
135
+ # A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single
136
+ # Table inheritance, then that field is reflected in the versioned model as 'versioned_type' by default.
137
+ #
138
+ # Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table
139
+ # method, perfect for a migration. It will also create the version column if the main model does not
140
+ # already have it.
141
+ #
142
+ # class AddVersions < ActiveRecord::Migration
143
+ # def self.up
144
+ # # create_versioned_table takes the same options hash
145
+ # # that create_table does
146
+ # Post.create_versioned_table
147
+ # end
148
+ #
149
+ # def self.down
150
+ # Post.drop_versioned_table
151
+ # end
152
+ # end
153
+ #
154
+ # == Changing What Fields Are Versioned
155
+ #
156
+ # By default, acts_as_versioned will version all but these fields:
157
+ #
158
+ # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
159
+ #
160
+ # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols.
161
+ #
162
+ # class Post < ActiveRecord::Base
163
+ # acts_as_versioned
164
+ # self.non_versioned_columns << 'comments_count'
165
+ # end
166
+ #
167
+ def acts_as_versioned(options = {}, &extension)
168
+ # don't allow multiple calls
169
+ return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods)
170
+
171
+ send :include, ActiveRecord::Acts::Versioned::ActMethods
172
+
173
+ cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
174
+ :version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns,
175
+ :version_association_options, :version_if_changed
176
+
177
+ self.versioned_class_name = options[:class_name] || "Version"
178
+ self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
179
+ self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
180
+ self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
181
+ self.version_column = options[:version_column] || 'version'
182
+ self.version_sequence_name = options[:sequence_name]
183
+ self.max_version_limit = options[:limit].to_i
184
+ self.version_condition = options[:if] || true
185
+ self.non_versioned_columns = [self.primary_key, inheritance_column, self.version_column, 'lock_version', versioned_inheritance_column, 'created_at', 'created_on'] + options[:non_versioned_columns].to_a.map(&:to_s)
186
+ self.version_association_options = {
187
+ :class_name => "#{self.to_s}::#{versioned_class_name}",
188
+ :foreign_key => versioned_foreign_key,
189
+ :order => "#{version_column} ASC",
190
+ :dependent => :delete_all
191
+ }.merge(options[:association_options] || {})
192
+
193
+ if block_given?
194
+ extension_module_name = "#{versioned_class_name}Extension"
195
+ silence_warnings do
196
+ self.const_set(extension_module_name, Module.new(&extension))
197
+ end
198
+
199
+ options[:extend] = self.const_get(extension_module_name)
200
+ end
201
+
202
+ class_eval <<-CLASS_METHODS
203
+ has_many :versions, version_association_options do
204
+ # finds earliest version of this record
205
+ def earliest
206
+ @earliest ||= find(:first, :order => '#{version_column}')
207
+ end
208
+
209
+ # find latest version of this record
210
+ def latest
211
+ @latest ||= find(:first, :order => '#{version_column} desc')
212
+ end
213
+ end
214
+ before_save :set_new_version
215
+ after_save :save_version
216
+ after_save :clear_old_versions
217
+
218
+ unless options[:if_changed].nil?
219
+ self.track_altered_attributes = true
220
+ options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
221
+ self.version_if_changed = options[:if_changed].map(&:to_s)
222
+ end
223
+
224
+ include options[:extend] if options[:extend].is_a?(Module)
225
+ CLASS_METHODS
226
+
227
+ # create the dynamic versioned model
228
+ const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
229
+ def self.reloadable? ; false ; end
230
+ # find first version before the given version
231
+ def self.before(version)
232
+ find :first, :order => 'version desc',
233
+ :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]
234
+ end
235
+
236
+ # find first version after the given version.
237
+ def self.after(version)
238
+ find :first, :order => 'version',
239
+ :conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]
240
+ end
241
+
242
+ def previous
243
+ self.class.before(self)
244
+ end
245
+
246
+ def next
247
+ self.class.after(self)
248
+ end
249
+
250
+ end
251
+
252
+ versioned_class.cattr_accessor :original_class
253
+ versioned_class.original_class = self
254
+ versioned_class.set_table_name versioned_table_name
255
+ versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
256
+ :class_name => "::#{self.to_s}",
257
+ :foreign_key => versioned_foreign_key
258
+ versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
259
+ versioned_class.set_sequence_name version_sequence_name if version_sequence_name
260
+
261
+ create_versioned_table
262
+ end
263
+ end
264
+
265
+ module ActMethods
266
+ def self.included(base) # :nodoc:
267
+ base.extend ClassMethods
268
+ end
269
+
270
+ # Saves a version of the model in the versioned table. This is called in the after_save callback by default
271
+ def save_version
272
+ if @saving_version
273
+ @saving_version = nil
274
+ rev = self.class.versioned_class.new
275
+ clone_versioned_model(self, rev)
276
+ rev.send("#{self.class.version_column}=", send(self.class.version_column))
277
+ rev.send("#{self.class.versioned_foreign_key}=", id)
278
+ rev.save
279
+ end
280
+ end
281
+
282
+ # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
283
+ # Override this method to set your own criteria for clearing old versions.
284
+ def clear_old_versions
285
+ return if self.class.max_version_limit == 0
286
+ excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
287
+ if excess_baggage > 0
288
+ self.class.versioned_class.delete_all ["#{self.class.version_column} <= ? and #{self.class.versioned_foreign_key} = ?", excess_baggage, id]
289
+ end
290
+ end
291
+
292
+ # Reverts a model to a given version. Takes either a version number or an instance of the versioned model
293
+ def revert_to(version)
294
+ if version.is_a?(self.class.versioned_class)
295
+ return false unless version.send(self.class.versioned_foreign_key) == id and !version.new_record?
296
+ else
297
+ return false unless version = versions.send("find_by_#{self.class.version_column}", version)
298
+ end
299
+ self.clone_versioned_model(version, self)
300
+ send("#{self.class.version_column}=", version.send(self.class.version_column))
301
+ true
302
+ end
303
+
304
+ # Reverts a model to a given version and saves the model.
305
+ # Takes either a version number or an instance of the versioned model
306
+ def revert_to!(version)
307
+ revert_to(version) ? save_without_revision : false
308
+ end
309
+
310
+ # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
311
+ def save_without_revision
312
+ save_without_revision!
313
+ true
314
+ rescue
315
+ false
316
+ end
317
+
318
+ def save_without_revision!
319
+ without_locking do
320
+ without_revision do
321
+ save!
322
+ end
323
+ end
324
+ end
325
+
326
+ def altered?
327
+ track_altered_attributes ? (version_if_changed - changed).length < version_if_changed.length : changed?
328
+ end
329
+
330
+ # Clones a model. Used when saving a new version or reverting a model's version.
331
+ def clone_versioned_model(orig_model, new_model)
332
+ self.class.versioned_columns.each do |col|
333
+ new_model.send("#{col.name}=", orig_model.send(col.name)) if orig_model.has_attribute?(col.name)
334
+ end
335
+
336
+ if orig_model.is_a?(self.class.versioned_class)
337
+ new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
338
+ elsif new_model.is_a?(self.class.versioned_class)
339
+ new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
340
+ end
341
+ end
342
+
343
+ # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
344
+ def save_version?
345
+ version_condition_met? && altered?
346
+ end
347
+
348
+ # Checks condition set in the :if option to check whether a revision should be created or not. Override this for
349
+ # custom version condition checking.
350
+ def version_condition_met?
351
+ case
352
+ when version_condition.is_a?(Symbol)
353
+ send(version_condition)
354
+ when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
355
+ version_condition.call(self)
356
+ else
357
+ version_condition
358
+ end
359
+ end
360
+
361
+ # Executes the block with the versioning callbacks disabled.
362
+ #
363
+ # @foo.without_revision do
364
+ # @foo.save
365
+ # end
366
+ #
367
+ def without_revision(&block)
368
+ self.class.without_revision(&block)
369
+ end
370
+
371
+ # Turns off optimistic locking for the duration of the block
372
+ #
373
+ # @foo.without_locking do
374
+ # @foo.save
375
+ # end
376
+ #
377
+ def without_locking(&block)
378
+ self.class.without_locking(&block)
379
+ end
380
+
381
+ def empty_callback() end #:nodoc:
382
+
383
+ protected
384
+ # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
385
+ def set_new_version
386
+ @saving_version = new_record? || save_version?
387
+ self.send("#{self.class.version_column}=", next_version) if new_record? || (!locking_enabled? && save_version?)
388
+ end
389
+
390
+ # Gets the next available version for the current record, or 1 for a new record
391
+ def next_version
392
+ (new_record? ? 0 : versions.calculate(:max, version_column).to_i) + 1
393
+ end
394
+
395
+ module ClassMethods
396
+ # Returns an array of columns that are versioned. See non_versioned_columns
397
+ def versioned_columns
398
+ @versioned_columns ||= columns.select { |c| !non_versioned_columns.include?(c.name) }
399
+ end
400
+
401
+ # Returns an instance of the dynamic versioned model
402
+ def versioned_class
403
+ const_get versioned_class_name
404
+ end
405
+
406
+ # Rake migration task to create the versioned table using options passed to acts_as_versioned
407
+ def create_versioned_table(create_table_options = {})
408
+ # create version column in main table if it does not exist
409
+ if !self.content_columns.find { |c| [version_column.to_s, 'lock_version'].include? c.name }
410
+ self.connection.add_column table_name, version_column, :integer
411
+ self.reset_column_information
412
+ end
413
+
414
+ return if connection.tables.include?(versioned_table_name.to_s)
415
+
416
+ self.connection.create_table(versioned_table_name, create_table_options) do |t|
417
+ t.column versioned_foreign_key, :integer
418
+ t.column version_column, :integer
419
+ end
420
+
421
+ updated_col = nil
422
+ self.versioned_columns.each do |col|
423
+ updated_col = col if !updated_col && %(updated_at updated_on).include?(col.name)
424
+ self.connection.add_column versioned_table_name, col.name, col.type,
425
+ :limit => col.limit,
426
+ :default => col.default,
427
+ :scale => col.scale,
428
+ :precision => col.precision
429
+ end
430
+
431
+ if type_col = self.columns_hash[inheritance_column]
432
+ self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
433
+ :limit => type_col.limit,
434
+ :default => type_col.default,
435
+ :scale => type_col.scale,
436
+ :precision => type_col.precision
437
+ end
438
+
439
+ if updated_col.nil?
440
+ self.connection.add_column versioned_table_name, :updated_at, :timestamp
441
+ end
442
+
443
+ self.connection.add_index versioned_table_name, versioned_foreign_key
444
+ end
445
+
446
+ # Rake migration task to drop the versioned table
447
+ def drop_versioned_table
448
+ self.connection.drop_table versioned_table_name
449
+ end
450
+
451
+ # Executes the block with the versioning callbacks disabled.
452
+ #
453
+ # Foo.without_revision do
454
+ # @foo.save
455
+ # end
456
+ #
457
+ def without_revision(&block)
458
+ class_eval do
459
+ CALLBACKS.each do |attr_name|
460
+ alias_method "orig_#{attr_name}".to_sym, attr_name
461
+ alias_method attr_name, :empty_callback
462
+ end
463
+ end
464
+ block.call
465
+ ensure
466
+ class_eval do
467
+ CALLBACKS.each do |attr_name|
468
+ alias_method attr_name, "orig_#{attr_name}".to_sym
469
+ end
470
+ end
471
+ end
472
+
473
+ # Turns off optimistic locking for the duration of the block
474
+ #
475
+ # Foo.without_locking do
476
+ # @foo.save
477
+ # end
478
+ #
479
+ def without_locking(&block)
480
+ current = ActiveRecord::Base.lock_optimistically
481
+ ActiveRecord::Base.lock_optimistically = false if current
482
+ result = block.call
483
+ ActiveRecord::Base.lock_optimistically = true if current
484
+ result
485
+ end
486
+ end
487
+ end
488
+ end
489
+ end
490
+ end
491
+
492
+ ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned