db_acts_as_versioned 3.3.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fc529682ddb1d3441156a5301be62700e537af9e
4
+ data.tar.gz: 5002fe19442d09b66c5a4888fe36e49c03ff11fe
5
+ SHA512:
6
+ metadata.gz: 7b985b262d5a4a148ac7d77fdbd6682f3c19bc02f833ea605d5ec09bfea7a53468cd8a4612fbccddba2c454f2edb0700212ac9c8299db8ca93ebfe1ba847adce
7
+ data.tar.gz: 70fec674844344ed3a2e47a49b29a1c45c659f00e1c2e7156200657ec3dd80e63d35d364c0ea01dd43fe3f29a7b965f86d8ccc78b127e2cbb9e2f1f1eba72b8d
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'http://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'rails', '~> 3.1.0'
5
+ gem 'sqlite3'
6
+ gem 'echoe'
7
+ end
@@ -0,0 +1,101 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ actionmailer (3.1.4)
5
+ actionpack (= 3.1.4)
6
+ mail (~> 2.3.0)
7
+ actionpack (3.1.4)
8
+ activemodel (= 3.1.4)
9
+ activesupport (= 3.1.4)
10
+ builder (~> 3.0.0)
11
+ erubis (~> 2.7.0)
12
+ i18n (~> 0.6)
13
+ rack (~> 1.3.6)
14
+ rack-cache (~> 1.1)
15
+ rack-mount (~> 0.8.2)
16
+ rack-test (~> 0.6.1)
17
+ sprockets (~> 2.0.3)
18
+ activemodel (3.1.4)
19
+ activesupport (= 3.1.4)
20
+ builder (~> 3.0.0)
21
+ i18n (~> 0.6)
22
+ activerecord (3.1.4)
23
+ activemodel (= 3.1.4)
24
+ activesupport (= 3.1.4)
25
+ arel (~> 2.2.3)
26
+ tzinfo (~> 0.3.29)
27
+ activeresource (3.1.4)
28
+ activemodel (= 3.1.4)
29
+ activesupport (= 3.1.4)
30
+ activesupport (3.1.4)
31
+ multi_json (~> 1.0)
32
+ allison (2.0.3)
33
+ arel (2.2.3)
34
+ builder (3.0.0)
35
+ echoe (4.6.3)
36
+ allison (>= 2.0.3)
37
+ gemcutter (>= 0.7.0)
38
+ rake (>= 0.9.2)
39
+ rdoc (>= 3.6.1)
40
+ rubyforge (>= 2.0.4)
41
+ erubis (2.7.0)
42
+ gemcutter (0.7.1)
43
+ hike (1.2.1)
44
+ i18n (0.6.0)
45
+ json (1.6.6)
46
+ json_pure (1.7.7)
47
+ mail (2.3.3)
48
+ i18n (>= 0.4.0)
49
+ mime-types (~> 1.16)
50
+ treetop (~> 1.4.8)
51
+ mime-types (1.18)
52
+ multi_json (1.2.0)
53
+ polyglot (0.3.3)
54
+ rack (1.3.6)
55
+ rack-cache (1.2)
56
+ rack (>= 0.4)
57
+ rack-mount (0.8.3)
58
+ rack (>= 1.0.0)
59
+ rack-ssl (1.3.2)
60
+ rack
61
+ rack-test (0.6.1)
62
+ rack (>= 1.0)
63
+ rails (3.1.4)
64
+ actionmailer (= 3.1.4)
65
+ actionpack (= 3.1.4)
66
+ activerecord (= 3.1.4)
67
+ activeresource (= 3.1.4)
68
+ activesupport (= 3.1.4)
69
+ bundler (~> 1.0)
70
+ railties (= 3.1.4)
71
+ railties (3.1.4)
72
+ actionpack (= 3.1.4)
73
+ activesupport (= 3.1.4)
74
+ rack-ssl (~> 1.3.2)
75
+ rake (>= 0.8.7)
76
+ rdoc (~> 3.4)
77
+ thor (~> 0.14.6)
78
+ rake (0.9.2.2)
79
+ rdoc (3.12)
80
+ json (~> 1.4)
81
+ rubyforge (2.0.4)
82
+ json_pure (>= 1.1.7)
83
+ sprockets (2.0.3)
84
+ hike (~> 1.2)
85
+ rack (~> 1.0)
86
+ tilt (~> 1.1, != 1.3.0)
87
+ sqlite3 (1.3.5)
88
+ thor (0.14.6)
89
+ tilt (1.3.3)
90
+ treetop (1.4.10)
91
+ polyglot
92
+ polyglot (>= 0.3.1)
93
+ tzinfo (0.3.33)
94
+
95
+ PLATFORMS
96
+ ruby
97
+
98
+ DEPENDENCIES
99
+ echoe
100
+ rails (~> 3.1.0)
101
+ sqlite3
@@ -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.
@@ -0,0 +1,10 @@
1
+ Gemfile
2
+ Gemfile.lock
3
+ MIT-LICENSE
4
+ Manifest
5
+ README.md
6
+ RUNNING_UNIT_TESTS
7
+ Rakefile
8
+ acts_as_versioned.gemspec
9
+ init.rb
10
+ lib/acts_as_versioned.rb
@@ -0,0 +1,120 @@
1
+ ## About ##
2
+ =====
3
+
4
+ acts_as_versioned is a gem for Rails 3.1, 3.2 & 4 to enable easy versioning of models. As a versioned model is updated revisions are kept in a seperate table, providing a record of what changed.
5
+
6
+ ## Getting Started ##
7
+ =====
8
+
9
+ In your Gemfile simply include:
10
+
11
+ gem 'acts_as_versioned', :git => 'https://github.com/mjsommer/acts_as_versioned.git'
12
+
13
+ The next time you run `bundle install` you'll be all set to start using acts_as_versioned.
14
+
15
+ ## Usage ##
16
+ =====
17
+
18
+ #### Versioning a Model ####
19
+ By default acts_as_versioned is unobtrusive. You will need to explicitly state which models to version. To do so, add the line `acts_as_versioned` to your model, like so:
20
+
21
+ class MyModel < ActiveRecord::Base
22
+ acts_as_versioned
23
+ #...
24
+ end
25
+
26
+ Next we need to create a migration to setup our versioning tables:
27
+
28
+ bundle exec rails generate migration AddVersioningToMyModel
29
+
30
+ Once that is completed, edit the generated migration. acts_as_versioned patches your model to add a `create_versioned_table` and `drop_versioned_table` method. A migration for `MyModel` (assuming MyModel already existed) might look like:
31
+
32
+ class AddVersioningToMyModel < ActiveRecord::Migration
33
+ def self.up
34
+ MyModel.create_versioned_table
35
+ end
36
+
37
+ def self.down
38
+ MyModel.drop_versioned_table
39
+ end
40
+ end
41
+
42
+ Execute your migration:
43
+
44
+ bundle exec rake db:migrate
45
+
46
+ And you're finished! Without any addition work, `MyModel` is being versioned.
47
+
48
+ #### Excluding attributes from versioning ####
49
+
50
+ Sometime you want to exclude an attribute of a model from being versioned. That can be accomplished with the `:except` paramter to `acts_as_versioned`:
51
+
52
+ class MyMode < ActiveRecord::Base
53
+ acts_as_versioned :except => :some_attr_i_dont_want_versioned
54
+
55
+ end
56
+
57
+
58
+ #### Revisions ####
59
+
60
+ Recording a history of changes to a model is only useful if you can do something with that data. With acts_as_versioned there are several ways you can interact with a model's revisions.
61
+
62
+ ##### Version Number #####
63
+ To determine what the current version number for a model is:
64
+
65
+ model.version
66
+
67
+ The `version` attribute is available for both the actual model, and also any revisions of a model. Thusly, the following is valid:
68
+
69
+ model.versions.last.version
70
+
71
+ ##### Revisions List #####
72
+ As alluded to above, you can get an array of revisions of a model via the `versions` attribute:
73
+
74
+ model.versions
75
+
76
+ The returned objects are of a type `MyModel::Version` where `MyModel` is the model you are working with. These objects have identical fields to `MyModel`. So, if `MyModel` had a `name` attribute, you could also say:
77
+
78
+ model.versions.last.name
79
+
80
+ ##### Reverting to a Revision #####
81
+ To revert a model to an older revision, simply call `revert_to` with the version number you desire to rever to:
82
+
83
+ model.revert_to(version_number)
84
+
85
+ ##### Saving Without Revisions #####
86
+ Occasionally you might need to save a model without necessary creating revisions. To do so, use the `save_without_revision` method:
87
+
88
+ model.save_without_revision
89
+
90
+
91
+ #### Migrations ####
92
+ Adding a field to your model does not automatically add it to the versioning table. So, when you add new fields, be sure to add them to both:
93
+
94
+ class AddNewFieldToMyModel < ActiveRecord::Migration
95
+ def change
96
+ add_column :my_models, :new_field_, :string
97
+ add_column :my_model_versions, :new_field_, :string
98
+ end
99
+ end
100
+
101
+ #### Version Class ####
102
+ As has been stated, the versioned data is stored seperately from the main class. This also implies that `model.versions` returns an area of object of a class _other_ than `MyModel` (where `model` is an instance of `MyModel`, keeping with our working example). The instances returned are actually of type `MyModel::Version`. With this, comes the fact that any methods, associations, etc. defined on `MyModel` are not present on `MyModel::Version`.
103
+
104
+ While this sounds obvious, it can some times be unexpected. Especially when acts_as_versioned make it so easy to grab historical records from a live record. A common scenario where this can come up is associations.
105
+
106
+ Say `MyModel` belongs to `TheMan`. Also, assume that you want to find out where (in the past) a particular instance of `MyModel` was updated in regards to it's association to `TheMan`. You could write that as:
107
+
108
+ model.versions.keep_if { |m| m.the_man != current_man }.last
109
+
110
+ However, this will not work. This is because `MyModel::Version` does _not_ belong to `TheMan`. You could compare ids here, or you could patch `MyModel::Version` to belong to `TheMan` like:
111
+
112
+ class MyModel
113
+ acts_as_versioned
114
+ belongs_to :the_men
115
+ #some stuff
116
+
117
+ class Version
118
+ belongs_to :the_men
119
+ end
120
+ end
@@ -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,13 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('acts_as_versioned', '3.3.0') do |p|
6
+ p.description = "Active Record model versioning"
7
+ p.url = "http://github.com/mjsommer/acts_as_versioned"
8
+ p.author = "Sommer Systems, LLC"
9
+ p.email = "martin@sommer.net"
10
+ p.dependencies = ['activerecord']
11
+ end
12
+
13
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "db_acts_as_versioned"
5
+ s.version = "3.3.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Sommer Systems, LLC"]
9
+ s.date = "2014-08-04"
10
+ s.description = "Active Record model versioning"
11
+ s.email = "martin@sommer.net"
12
+ s.extra_rdoc_files = ["README.md", "lib/acts_as_versioned.rb"]
13
+ s.files = ["Gemfile", "Gemfile.lock", "MIT-LICENSE", "Manifest", "README.md", "RUNNING_UNIT_TESTS", "Rakefile", "acts_as_versioned.gemspec", "init.rb", "lib/acts_as_versioned.rb"]
14
+ s.homepage = "http://github.com/mjsommer/acts_as_versioned"
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Acts_as_versioned", "--main", "README.md"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = "db_acts_as_versioned"
18
+ s.rubygems_version = "1.8.25"
19
+ s.summary = "Active Record model versioning"
20
+
21
+ if s.respond_to? :specification_version then
22
+ s.specification_version = 3
23
+
24
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
25
+ s.add_runtime_dependency(%q<activerecord>, [">= 0"])
26
+ else
27
+ s.add_dependency(%q<activerecord>, [">= 0"])
28
+ end
29
+ else
30
+ s.add_dependency(%q<activerecord>, [">= 0"])
31
+ end
32
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'acts_as_versioned'
@@ -0,0 +1,506 @@
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 = "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, :version_except_columns
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.version_except_columns = [options[:except]].flatten.map(&:to_s) #these columns are kept in _versioned, but changing them does not excplitly cause a version change
180
+ self.non_versioned_columns = [self.primary_key, inheritance_column, self.version_column, 'lock_version', versioned_inheritance_column] #these columns are excluded from _versions, and changing them does not cause a version change
181
+ self.version_association_options = {
182
+ :class_name => "#{self.to_s}::#{versioned_class_name}",
183
+ :foreign_key => versioned_foreign_key,
184
+ :dependent => :delete_all
185
+ }.merge(options[:association_options] || {})
186
+
187
+ if block_given?
188
+ extension_module_name = "#{versioned_class_name}Extension"
189
+ silence_warnings do
190
+ self.const_set(extension_module_name, Module.new(&extension))
191
+ end
192
+
193
+ options[:extend] = self.const_get(extension_module_name)
194
+ end
195
+
196
+ unless options[:if_changed].nil?
197
+ self.track_altered_attributes = true
198
+ options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
199
+ self.version_if_changed = options[:if_changed].map(&:to_s)
200
+ end
201
+
202
+ include options[:extend] if options[:extend].is_a?(Module)
203
+
204
+ include ActiveRecord::Acts::Versioned::Behaviors
205
+
206
+ #
207
+ # Create the dynamic versioned model
208
+ #
209
+ const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
210
+ def self.reloadable?;
211
+ false;
212
+ end
213
+
214
+ # find first version before the given version
215
+ def self.before(version)
216
+ where(["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]).
217
+ order('version DESC').
218
+ first
219
+ end
220
+
221
+ # find first version after the given version.
222
+ def self.after(version)
223
+ where(["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]).
224
+ order('version ASC').
225
+ first
226
+ end
227
+
228
+ # finds earliest version of this record
229
+ def self.earliest
230
+ order("#{original_class.version_column}").first
231
+ end
232
+
233
+ # find latest version of this record
234
+ def self.latest
235
+ order("#{original_class.version_column} desc").first
236
+ end
237
+
238
+ def previous
239
+ self.class.before(self)
240
+ end
241
+
242
+ def next
243
+ self.class.after(self)
244
+ end
245
+
246
+ def versions_count
247
+ page.version
248
+ end
249
+ end
250
+
251
+ versioned_class.cattr_accessor :original_class
252
+ versioned_class.original_class = self
253
+ versioned_class.table_name = versioned_table_name
254
+ versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
255
+ :class_name => "::#{self.to_s}",
256
+ :foreign_key => versioned_foreign_key
257
+ versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
258
+ versioned_class.set_sequence_name version_sequence_name if version_sequence_name
259
+ end
260
+
261
+ module Behaviors
262
+ extend ActiveSupport::Concern
263
+
264
+ included do
265
+ has_many :versions, self.version_association_options
266
+
267
+ before_save :set_new_version
268
+ after_save :save_version
269
+ after_save :clear_old_versions
270
+ end
271
+
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.create
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
281
+ end
282
+ end
283
+
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]
291
+ end
292
+ end
293
+
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
+ changed.map { |c| self.class.versioned_columns.map(&:name).include?(c) & !self.class.version_except_columns.include?(c) }.any?
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
+ next unless orig_model.has_attribute?(col.name)
336
+ define_method(new_model, col.name.to_sym)
337
+ new_model.send("#{col.name.to_sym}=", orig_model.send(col.name))
338
+ end
339
+
340
+ if orig_model.is_a?(self.class.versioned_class)
341
+ new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
342
+ elsif new_model.is_a?(self.class.versioned_class)
343
+ sym = self.class.versioned_inheritance_column.to_sym
344
+ define_method new_model, sym
345
+ new_model.send("#{sym}=", orig_model[orig_model.class.inheritance_column]) if orig_model[orig_model.class.inheritance_column]
346
+ end
347
+ end
348
+
349
+ def define_method(object, method)
350
+ return if object.methods.include? method
351
+ metaclass = class << object; self; end
352
+ metaclass.send :attr_accessor, method
353
+ end
354
+
355
+ # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
356
+ def save_version?
357
+ version_condition_met? && altered?
358
+ end
359
+
360
+ # Checks condition set in the :if option to check whether a revision should be created or not. Override this for
361
+ # custom version condition checking.
362
+ def version_condition_met?
363
+ case
364
+ when version_condition.is_a?(Symbol)
365
+ send(version_condition)
366
+ when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
367
+ version_condition.call(self)
368
+ else
369
+ version_condition
370
+ end
371
+ end
372
+
373
+ # Executes the block with the versioning callbacks disabled.
374
+ #
375
+ # @foo.without_revision do
376
+ # @foo.save
377
+ # end
378
+ #
379
+ def without_revision(&block)
380
+ self.class.without_revision(&block)
381
+ end
382
+
383
+ # Turns off optimistic locking for the duration of the block
384
+ #
385
+ # @foo.without_locking do
386
+ # @foo.save
387
+ # end
388
+ #
389
+ def without_locking(&block)
390
+ self.class.without_locking(&block)
391
+ end
392
+
393
+ def empty_callback()
394
+ end
395
+
396
+ #:nodoc:
397
+
398
+ protected
399
+ # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
400
+ def set_new_version
401
+ @saving_version = new_record? || save_version?
402
+ self.send("#{self.class.version_column}=", next_version) if new_record? || (!locking_enabled? && save_version?)
403
+ end
404
+
405
+ # Gets the next available version for the current record, or 1 for a new record
406
+ def next_version
407
+ (new_record? ? 0 : versions.calculate(:maximum, version_column).to_i) + 1
408
+ end
409
+
410
+
411
+ module ClassMethods
412
+ # Returns an array of columns that are versioned. See non_versioned_columns
413
+ def versioned_columns
414
+ @versioned_columns ||= columns.select { |c| !non_versioned_columns.include?(c.name) }
415
+ end
416
+
417
+ # Returns an instance of the dynamic versioned model
418
+ def versioned_class
419
+ const_get versioned_class_name
420
+ end
421
+
422
+ # Rake migration task to create the versioned table using options passed to acts_as_versioned
423
+ def create_versioned_table(create_table_options = {})
424
+ # create version column in main table if it does not exist
425
+ if !self.content_columns.find { |c| [version_column.to_s, 'lock_version'].include? c.name }
426
+ self.connection.add_column table_name, version_column, :integer
427
+ self.reset_column_information
428
+ end
429
+
430
+ return if connection.table_exists?(versioned_table_name)
431
+
432
+ self.connection.create_table(versioned_table_name, create_table_options) do |t|
433
+ t.column versioned_foreign_key, :integer
434
+ t.column version_column, :integer
435
+ end
436
+
437
+ self.versioned_columns.each do |col|
438
+ self.connection.add_column versioned_table_name, col.name, col.type,
439
+ :limit => col.limit,
440
+ :default => col.default,
441
+ :scale => col.scale,
442
+ :precision => col.precision
443
+ end
444
+
445
+ if type_col = self.columns_hash[inheritance_column]
446
+ self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
447
+ :limit => type_col.limit,
448
+ :default => type_col.default,
449
+ :scale => type_col.scale,
450
+ :precision => type_col.precision
451
+ end
452
+
453
+ # Limit index name length to 63, the Postgresql limit of NAMEDATALEN-1.
454
+ name = 'index_' + versioned_table_name + '_on_' + versioned_foreign_key
455
+ self.connection.add_index versioned_table_name, versioned_foreign_key, :name => name[0,63]
456
+ end
457
+
458
+ # Rake migration task to drop the versioned table
459
+ def drop_versioned_table
460
+ self.connection.drop_table versioned_table_name
461
+ end
462
+
463
+ # Executes the block with the versioning callbacks disabled.
464
+ #
465
+ # Foo.without_revision do
466
+ # @foo.save
467
+ # end
468
+ #
469
+ def without_revision(&block)
470
+ class_eval do
471
+ CALLBACKS.each do |attr_name|
472
+ alias_method "orig_#{attr_name}".to_sym, attr_name
473
+ alias_method attr_name, :empty_callback
474
+ end
475
+ end
476
+ block.call
477
+ ensure
478
+ class_eval do
479
+ CALLBACKS.each do |attr_name|
480
+ alias_method attr_name, "orig_#{attr_name}".to_sym
481
+ end
482
+ end
483
+ end
484
+
485
+ # Turns off optimistic locking for the duration of the block
486
+ #
487
+ # Foo.without_locking do
488
+ # @foo.save
489
+ # end
490
+ #
491
+ def without_locking(&block)
492
+ current = ActiveRecord::Base.lock_optimistically
493
+ ActiveRecord::Base.lock_optimistically = false if current
494
+ begin
495
+ block.call
496
+ ensure
497
+ ActiveRecord::Base.lock_optimistically = true if current
498
+ end
499
+ end
500
+ end
501
+ end
502
+ end
503
+ end
504
+ end
505
+
506
+ ActiveRecord::Base.extend ActiveRecord::Acts::Versioned
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: db_acts_as_versioned
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Sommer Systems, LLC
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Active Record model versioning
28
+ email: martin@sommer.net
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files:
32
+ - README.md
33
+ - lib/acts_as_versioned.rb
34
+ files:
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - MIT-LICENSE
38
+ - Manifest
39
+ - README.md
40
+ - RUNNING_UNIT_TESTS
41
+ - Rakefile
42
+ - acts_as_versioned.gemspec
43
+ - init.rb
44
+ - lib/acts_as_versioned.rb
45
+ homepage: http://github.com/mjsommer/acts_as_versioned
46
+ licenses: []
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --line-numbers
51
+ - --inline-source
52
+ - --title
53
+ - Acts_as_versioned
54
+ - --main
55
+ - README.md
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '1.2'
68
+ requirements: []
69
+ rubyforge_project: db_acts_as_versioned
70
+ rubygems_version: 2.0.3
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Active Record model versioning
74
+ test_files: []