gluttonberg_acts_as_versioned 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cf5dfff6af85d57329df7f868547eb4ab302d53e
4
+ data.tar.gz: 4c795f10ab7b141ad245207fc33da267e003759b
5
+ SHA512:
6
+ metadata.gz: 25d1f4377595d5f071dfb92ad17f1e103edf5cfa8dbf995ab66ef74890f12207359da08de6a2752abf648f44cb219ae920e9d1e13c1d2da5da33a58a4ee2cda7
7
+ data.tar.gz: 793c6cfb4b20a281ead4e20200ab898ff80f83e14d0d35191bfcdf5c0e56f4e9792315814ba7c24cec07679b78555f153cb4e26ceb6411c1cd1d6afcd2d9d231
@@ -0,0 +1,84 @@
1
+ *GIT* (version numbers are overrated)
2
+
3
+ * 0.6 (19 Jul 2010) Rails 3 refactoring by gvarela!
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
+
46
+ *0.2.3*
47
+
48
+ * (12 Nov 2005) fixed bug with old behavior of #blank? [Michael Schuerig]
49
+ * (12 Nov 2005) updated tests to use ActiveRecord Schema
50
+
51
+ *0.2.2*
52
+
53
+ * (3 Nov 2005) added documentation note to #acts_as_versioned [Martin Jul]
54
+
55
+ *0.2.1*
56
+
57
+ * (6 Oct 2005) renamed dirty? to changed? to keep it uniform. it was aliased to keep it backwards compatible.
58
+
59
+ *0.2*
60
+
61
+ * (6 Oct 2005) added find_versions and find_version class methods.
62
+
63
+ * (6 Oct 2005) removed transaction from create_versioned_table().
64
+ this way you can specify your own transaction around a group of operations.
65
+
66
+ * (30 Sep 2005) fixed bug where find_versions() would order by 'version' twice. (found by Joe Clark)
67
+
68
+ * (26 Sep 2005) added :sequence_name option to acts_as_versioned to set the sequence name on the versioned model
69
+
70
+ *0.1.3* (18 Sep 2005)
71
+
72
+ * First RubyForge release
73
+
74
+ *0.1.2*
75
+
76
+ * check if module is already included when acts_as_versioned is called
77
+
78
+ *0.1.1*
79
+
80
+ * Adding tests and rdocs
81
+
82
+ *0.1*
83
+
84
+ * Initial transfer from Rails ticket: http://dev.rubyonrails.com/ticket/1974
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'http://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'rails', '~> 3.2'
5
+ gem 'pg', '~> 0.17.1'
6
+ gem 'mysql2', '0.3.15'
7
+ end
@@ -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 ADDED
@@ -0,0 +1,21 @@
1
+ = acts_as_versioned
2
+
3
+ This library adds simple versioning to an ActiveRecord module. ActiveRecord is required.
4
+
5
+ == Resources
6
+
7
+ Install
8
+
9
+ * gem install acts_as_versioned
10
+
11
+ <3 GitHub
12
+
13
+ * http://github.com/abdulrauf/acts_as_versioned
14
+
15
+ Gemcutter FTW
16
+
17
+ * http://gemcutter.org/gems/freerange_acts_as_versioned
18
+
19
+
20
+
21
+ Add simple versioning to ActiveRecord models. This version supports rails ~>3.1.1. Also it have support for autosave. It introduces new method auto_save_version which keeps 1 autosave version for an object.
@@ -0,0 +1,41 @@
1
+ == Creating the test database
2
+
3
+ The default name for the test databases is "activerecord_versioned". If you
4
+ want to use another database name then be sure to update the connection
5
+ adapter setups you want to test with in test/connections/<your database>/connection.rb.
6
+ When you have the database online, you can import the fixture tables with
7
+ the test/fixtures/db_definitions/*.sql files.
8
+
9
+ Make sure that you create database objects with the same user that you specified in i
10
+ connection.rb otherwise (on Postgres, at least) tests for default values will fail.
11
+
12
+ == Running with Rake
13
+
14
+ The easiest way to run the unit tests is through Rake. The default task runs
15
+ the entire test suite for all the adapters. You can also run the suite on just
16
+ one adapter by using the tasks test_mysql_ruby, test_ruby_mysql, test_sqlite,
17
+ or test_postresql. For more information, checkout the full array of rake tasks with "rake -T"
18
+
19
+ Rake can be found at http://rake.rubyforge.org
20
+
21
+ == Running by hand
22
+
23
+ Unit tests are located in test directory. If you only want to run a single test suite,
24
+ or don't want to bother with Rake, you can do so with something like:
25
+
26
+ cd test; ruby -I "connections/native_mysql" base_test.rb
27
+
28
+ That'll run the base suite using the MySQL-Ruby adapter. Change the adapter
29
+ and test suite name as needed.
30
+
31
+ == Faster tests
32
+
33
+ If you are using a database that supports transactions, you can set the
34
+ "AR_TX_FIXTURES" environment variable to "yes" to use transactional fixtures.
35
+ This gives a very large speed boost. With rake:
36
+
37
+ rake AR_TX_FIXTURES=yes
38
+
39
+ Or, by hand:
40
+
41
+ AR_TX_FIXTURES=yes ruby -I connections/native_sqlite3 base_test.rb
@@ -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 'rdoc/task'
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,46 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+ s.name = 'gluttonberg_acts_as_versioned'
5
+ s.version = '2.0.0'
6
+ s.date = '2014-02-07'
7
+ s.rubyforge_project = 'gluttonberg_acts_as_versioned'
8
+ s.summary = "Add simple versioning to ActiveRecord models. This version supports rails 3.2. Also it have support for autosave. It introduces new method auto_save_version which keeps 1 autosave version for an object."
9
+ s.description = "Add simple versioning to ActiveRecord models. rails 3.2"
10
+ s.authors = ["Nick Crowther", "Yuri Tománek", "Abdul Rauf", "Luke Sutton"]
11
+ s.email = ["office@freerangefuture.com"]
12
+ s.homepage = 'https://github.com/Gluttonberg/acts_as_versioned'
13
+ s.require_paths = %w[lib]
14
+ s.rdoc_options = ["--charset=UTF-8"]
15
+ s.extra_rdoc_files = %w[README MIT-LICENSE CHANGELOG]
16
+ s.add_dependency('activerecord', ["~> 3.2"])
17
+ s.add_development_dependency 'pg', '~> 0.17', '>= 0.17.1'
18
+ s.files = %w[
19
+ CHANGELOG
20
+ Gemfile
21
+ MIT-LICENSE
22
+ README
23
+ RUNNING_UNIT_TESTS
24
+ Rakefile
25
+ acts_as_versioned.gemspec
26
+ init.rb
27
+ lib/acts_as_versioned.rb
28
+ test/abstract_unit.rb
29
+ test/database.yml
30
+ test/fixtures/authors.yml
31
+ test/fixtures/landmark.rb
32
+ test/fixtures/landmark_versions.yml
33
+ test/fixtures/landmarks.yml
34
+ test/fixtures/locked_pages.yml
35
+ test/fixtures/locked_pages_revisions.yml
36
+ test/fixtures/migrations/1_add_versioned_tables.rb
37
+ test/fixtures/page.rb
38
+ test/fixtures/page_versions.yml
39
+ test/fixtures/pages.yml
40
+ test/fixtures/widget.rb
41
+ test/migration_test.rb
42
+ test/schema.rb
43
+ test/versioned_test.rb
44
+ ]
45
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
46
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'acts_as_versioned'
@@ -0,0 +1,522 @@
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
+ require 'active_support/concern'
22
+
23
+ module ActiveRecord #:nodoc:
24
+ module Acts #:nodoc:
25
+ # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a
26
+ # versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version
27
+ # column is present as well.
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
30
+ # your container for the changes to be reflected. In development mode 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
+ VERSION = "1.0.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, :version_status_column, :version_user_id_column
170
+
171
+ attr_accessor :loaded_version
172
+ self.versioned_class_name = options[:class_name] || "Version"
173
+ self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
174
+ self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
175
+ self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
176
+ self.version_column = options[:version_column] || 'version'
177
+ self.version_status_column = options[:version_status_column] || 'version_status'
178
+ self.version_user_id_column = options[:version_user_id_column] || 'version_user_id'
179
+ self.version_sequence_name = options[:sequence_name]
180
+ self.max_version_limit = options[:limit].to_i
181
+ self.version_condition = options[:if] || true
182
+ 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)
183
+ self.version_association_options = {
184
+ :class_name => "#{self.to_s}::#{versioned_class_name}",
185
+ :foreign_key => versioned_foreign_key,
186
+ :dependent => :delete_all
187
+ }.merge(options[:association_options] || {})
188
+
189
+ if block_given?
190
+ extension_module_name = "#{versioned_class_name}Extension"
191
+ silence_warnings do
192
+ self.const_set(extension_module_name, Module.new(&extension))
193
+ end
194
+
195
+ options[:extend] = self.const_get(extension_module_name)
196
+ end
197
+
198
+ unless options[:if_changed].nil?
199
+ self.track_altered_attributes = true
200
+ options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
201
+ self.version_if_changed = options[:if_changed].map(&:to_s)
202
+ end
203
+
204
+ include options[:extend] if options[:extend].is_a?(Module)
205
+
206
+ include ActiveRecord::Acts::Versioned::Behaviors
207
+
208
+ #
209
+ # Create the dynamic versioned model
210
+ #
211
+ const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
212
+ def self.reloadable?;
213
+ false;
214
+ end
215
+
216
+ # find first version before the given version
217
+ def self.before(version)
218
+ where(["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]).
219
+ order('version DESC').
220
+ first
221
+ end
222
+
223
+ # find first version after the given version.
224
+ def self.after(version)
225
+ where(["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]).
226
+ order('version ASC').
227
+ first
228
+ end
229
+
230
+ # finds earliest version of this record
231
+ def self.earliest
232
+ order("#{original_class.version_column}").first
233
+ end
234
+
235
+ # find latest version of this record
236
+ def self.latest
237
+ order("#{original_class.version_column} desc").first
238
+ end
239
+
240
+ def previous
241
+ self.class.before(self)
242
+ end
243
+
244
+ def next
245
+ self.class.after(self)
246
+ end
247
+
248
+ def versions_count
249
+ page.version
250
+ end
251
+ end
252
+
253
+ versioned_class.cattr_accessor :original_class
254
+ versioned_class.original_class = self
255
+ versioned_class.table_name = versioned_table_name
256
+ versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
257
+ :class_name => "::#{self.to_s}",
258
+ :foreign_key => versioned_foreign_key
259
+ versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
260
+ versioned_class.sequence_name = version_sequence_name if version_sequence_name
261
+ versioned_class.belongs_to :user,
262
+ :foreign_key => version_user_id_column if version_user_id_column
263
+ end
264
+
265
+ module Behaviors
266
+ extend ActiveSupport::Concern
267
+
268
+ included do
269
+ has_many :versions, self.version_association_options
270
+
271
+ before_save :set_new_version
272
+ after_save :save_version
273
+ after_save :clear_old_versions
274
+ end
275
+
276
+ # Saves a version of the model in the versioned table. This is called in the after_save callback by default
277
+ def save_version
278
+ if @saving_version
279
+ @saving_version = nil
280
+ rev = self.class.versioned_class.new
281
+ clone_versioned_model(self, rev)
282
+ rev.send("#{self.class.version_column}=", send(self.class.version_column))
283
+ rev.send("#{self.class.versioned_foreign_key}=", id)
284
+ if rev.respond_to?(self.class.version_status_column)
285
+ rev.send("#{self.class.version_status_column}=", self._publish_status) if self.respond_to?(:_publish_status)
286
+ rev.send("#{self.class.version_status_column}=", self.state) if self.respond_to?(:state) && rev.send(self.class.version_status_column).blank?
287
+ end
288
+ if rev.respond_to?(self.class.version_user_id_column)
289
+ rev.send("#{self.class.version_user_id_column}=", self.current_user_id) if self.respond_to?(:current_user_id)
290
+ end
291
+ rev.save
292
+ revert_to_published_version(rev)
293
+ end
294
+ end
295
+
296
+ def revert_to_published_version(rev)
297
+ if rev.respond_to?(self.class.version_status_column) && ["revision", "submitted_for_approval"].include?(rev.send(self.class.version_status_column))
298
+ published_version = self.versions.where(:version_status => 'published').order("version DESC").first
299
+ unless published_version.blank?
300
+ self.reload
301
+ self.revert_to!(published_version)
302
+ end
303
+ end
304
+ end
305
+
306
+ # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
307
+ # Override this method to set your own criteria for clearing old versions.
308
+ def clear_old_versions
309
+ return if self.class.max_version_limit == 0
310
+ excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
311
+ if excess_baggage > 0
312
+ self.class.versioned_class.delete_all ["#{self.class.version_column} <= ? and #{self.class.versioned_foreign_key} = ?", excess_baggage, id]
313
+ end
314
+ end
315
+
316
+ # Reverts a model to a given version. Takes either a version number or an instance of the versioned model
317
+ def revert_to(version)
318
+ if version.is_a?(self.class.versioned_class)
319
+ return false unless version.send(self.class.versioned_foreign_key) == id and !version.new_record?
320
+ else
321
+ return false unless version = versions.where(self.class.version_column => version).first
322
+ end
323
+ self.clone_versioned_model(version, self)
324
+ send("#{self.class.version_column}=", version.send(self.class.version_column))
325
+ self.loaded_version = version
326
+ true
327
+ end
328
+
329
+ # Reverts a model to a given version and saves the model.
330
+ # Takes either a version number or an instance of the versioned model
331
+ def revert_to!(version)
332
+ revert_to(version) ? save_without_revision : false
333
+ end
334
+
335
+ # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
336
+ def save_without_revision
337
+ save_without_revision!
338
+ true
339
+ rescue
340
+ false
341
+ end
342
+
343
+ def save_without_revision!
344
+ without_locking do
345
+ without_revision do
346
+ save!
347
+ end
348
+ end
349
+ end
350
+
351
+ def altered?
352
+ track_altered_attributes ? (version_if_changed - changed).length < version_if_changed.length : changed?
353
+ end
354
+
355
+ # Clones a model. Used when saving a new version or reverting a model's version.
356
+ def clone_versioned_model(orig_model, new_model)
357
+ self.class.versioned_columns.each do |col|
358
+ new_model[col.name] = orig_model.send(col.name) if orig_model.has_attribute?(col.name)
359
+ end
360
+
361
+ clone_inheritance_column(orig_model, new_model)
362
+ end
363
+
364
+ def clone_inheritance_column(orig_model, new_model)
365
+ if orig_model.is_a?(self.class.versioned_class) && new_model.class.column_names.include?(new_model.class.inheritance_column.to_s)
366
+ new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
367
+ elsif new_model.is_a?(self.class.versioned_class) && new_model.class.column_names.include?(self.class.versioned_inheritance_column.to_s)
368
+ new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
369
+ end
370
+ end
371
+
372
+ # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
373
+ def save_version?
374
+ version_condition_met? && altered? || (self.respond_to?(:_publish_status) && (self.state_changed?) )
375
+ end
376
+
377
+ # Checks condition set in the :if option to check whether a revision should be created or not. Override this for
378
+ # custom version condition checking.
379
+ def version_condition_met?
380
+ case
381
+ when version_condition.is_a?(Symbol)
382
+ send(version_condition)
383
+ when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
384
+ version_condition.call(self)
385
+ else
386
+ version_condition
387
+ end
388
+ end
389
+
390
+ # Executes the block with the versioning callbacks disabled.
391
+ #
392
+ # @foo.without_revision do
393
+ # @foo.save
394
+ # end
395
+ #
396
+ def without_revision(&block)
397
+ self.class.without_revision(&block)
398
+ end
399
+
400
+ # Turns off optimistic locking for the duration of the block
401
+ #
402
+ # @foo.without_locking do
403
+ # @foo.save
404
+ # end
405
+ #
406
+ def without_locking(&block)
407
+ self.class.without_locking(&block)
408
+ end
409
+
410
+ def empty_callback()
411
+ end
412
+
413
+ #:nodoc:
414
+
415
+ protected
416
+ # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
417
+ def set_new_version
418
+ @saving_version = new_record? || save_version?
419
+ self.send("#{self.class.version_column}=", next_version) if new_record? || (!locking_enabled? && save_version?)
420
+ end
421
+
422
+ # Gets the next available version for the current record, or 1 for a new record
423
+ def next_version
424
+ (new_record? ? 0 : versions.calculate(:maximum, version_column).to_i) + 1
425
+ end
426
+
427
+ module ClassMethods
428
+ # Returns an array of columns that are versioned. See non_versioned_columns
429
+ def versioned_columns
430
+ @versioned_columns ||= columns.select { |c| !non_versioned_columns.include?(c.name) }
431
+ end
432
+
433
+ # Returns an instance of the dynamic versioned model
434
+ def versioned_class
435
+ const_get versioned_class_name
436
+ end
437
+
438
+ # Rake migration task to create the versioned table using options passed to acts_as_versioned
439
+ def create_versioned_table(create_table_options = {})
440
+ # create version column in main table if it does not exist
441
+ if !self.content_columns.find { |c| [version_column.to_s, 'lock_version'].include? c.name }
442
+ self.connection.add_column table_name, version_column, :integer
443
+ self.reset_column_information
444
+ end
445
+
446
+ return if connection.table_exists?(versioned_table_name)
447
+
448
+ self.connection.create_table(versioned_table_name, create_table_options) do |t|
449
+ t.column versioned_foreign_key, :integer
450
+ t.column version_column, :integer
451
+ t.column version_status_column, :string #this field is used for publishing status
452
+ t.column version_user_id_column, :integer #this field used who has modified this version
453
+ end
454
+
455
+ self.versioned_columns.each do |col|
456
+ self.connection.add_column versioned_table_name, col.name, col.type,
457
+ :limit => col.limit,
458
+ :default => col.default,
459
+ :scale => col.scale,
460
+ :precision => col.precision
461
+ end
462
+
463
+ if type_col = self.columns_hash[inheritance_column]
464
+ self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
465
+ :limit => type_col.limit,
466
+ :default => type_col.default,
467
+ :scale => type_col.scale,
468
+ :precision => type_col.precision
469
+ end
470
+
471
+ self.connection.add_index versioned_table_name, versioned_foreign_key
472
+ end
473
+
474
+ # Rake migration task to drop the versioned table
475
+ def drop_versioned_table
476
+ self.connection.drop_table versioned_table_name
477
+ end
478
+
479
+ # Executes the block with the versioning callbacks disabled.
480
+ #
481
+ # Foo.without_revision do
482
+ # @foo.save
483
+ # end
484
+ #
485
+ def without_revision(&block)
486
+ class_eval do
487
+ CALLBACKS.each do |attr_name|
488
+ alias_method "orig_#{attr_name}".to_sym, attr_name
489
+ alias_method attr_name, :empty_callback
490
+ end
491
+ end
492
+ block.call
493
+ ensure
494
+ class_eval do
495
+ CALLBACKS.each do |attr_name|
496
+ alias_method attr_name, "orig_#{attr_name}".to_sym
497
+ end
498
+ end
499
+ end
500
+
501
+ # Turns off optimistic locking for the duration of the block
502
+ #
503
+ # Foo.without_locking do
504
+ # @foo.save
505
+ # end
506
+ #
507
+ def without_locking(&block)
508
+ current = ActiveRecord::Base.lock_optimistically
509
+ ActiveRecord::Base.lock_optimistically = false if current
510
+ begin
511
+ block.call
512
+ ensure
513
+ ActiveRecord::Base.lock_optimistically = true if current
514
+ end
515
+ end
516
+ end
517
+ end
518
+ end
519
+ end
520
+ end
521
+
522
+ ActiveRecord::Base.extend ActiveRecord::Acts::Versioned