acts_as_versioned 0.2.3 → 0.6.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/CHANGELOG +45 -0
- data/Gemfile +7 -0
- data/Rakefile +146 -0
- data/acts_as_versioned.gemspec +85 -0
- data/init.rb +1 -0
- data/lib/acts_as_versioned.rb +388 -291
- data/test/abstract_unit.rb +24 -19
- data/test/database.yml +14 -14
- data/test/fixtures/authors.yml +6 -0
- data/test/fixtures/landmark.rb +3 -0
- data/test/fixtures/landmark_versions.yml +7 -0
- data/test/fixtures/landmarks.yml +7 -0
- data/test/fixtures/locked_pages_revisions.yml +4 -4
- data/test/fixtures/migrations/1_add_versioned_tables.rb +2 -0
- data/test/fixtures/page.rb +26 -7
- data/test/fixtures/page_versions.yml +4 -0
- data/test/fixtures/pages.yml +3 -0
- data/test/fixtures/widget.rb +4 -1
- data/test/migration_test.rb +19 -5
- data/test/schema.rb +41 -2
- data/test/versioned_test.rb +214 -82
- metadata +108 -69
data/CHANGELOG
CHANGED
@@ -1,3 +1,48 @@
|
|
1
|
+
*GIT* (version numbers are overrated)
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
* (16 Jun 2008) Backwards Compatibility is overrated (big updates for rails 2.1)
|
6
|
+
|
7
|
+
* Use ActiveRecord 2.1's dirty attribute checking instead [Asa Calow]
|
8
|
+
* Remove last traces of #non_versioned_fields
|
9
|
+
* Remove AR::Base.find_version and AR::Base.find_versions, rely on AR association proxies and named_scope
|
10
|
+
* Remove #versions_count, rely on AR association counter caching.
|
11
|
+
* Remove #versioned_attributes, basically the same as AR::Base.versioned_columns
|
12
|
+
|
13
|
+
* (5 Oct 2006) Allow customization of #versions association options [Dan Peterson]
|
14
|
+
|
15
|
+
*0.5.1*
|
16
|
+
|
17
|
+
* (8 Aug 2006) Versioned models now belong to the unversioned model. @article_version.article.class => Article [Aslak Hellesoy]
|
18
|
+
|
19
|
+
*0.5* # do versions even matter for plugins?
|
20
|
+
|
21
|
+
* (21 Apr 2006) Added without_locking and without_revision methods.
|
22
|
+
|
23
|
+
Foo.without_revision do
|
24
|
+
@foo.update_attributes ...
|
25
|
+
end
|
26
|
+
|
27
|
+
*0.4*
|
28
|
+
|
29
|
+
* (28 March 2006) Rename non_versioned_fields to non_versioned_columns (old one is kept for compatibility).
|
30
|
+
* (28 March 2006) Made explicit documentation note that string column names are required for non_versioned_columns.
|
31
|
+
|
32
|
+
*0.3.1*
|
33
|
+
|
34
|
+
* (7 Jan 2006) explicitly set :foreign_key option for the versioned model's belongs_to assocation for STI [Caged]
|
35
|
+
* (7 Jan 2006) added tests to prove has_many :through joins work
|
36
|
+
|
37
|
+
*0.3*
|
38
|
+
|
39
|
+
* (2 Jan 2006) added ability to share a mixin with versioned class
|
40
|
+
* (2 Jan 2006) changed the dynamic version model to MyModel::Version
|
41
|
+
|
42
|
+
*0.2.4*
|
43
|
+
|
44
|
+
* (27 Nov 2005) added note about possible destructive behavior of if_changed? [Michael Schuerig]
|
45
|
+
|
1
46
|
*0.2.3*
|
2
47
|
|
3
48
|
* (12 Nov 2005) fixed bug with old behavior of #blank? [Michael Schuerig]
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/testtask'
|
49
|
+
Rake::TestTask.new(:test) do |test|
|
50
|
+
test.libs << 'lib' << 'test'
|
51
|
+
test.pattern = 'test/**/*_test.rb'
|
52
|
+
test.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Generate RCov test coverage and open in your browser"
|
56
|
+
task :coverage do
|
57
|
+
require 'rcov'
|
58
|
+
sh "rm -fr coverage"
|
59
|
+
sh "rcov test/test_*.rb"
|
60
|
+
sh "open coverage/index.html"
|
61
|
+
end
|
62
|
+
|
63
|
+
require 'rake/rdoctask'
|
64
|
+
Rake::RDocTask.new do |rdoc|
|
65
|
+
rdoc.rdoc_dir = 'rdoc'
|
66
|
+
rdoc.title = "#{name} #{version}"
|
67
|
+
rdoc.rdoc_files.include('README*')
|
68
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "Open an irb session preloaded with this library"
|
72
|
+
task :console do
|
73
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
74
|
+
end
|
75
|
+
|
76
|
+
#############################################################################
|
77
|
+
#
|
78
|
+
# Custom tasks (add your own tasks here)
|
79
|
+
#
|
80
|
+
#############################################################################
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
#############################################################################
|
85
|
+
#
|
86
|
+
# Packaging tasks
|
87
|
+
#
|
88
|
+
#############################################################################
|
89
|
+
|
90
|
+
task :release => :build do
|
91
|
+
unless `git branch` =~ /^\* master$/
|
92
|
+
puts "You must be on the master branch to release!"
|
93
|
+
exit!
|
94
|
+
end
|
95
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
96
|
+
sh "git tag v#{version}"
|
97
|
+
sh "git push origin master"
|
98
|
+
sh "git push v#{version}"
|
99
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
100
|
+
end
|
101
|
+
|
102
|
+
task :build => :gemspec do
|
103
|
+
sh "mkdir -p pkg"
|
104
|
+
sh "gem build #{gemspec_file}"
|
105
|
+
sh "mv #{gem_file} pkg"
|
106
|
+
end
|
107
|
+
|
108
|
+
task :gemspec => :validate do
|
109
|
+
# read spec file and split out manifest section
|
110
|
+
spec = File.read(gemspec_file)
|
111
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
112
|
+
|
113
|
+
# replace name version and date
|
114
|
+
replace_header(head, :name)
|
115
|
+
replace_header(head, :version)
|
116
|
+
replace_header(head, :date)
|
117
|
+
#comment this out if your rubyforge_project has a different name
|
118
|
+
replace_header(head, :rubyforge_project)
|
119
|
+
|
120
|
+
# determine file list from git ls-files
|
121
|
+
files = `git ls-files`.
|
122
|
+
split("\n").
|
123
|
+
sort.
|
124
|
+
reject { |file| file =~ /^\./ }.
|
125
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
126
|
+
map { |file| " #{file}" }.
|
127
|
+
join("\n")
|
128
|
+
|
129
|
+
# piece file back together and write
|
130
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
131
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
132
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
133
|
+
puts "Updated #{gemspec_file}"
|
134
|
+
end
|
135
|
+
|
136
|
+
task :validate do
|
137
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
138
|
+
unless libfiles.empty?
|
139
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
140
|
+
exit!
|
141
|
+
end
|
142
|
+
unless Dir['VERSION*'].empty?
|
143
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
144
|
+
exit!
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
## This is the rakegem gemspec template. Make sure you read and understand
|
2
|
+
## all of the comments. Some sections require modification, and others can
|
3
|
+
## be deleted if you don't need them. Once you understand the contents of
|
4
|
+
## this file, feel free to delete any comments that begin with two hash marks.
|
5
|
+
## You can find comprehensive Gem::Specification documentation, at
|
6
|
+
## http://docs.rubygems.org/read/chapter/20
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
|
+
s.rubygems_version = '1.3.5'
|
11
|
+
|
12
|
+
## Leave these as is they will be modified for you by the rake gemspec task.
|
13
|
+
## If your rubyforge_project name is different, then edit it and comment out
|
14
|
+
## the sub! line in the Rakefile
|
15
|
+
s.name = 'acts_as_versioned'
|
16
|
+
s.version = '0.6.0'
|
17
|
+
s.date = '2010-07-19'
|
18
|
+
s.rubyforge_project = 'acts_as_versioned'
|
19
|
+
|
20
|
+
## Make sure your summary is short. The description may be as long
|
21
|
+
## as you like.
|
22
|
+
s.summary = "Add simple versioning to ActiveRecord models."
|
23
|
+
s.description = "Add simple versioning to ActiveRecord models."
|
24
|
+
|
25
|
+
## List the primary authors. If there are a bunch of authors, it's probably
|
26
|
+
## better to set the email to an email list or something. If you don't have
|
27
|
+
## a custom homepage, consider using your GitHub URL or the like.
|
28
|
+
s.authors = ["Rick Olson"]
|
29
|
+
s.email = 'technoweenie@gmail.com'
|
30
|
+
s.homepage = 'http://github.com/technoweenie/acts_as_versioned'
|
31
|
+
|
32
|
+
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
|
33
|
+
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
|
34
|
+
s.require_paths = %w[lib]
|
35
|
+
|
36
|
+
## Specify any RDoc options here. You'll want to add your README and
|
37
|
+
## LICENSE files to the extra_rdoc_files list.
|
38
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
39
|
+
s.extra_rdoc_files = %w[README MIT-LICENSE CHANGELOG]
|
40
|
+
|
41
|
+
## List your runtime dependencies here. Runtime dependencies are those
|
42
|
+
## that are needed for an end user to actually USE your code.
|
43
|
+
s.add_dependency('activerecord', ["~> 3.0.0.beta4"])
|
44
|
+
|
45
|
+
## List your development dependencies here. Development dependencies are
|
46
|
+
## those that are only needed during development
|
47
|
+
s.add_development_dependency('sqlite3-ruby', ["~> 1.3.1"])
|
48
|
+
|
49
|
+
## Leave this section as-is. It will be automatically generated from the
|
50
|
+
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
51
|
+
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
52
|
+
# = MANIFEST =
|
53
|
+
s.files = %w[
|
54
|
+
CHANGELOG
|
55
|
+
Gemfile
|
56
|
+
MIT-LICENSE
|
57
|
+
README
|
58
|
+
RUNNING_UNIT_TESTS
|
59
|
+
Rakefile
|
60
|
+
acts_as_versioned.gemspec
|
61
|
+
init.rb
|
62
|
+
lib/acts_as_versioned.rb
|
63
|
+
test/abstract_unit.rb
|
64
|
+
test/database.yml
|
65
|
+
test/fixtures/authors.yml
|
66
|
+
test/fixtures/landmark.rb
|
67
|
+
test/fixtures/landmark_versions.yml
|
68
|
+
test/fixtures/landmarks.yml
|
69
|
+
test/fixtures/locked_pages.yml
|
70
|
+
test/fixtures/locked_pages_revisions.yml
|
71
|
+
test/fixtures/migrations/1_add_versioned_tables.rb
|
72
|
+
test/fixtures/page.rb
|
73
|
+
test/fixtures/page_versions.yml
|
74
|
+
test/fixtures/pages.yml
|
75
|
+
test/fixtures/widget.rb
|
76
|
+
test/migration_test.rb
|
77
|
+
test/schema.rb
|
78
|
+
test/versioned_test.rb
|
79
|
+
]
|
80
|
+
# = MANIFEST =
|
81
|
+
|
82
|
+
## Test files will be grabbed from the file list. Make sure the path glob
|
83
|
+
## matches what you actually use.
|
84
|
+
s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
|
85
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'acts_as_versioned'
|
data/lib/acts_as_versioned.rb
CHANGED
@@ -18,11 +18,12 @@
|
|
18
18
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
19
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
require 'active_support/concern'
|
21
22
|
|
22
23
|
module ActiveRecord #:nodoc:
|
23
24
|
module Acts #:nodoc:
|
24
25
|
# 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
|
26
|
+
# versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version
|
26
27
|
# column is present as well.
|
27
28
|
#
|
28
29
|
# The class for the versioned model is derived the first time it is seen. Therefore, if you change your database schema you have to restart
|
@@ -49,349 +50,445 @@ module ActiveRecord #:nodoc:
|
|
49
50
|
# page.revert_to(page.versions.last) # using versioned instance
|
50
51
|
# page.title # => 'hello world'
|
51
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
|
+
#
|
52
68
|
# See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options
|
53
69
|
module Versioned
|
54
|
-
|
55
|
-
|
56
|
-
|
70
|
+
VERSION = "0.6.0"
|
71
|
+
CALLBACKS = [:set_new_version, :save_version, :save_version?]
|
72
|
+
|
73
|
+
# == Configuration options
|
74
|
+
#
|
75
|
+
# * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
|
76
|
+
# * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
|
77
|
+
# * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
|
78
|
+
# * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
|
79
|
+
# * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
|
80
|
+
# * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
|
81
|
+
# * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
|
82
|
+
# * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved.
|
83
|
+
# For finer control, pass either a Proc or modify Model#version_condition_met?
|
84
|
+
#
|
85
|
+
# acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
|
86
|
+
#
|
87
|
+
# or...
|
88
|
+
#
|
89
|
+
# class Auction
|
90
|
+
# def version_condition_met? # totally bypasses the <tt>:if</tt> option
|
91
|
+
# !expired?
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
|
96
|
+
# either a symbol or array of symbols.
|
97
|
+
#
|
98
|
+
# * <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
|
99
|
+
# to create an anonymous mixin:
|
100
|
+
#
|
101
|
+
# class Auction
|
102
|
+
# acts_as_versioned do
|
103
|
+
# def started?
|
104
|
+
# !started_at.nil?
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# or...
|
110
|
+
#
|
111
|
+
# module AuctionExtension
|
112
|
+
# def started?
|
113
|
+
# !started_at.nil?
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
# class Auction
|
117
|
+
# acts_as_versioned :extend => AuctionExtension
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# Example code:
|
121
|
+
#
|
122
|
+
# @auction = Auction.find(1)
|
123
|
+
# @auction.started?
|
124
|
+
# @auction.versions.first.started?
|
125
|
+
#
|
126
|
+
# == Database Schema
|
127
|
+
#
|
128
|
+
# The model that you're versioning needs to have a 'version' attribute. The model is versioned
|
129
|
+
# into a table called #{model}_versions where the model name is singlular. The _versions table should
|
130
|
+
# contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
|
131
|
+
#
|
132
|
+
# A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance,
|
133
|
+
# then that field is reflected in the versioned model as 'versioned_type' by default.
|
134
|
+
#
|
135
|
+
# Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table
|
136
|
+
# method, perfect for a migration. It will also create the version column if the main model does not already have it.
|
137
|
+
#
|
138
|
+
# class AddVersions < ActiveRecord::Migration
|
139
|
+
# def self.up
|
140
|
+
# # create_versioned_table takes the same options hash
|
141
|
+
# # that create_table does
|
142
|
+
# Post.create_versioned_table
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# def self.down
|
146
|
+
# Post.drop_versioned_table
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
#
|
150
|
+
# == Changing What Fields Are Versioned
|
151
|
+
#
|
152
|
+
# By default, acts_as_versioned will version all but these fields:
|
153
|
+
#
|
154
|
+
# [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
|
155
|
+
#
|
156
|
+
# You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols.
|
157
|
+
#
|
158
|
+
# class Post < ActiveRecord::Base
|
159
|
+
# acts_as_versioned
|
160
|
+
# self.non_versioned_columns << 'comments_count'
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
def acts_as_versioned(options = {}, &extension)
|
164
|
+
# don't allow multiple calls
|
165
|
+
return if self.included_modules.include?(ActiveRecord::Acts::Versioned::Behaviors)
|
166
|
+
|
167
|
+
cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
|
168
|
+
:version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns,
|
169
|
+
:version_association_options, :version_if_changed
|
170
|
+
|
171
|
+
self.versioned_class_name = options[:class_name] || "Version"
|
172
|
+
self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
|
173
|
+
self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
|
174
|
+
self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
|
175
|
+
self.version_column = options[:version_column] || 'version'
|
176
|
+
self.version_sequence_name = options[:sequence_name]
|
177
|
+
self.max_version_limit = options[:limit].to_i
|
178
|
+
self.version_condition = options[:if] || true
|
179
|
+
self.non_versioned_columns = [self.primary_key, inheritance_column, self.version_column, 'lock_version', versioned_inheritance_column] + options[:non_versioned_columns].to_a.map(&:to_s)
|
180
|
+
self.version_association_options = {
|
181
|
+
:class_name => "#{self.to_s}::#{versioned_class_name}",
|
182
|
+
:foreign_key => versioned_foreign_key,
|
183
|
+
:dependent => :delete_all
|
184
|
+
}.merge(options[:association_options] || {})
|
185
|
+
|
186
|
+
if block_given?
|
187
|
+
extension_module_name = "#{versioned_class_name}Extension"
|
188
|
+
silence_warnings do
|
189
|
+
self.const_set(extension_module_name, Module.new(&extension))
|
190
|
+
end
|
191
|
+
|
192
|
+
options[:extend] = self.const_get(extension_module_name)
|
193
|
+
end
|
194
|
+
|
195
|
+
unless options[:if_changed].nil?
|
196
|
+
self.track_altered_attributes = true
|
197
|
+
options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
|
198
|
+
self.version_if_changed = options[:if_changed].map(&:to_s)
|
199
|
+
end
|
200
|
+
|
201
|
+
include options[:extend] if options[:extend].is_a?(Module)
|
202
|
+
|
203
|
+
include ActiveRecord::Acts::Versioned::Behaviors
|
57
204
|
|
58
|
-
module ClassMethods
|
59
|
-
# == Configuration options
|
60
|
-
#
|
61
|
-
# * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
|
62
|
-
# * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
|
63
|
-
# * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
|
64
|
-
# * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
|
65
|
-
# * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
|
66
|
-
# * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
|
67
|
-
# * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
|
68
|
-
# * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved.
|
69
|
-
# For finer control, pass either a Proc or modify Model#version_condition_met?
|
70
|
-
#
|
71
|
-
# acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
|
72
|
-
#
|
73
|
-
# or...
|
74
|
-
#
|
75
|
-
# class Auction
|
76
|
-
# def version_condition_met? # totally bypasses the <tt>:if</tt> option
|
77
|
-
# !expired?
|
78
|
-
# end
|
79
|
-
# end
|
80
|
-
#
|
81
|
-
# * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
|
82
|
-
# either a symbol or array of symbols.
|
83
|
-
#
|
84
|
-
# == Database Schema
|
85
|
-
#
|
86
|
-
# The model that you're versioning needs to have a 'version' attribute. The model is versioned
|
87
|
-
# into a table called #{model}_versions where the model name is singlular. The _versions table should
|
88
|
-
# contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
|
89
|
-
#
|
90
|
-
# A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance,
|
91
|
-
# then that field is reflected in the versioned model as 'versioned_type' by default.
|
92
205
|
#
|
93
|
-
#
|
94
|
-
# method, perfect for a migration. It will also create the version column if the main model does not already have it.
|
206
|
+
# Create the dynamic versioned model
|
95
207
|
#
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
# # that create_table does
|
100
|
-
# Post.create_versioned_table
|
101
|
-
# end
|
102
|
-
#
|
103
|
-
# def self.down
|
104
|
-
# Post.drop_versioned_table
|
105
|
-
# end
|
106
|
-
# end
|
107
|
-
def acts_as_versioned(options = {})
|
108
|
-
# don't allow multiple calls
|
109
|
-
return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods)
|
110
|
-
|
111
|
-
class_eval do
|
112
|
-
include ActiveRecord::Acts::Versioned::ActMethods
|
113
|
-
cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
|
114
|
-
:version_column, :max_version_limit, :track_changed_attributes, :version_condition, :version_sequence_name
|
115
|
-
attr_accessor :changed_attributes
|
208
|
+
const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
|
209
|
+
def self.reloadable?;
|
210
|
+
false;
|
116
211
|
end
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
self.version_sequence_name = options[:sequence_name]
|
124
|
-
self.max_version_limit = options[:limit].to_i
|
125
|
-
self.version_condition = options[:if] || true
|
126
|
-
|
127
|
-
class_eval do
|
128
|
-
has_many :versions,
|
129
|
-
:class_name => "ActiveRecord::Acts::Versioned::#{versioned_class_name}",
|
130
|
-
:foreign_key => "#{versioned_foreign_key}",
|
131
|
-
:order => 'version'
|
132
|
-
before_save :set_new_version
|
133
|
-
after_create :save_version_on_create
|
134
|
-
after_update :save_version
|
135
|
-
after_save :clear_old_versions
|
136
|
-
after_save :clear_changed_attributes
|
137
|
-
|
138
|
-
unless options[:if_changed].nil?
|
139
|
-
self.track_changed_attributes = true
|
140
|
-
options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
|
141
|
-
options[:if_changed].each do |attr_name|
|
142
|
-
define_method("#{attr_name}=") do |value|
|
143
|
-
(self.changed_attributes ||= []) << attr_name.to_s unless self.changed?(attr_name) or self.send(attr_name) == value
|
144
|
-
write_attribute(attr_name.to_s, value)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
212
|
+
|
213
|
+
# find first version before the given version
|
214
|
+
def self.before(version)
|
215
|
+
where(["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]).
|
216
|
+
order('version DESC').
|
217
|
+
first
|
148
218
|
end
|
149
|
-
|
150
|
-
# create the dynamic versioned model
|
151
|
-
# maybe if i sit down long enough i can think up a better way to do this.
|
152
|
-
dynamic_model = <<-EOV
|
153
|
-
class ActiveRecord::Acts::Versioned::#{versioned_class_name} < ActiveRecord::Base
|
154
|
-
set_table_name "#{versioned_table_name}"
|
155
|
-
belongs_to :#{self.to_s.demodulize.underscore}, :class_name => "#{self.to_s}"
|
156
|
-
EOV
|
157
|
-
|
158
|
-
dynamic_model += %Q{set_sequence_name "#{version_sequence_name}"\n} if version_sequence_name
|
159
|
-
|
160
|
-
eval dynamic_model + 'end'
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
module ActMethods
|
165
|
-
def self.included(base) # :nodoc:
|
166
|
-
base.extend ClassMethods
|
167
|
-
end
|
168
|
-
|
169
|
-
# Saves a version of the model if applicable
|
170
|
-
def save_version
|
171
|
-
save_version_on_create if save_version?
|
172
|
-
end
|
173
|
-
|
174
|
-
# Saves a version of the model in the versioned table. This is called in the after_save callback by default
|
175
|
-
def save_version_on_create
|
176
|
-
rev = self.class.versioned_class.new
|
177
|
-
self.clone_versioned_model(self, rev)
|
178
|
-
rev.version = send(self.class.version_column)
|
179
|
-
rev.send("#{self.class.versioned_foreign_key}=", self.id)
|
180
|
-
rev.save
|
181
|
-
end
|
182
219
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
if excess_baggage > 0
|
189
|
-
sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version <= #{excess_baggage} AND #{self.class.versioned_foreign_key} = #{self.id}"
|
190
|
-
self.class.versioned_class.connection.execute sql
|
220
|
+
# find first version after the given version.
|
221
|
+
def self.after(version)
|
222
|
+
where(["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]).
|
223
|
+
order('version ASC').
|
224
|
+
first
|
191
225
|
end
|
192
|
-
end
|
193
226
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
find_versions(:conditions => ['version = ?', version], :limit => 1).first
|
199
|
-
end
|
200
|
-
|
201
|
-
# Finds versions of this model. Takes an options hash like <tt>find</tt>
|
202
|
-
def find_versions(options = {})
|
203
|
-
versions.find(:all, options)
|
204
|
-
end
|
227
|
+
# finds earliest version of this record
|
228
|
+
def self.earliest
|
229
|
+
order("#{original_class.version_column}").first
|
230
|
+
end
|
205
231
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
return false unless version.send(self.class.versioned_foreign_key) == self.id and !version.new_record?
|
210
|
-
else
|
211
|
-
return false unless version = find_version(version)
|
232
|
+
# find latest version of this record
|
233
|
+
def self.latest
|
234
|
+
order("#{original_class.version_column} desc").first
|
212
235
|
end
|
213
|
-
self.clone_versioned_model(version, self)
|
214
|
-
self.send("#{self.class.version_column}=", version.version)
|
215
|
-
true
|
216
|
-
end
|
217
236
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
revert_to(version) ? save_without_revision : false
|
222
|
-
end
|
237
|
+
def previous
|
238
|
+
self.class.before(self)
|
239
|
+
end
|
223
240
|
|
224
|
-
|
225
|
-
|
226
|
-
old_lock_value = ActiveRecord::Base.lock_optimistically
|
227
|
-
ActiveRecord::Base.lock_optimistically = false if old_lock_value
|
228
|
-
disable_acts_as_versioned_callbacks
|
229
|
-
save_result = self.save
|
230
|
-
enable_acts_as_versioned_callbacks
|
231
|
-
ActiveRecord::Base.lock_optimistically = true if old_lock_value
|
232
|
-
save_result
|
233
|
-
end
|
234
|
-
|
235
|
-
# Returns an array of attribute keys that are versioned. See non_versioned_fields
|
236
|
-
def versioned_attributes
|
237
|
-
self.attributes.keys.select { |k| !self.class.non_versioned_fields.include?(k) }
|
238
|
-
end
|
239
|
-
|
240
|
-
# If called with no parameters, gets whether the current model has changed and needs to be versioned.
|
241
|
-
# If called with a single parameter, gets whether the parameter has changed.
|
242
|
-
def changed?(attr_name = nil)
|
243
|
-
attr_name.nil? ?
|
244
|
-
(!self.class.track_changed_attributes or (changed_attributes and changed_attributes.length > 0)) :
|
245
|
-
(changed_attributes and changed_attributes.include?(attr_name.to_s))
|
246
|
-
end
|
247
|
-
|
248
|
-
# keep old dirty? method
|
249
|
-
alias_method :dirty?, :changed?
|
250
|
-
|
251
|
-
# Clones a model. Used when saving a new version or reverting a model's version.
|
252
|
-
def clone_versioned_model(orig_model, new_model)
|
253
|
-
self.versioned_attributes.each do |key|
|
254
|
-
new_model.send("#{key}=", orig_model.attributes[key]) if orig_model.attribute_present?(key)
|
241
|
+
def next
|
242
|
+
self.class.after(self)
|
255
243
|
end
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
elsif new_model.is_a?(self.class.versioned_class)
|
260
|
-
new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
|
244
|
+
|
245
|
+
def versions_count
|
246
|
+
page.version
|
261
247
|
end
|
262
248
|
end
|
263
|
-
|
264
|
-
# Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
|
265
|
-
def save_version?
|
266
|
-
version_condition_met? and changed?
|
267
|
-
end
|
268
|
-
|
269
|
-
# Checks condition set in the :if option to check whether a revision should be created or not. Override this for
|
270
|
-
# custom version condition checking.
|
271
|
-
def version_condition_met?
|
272
|
-
case
|
273
|
-
when version_condition.is_a?(Symbol)
|
274
|
-
send(version_condition)
|
275
|
-
when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
|
276
|
-
version_condition.call(self)
|
277
|
-
else
|
278
|
-
version_condition
|
279
|
-
end
|
280
|
-
end
|
281
249
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
connection.select_one("SELECT MAX(version)+1 AS next_version FROM #{self.class.versioned_table_name} WHERE #{self.class.versioned_foreign_key} = #{self.id}")['next_version'] || 1
|
292
|
-
end
|
293
|
-
|
294
|
-
# clears current changed attributes. Called after save.
|
295
|
-
def clear_changed_attributes
|
296
|
-
self.changed_attributes = []
|
297
|
-
end
|
250
|
+
versioned_class.cattr_accessor :original_class
|
251
|
+
versioned_class.original_class = self
|
252
|
+
versioned_class.set_table_name versioned_table_name
|
253
|
+
versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
|
254
|
+
:class_name => "::#{self.to_s}",
|
255
|
+
:foreign_key => versioned_foreign_key
|
256
|
+
versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
|
257
|
+
versioned_class.set_sequence_name version_sequence_name if version_sequence_name
|
258
|
+
end
|
298
259
|
|
299
|
-
|
300
|
-
|
301
|
-
ACTS_AS_VERSIONED_CALLBACKS = [:set_new_version, :save_version_on_create, :save_version, :clear_changed_attributes]
|
302
|
-
end
|
260
|
+
module Behaviors
|
261
|
+
extend ActiveSupport::Concern
|
303
262
|
|
304
|
-
|
305
|
-
|
263
|
+
included do
|
264
|
+
has_many :versions, self.version_association_options
|
265
|
+
|
266
|
+
before_save :set_new_version
|
267
|
+
after_save :save_version
|
268
|
+
after_save :clear_old_versions
|
306
269
|
end
|
307
|
-
|
308
|
-
def empty_callback() end #:nodoc:
|
309
270
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
271
|
+
module InstanceMethods
|
272
|
+
# Saves a version of the model in the versioned table. This is called in the after_save callback by default
|
273
|
+
def save_version
|
274
|
+
if @saving_version
|
275
|
+
@saving_version = nil
|
276
|
+
rev = self.class.versioned_class.new
|
277
|
+
clone_versioned_model(self, rev)
|
278
|
+
rev.send("#{self.class.version_column}=", send(self.class.version_column))
|
279
|
+
rev.send("#{self.class.versioned_foreign_key}=", id)
|
280
|
+
rev.save
|
314
281
|
end
|
315
282
|
end
|
316
|
-
end
|
317
283
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
284
|
+
# Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
|
285
|
+
# Override this method to set your own criteria for clearing old versions.
|
286
|
+
def clear_old_versions
|
287
|
+
return if self.class.max_version_limit == 0
|
288
|
+
excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
|
289
|
+
if excess_baggage > 0
|
290
|
+
self.class.versioned_class.delete_all ["#{self.class.version_column} <= ? and #{self.class.versioned_foreign_key} = ?", excess_baggage, id]
|
322
291
|
end
|
323
292
|
end
|
324
|
-
end
|
325
293
|
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
294
|
+
# Reverts a model to a given version. Takes either a version number or an instance of the versioned model
|
295
|
+
def revert_to(version)
|
296
|
+
if version.is_a?(self.class.versioned_class)
|
297
|
+
return false unless version.send(self.class.versioned_foreign_key) == id and !version.new_record?
|
298
|
+
else
|
299
|
+
return false unless version = versions.where(self.class.version_column => version).first
|
300
|
+
end
|
301
|
+
self.clone_versioned_model(version, self)
|
302
|
+
send("#{self.class.version_column}=", version.send(self.class.version_column))
|
303
|
+
true
|
304
|
+
end
|
305
|
+
|
306
|
+
# Reverts a model to a given version and saves the model.
|
307
|
+
# Takes either a version number or an instance of the versioned model
|
308
|
+
def revert_to!(version)
|
309
|
+
revert_to(version) ? save_without_revision : false
|
310
|
+
end
|
311
|
+
|
312
|
+
# Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
|
313
|
+
def save_without_revision
|
314
|
+
save_without_revision!
|
315
|
+
true
|
316
|
+
rescue
|
317
|
+
false
|
318
|
+
end
|
319
|
+
|
320
|
+
def save_without_revision!
|
321
|
+
without_locking do
|
322
|
+
without_revision do
|
323
|
+
save!
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def altered?
|
329
|
+
track_altered_attributes ? (version_if_changed - changed).length < version_if_changed.length : changed?
|
330
|
+
end
|
331
|
+
|
332
|
+
# Clones a model. Used when saving a new version or reverting a model's version.
|
333
|
+
def clone_versioned_model(orig_model, new_model)
|
334
|
+
self.class.versioned_columns.each do |col|
|
335
|
+
new_model.send("#{col.name}=", orig_model.send(col.name)) if orig_model.has_attribute?(col.name)
|
336
|
+
end
|
337
|
+
|
338
|
+
if orig_model.is_a?(self.class.versioned_class)
|
339
|
+
new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
|
340
|
+
elsif new_model.is_a?(self.class.versioned_class)
|
341
|
+
new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
|
346
|
+
def save_version?
|
347
|
+
version_condition_met? && altered?
|
348
|
+
end
|
349
|
+
|
350
|
+
# Checks condition set in the :if option to check whether a revision should be created or not. Override this for
|
351
|
+
# custom version condition checking.
|
352
|
+
def version_condition_met?
|
353
|
+
case
|
354
|
+
when version_condition.is_a?(Symbol)
|
355
|
+
send(version_condition)
|
356
|
+
when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
|
357
|
+
version_condition.call(self)
|
358
|
+
else
|
359
|
+
version_condition
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Executes the block with the versioning callbacks disabled.
|
364
|
+
#
|
365
|
+
# @foo.without_revision do
|
366
|
+
# @foo.save
|
367
|
+
# end
|
368
|
+
#
|
369
|
+
def without_revision(&block)
|
370
|
+
self.class.without_revision(&block)
|
371
|
+
end
|
372
|
+
|
373
|
+
# Turns off optimistic locking for the duration of the block
|
374
|
+
#
|
375
|
+
# @foo.without_locking do
|
376
|
+
# @foo.save
|
377
|
+
# end
|
378
|
+
#
|
379
|
+
def without_locking(&block)
|
380
|
+
self.class.without_locking(&block)
|
381
|
+
end
|
382
|
+
|
383
|
+
def empty_callback()
|
332
384
|
end
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
385
|
+
|
386
|
+
#:nodoc:
|
387
|
+
|
388
|
+
protected
|
389
|
+
# sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
|
390
|
+
def set_new_version
|
391
|
+
@saving_version = new_record? || save_version?
|
392
|
+
self.send("#{self.class.version_column}=", next_version) if new_record? || (!locking_enabled? && save_version?)
|
339
393
|
end
|
340
|
-
|
341
|
-
#
|
394
|
+
|
395
|
+
# Gets the next available version for the current record, or 1 for a new record
|
396
|
+
def next_version
|
397
|
+
(new_record? ? 0 : versions.calculate(:maximum, version_column).to_i) + 1
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
module ClassMethods
|
402
|
+
# Returns an array of columns that are versioned. See non_versioned_columns
|
342
403
|
def versioned_columns
|
343
|
-
|
404
|
+
@versioned_columns ||= columns.select { |c| !non_versioned_columns.include?(c.name) }
|
344
405
|
end
|
345
|
-
|
406
|
+
|
346
407
|
# Returns an instance of the dynamic versioned model
|
347
408
|
def versioned_class
|
348
|
-
|
409
|
+
const_get versioned_class_name
|
349
410
|
end
|
350
|
-
|
351
|
-
# An array of fields that are not saved in the versioned table
|
352
|
-
def non_versioned_fields
|
353
|
-
[self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
|
354
|
-
end
|
355
|
-
|
411
|
+
|
356
412
|
# Rake migration task to create the versioned table using options passed to acts_as_versioned
|
357
413
|
def create_versioned_table(create_table_options = {})
|
358
414
|
# create version column in main table if it does not exist
|
359
|
-
if !self.content_columns.find { |c|
|
360
|
-
self.connection.add_column table_name,
|
415
|
+
if !self.content_columns.find { |c| [version_column.to_s, 'lock_version'].include? c.name }
|
416
|
+
self.connection.add_column table_name, version_column, :integer
|
417
|
+
self.reset_column_information
|
361
418
|
end
|
362
|
-
|
419
|
+
|
420
|
+
return if connection.table_exists?(versioned_table_name)
|
421
|
+
|
363
422
|
self.connection.create_table(versioned_table_name, create_table_options) do |t|
|
364
423
|
t.column versioned_foreign_key, :integer
|
365
|
-
t.column
|
424
|
+
t.column version_column, :integer
|
366
425
|
end
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
426
|
+
|
427
|
+
self.versioned_columns.each do |col|
|
428
|
+
self.connection.add_column versioned_table_name, col.name, col.type,
|
429
|
+
:limit => col.limit,
|
430
|
+
:default => col.default,
|
431
|
+
:scale => col.scale,
|
432
|
+
:precision => col.precision
|
374
433
|
end
|
375
|
-
|
434
|
+
|
376
435
|
if type_col = self.columns_hash[inheritance_column]
|
377
|
-
self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
if updated_col.nil?
|
383
|
-
self.connection.add_column versioned_table_name, :updated_at, :timestamp
|
436
|
+
self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
|
437
|
+
:limit => type_col.limit,
|
438
|
+
:default => type_col.default,
|
439
|
+
:scale => type_col.scale,
|
440
|
+
:precision => type_col.precision
|
384
441
|
end
|
442
|
+
|
443
|
+
self.connection.add_index versioned_table_name, versioned_foreign_key
|
385
444
|
end
|
386
|
-
|
445
|
+
|
387
446
|
# Rake migration task to drop the versioned table
|
388
447
|
def drop_versioned_table
|
389
448
|
self.connection.drop_table versioned_table_name
|
390
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
|
+
begin
|
483
|
+
block.call
|
484
|
+
ensure
|
485
|
+
ActiveRecord::Base.lock_optimistically = true if current
|
486
|
+
end
|
487
|
+
end
|
391
488
|
end
|
392
489
|
end
|
393
490
|
end
|
394
491
|
end
|
395
492
|
end
|
396
493
|
|
397
|
-
ActiveRecord::Base.
|
494
|
+
ActiveRecord::Base.extend ActiveRecord::Acts::Versioned
|