Commit 5a8e79d5f6c81febe387d6fbfda910f89ebe3cba
1 parent
bacbab7d
Exists in
master
and in
1 other branch
add acts_as_versioned plugin to Question model
Showing
28 changed files
with
1631 additions
and
2 deletions
Show diff stats
app/models/question.rb
1 | 1 | class Question < ActiveRecord::Base |
2 | + acts_as_versioned | |
3 | + | |
2 | 4 | require 'set' |
3 | 5 | include Utility |
4 | 6 | extend ActiveSupport::Memoizable |
5 | - | |
7 | + | |
6 | 8 | belongs_to :creator, :class_name => "Visitor", :foreign_key => "creator_id" |
7 | 9 | belongs_to :site, :class_name => "User", :foreign_key => "site_id" |
8 | 10 | ... | ... |
... | ... | @@ -0,0 +1,20 @@ |
1 | +class AddVersionToQuestion < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + # set default to one because we're going to create | |
4 | + # versions for all the existing data. | |
5 | + add_column :questions, :version, :integer, :default => 1 | |
6 | + Question.create_versioned_table | |
7 | + Question.find(:all).each do |q| | |
8 | + attributes = q.attributes | |
9 | + attributes[q.versioned_foreign_key] = attributes.delete("id") | |
10 | + Question::Version.create(attributes) | |
11 | + end | |
12 | + # make version nil by default after we've created initial versions | |
13 | + change_column :questions, :version, :integer, :default => nil | |
14 | + end | |
15 | + | |
16 | + def self.down | |
17 | + remove_column :questions, :version | |
18 | + Question.drop_versioned_table | |
19 | + end | |
20 | +end | ... | ... |
db/schema.rb
... | ... | @@ -9,7 +9,7 @@ |
9 | 9 | # |
10 | 10 | # It's strongly recommended to check this file into your version control system. |
11 | 11 | |
12 | -ActiveRecord::Schema.define(:version => 20110131154228) do | |
12 | +ActiveRecord::Schema.define(:version => 20110524171217) do | |
13 | 13 | |
14 | 14 | create_table "appearances", :force => true do |t| |
15 | 15 | t.integer "voter_id" |
... | ... | @@ -142,6 +142,29 @@ ActiveRecord::Schema.define(:version => 20110131154228) do |
142 | 142 | add_index "prompts", ["question_id"], :name => "index_prompts_on_question_id" |
143 | 143 | add_index "prompts", ["right_choice_id"], :name => "index_prompts_on_right_choice_id" |
144 | 144 | |
145 | + create_table "question_versions", :force => true do |t| | |
146 | + t.integer "question_id" | |
147 | + t.integer "version" | |
148 | + t.integer "creator_id" | |
149 | + t.string "name", :default => "" | |
150 | + t.datetime "created_at" | |
151 | + t.datetime "updated_at" | |
152 | + t.integer "choices_count", :default => 0 | |
153 | + t.integer "prompts_count", :default => 0 | |
154 | + t.boolean "active", :default => false | |
155 | + t.text "tracking" | |
156 | + t.text "information" | |
157 | + t.integer "site_id" | |
158 | + t.string "local_identifier" | |
159 | + t.integer "votes_count", :default => 0 | |
160 | + t.boolean "it_should_autoactivate_ideas", :default => false | |
161 | + t.integer "inactive_choices_count", :default => 0 | |
162 | + t.boolean "uses_catchup", :default => true | |
163 | + t.boolean "show_results", :default => true | |
164 | + end | |
165 | + | |
166 | + add_index "question_versions", ["question_id"], :name => "index_question_versions_on_question_id" | |
167 | + | |
145 | 168 | create_table "questions", :force => true do |t| |
146 | 169 | t.integer "creator_id" |
147 | 170 | t.string "name", :default => "" |
... | ... | @@ -159,6 +182,7 @@ ActiveRecord::Schema.define(:version => 20110131154228) do |
159 | 182 | t.integer "inactive_choices_count", :default => 0 |
160 | 183 | t.boolean "uses_catchup", :default => true |
161 | 184 | t.boolean "show_results", :default => true |
185 | + t.integer "version" | |
162 | 186 | end |
163 | 187 | |
164 | 188 | create_table "skips", :force => true do |t| | ... | ... |
... | ... | @@ -0,0 +1,82 @@ |
1 | +*GIT* (version numbers are overrated) | |
2 | + | |
3 | +* (16 Jun 2008) Backwards Compatibility is overrated (big updates for rails 2.1) | |
4 | + | |
5 | + * Use ActiveRecord 2.1's dirty attribute checking instead [Asa Calow] | |
6 | + * Remove last traces of #non_versioned_fields | |
7 | + * Remove AR::Base.find_version and AR::Base.find_versions, rely on AR association proxies and named_scope | |
8 | + * Remove #versions_count, rely on AR association counter caching. | |
9 | + * Remove #versioned_attributes, basically the same as AR::Base.versioned_columns | |
10 | + | |
11 | +* (5 Oct 2006) Allow customization of #versions association options [Dan Peterson] | |
12 | + | |
13 | +*0.5.1* | |
14 | + | |
15 | +* (8 Aug 2006) Versioned models now belong to the unversioned model. @article_version.article.class => Article [Aslak Hellesoy] | |
16 | + | |
17 | +*0.5* # do versions even matter for plugins? | |
18 | + | |
19 | +* (21 Apr 2006) Added without_locking and without_revision methods. | |
20 | + | |
21 | + Foo.without_revision do | |
22 | + @foo.update_attributes ... | |
23 | + end | |
24 | + | |
25 | +*0.4* | |
26 | + | |
27 | +* (28 March 2006) Rename non_versioned_fields to non_versioned_columns (old one is kept for compatibility). | |
28 | +* (28 March 2006) Made explicit documentation note that string column names are required for non_versioned_columns. | |
29 | + | |
30 | +*0.3.1* | |
31 | + | |
32 | +* (7 Jan 2006) explicitly set :foreign_key option for the versioned model's belongs_to assocation for STI [Caged] | |
33 | +* (7 Jan 2006) added tests to prove has_many :through joins work | |
34 | + | |
35 | +*0.3* | |
36 | + | |
37 | +* (2 Jan 2006) added ability to share a mixin with versioned class | |
38 | +* (2 Jan 2006) changed the dynamic version model to MyModel::Version | |
39 | + | |
40 | +*0.2.4* | |
41 | + | |
42 | +* (27 Nov 2005) added note about possible destructive behavior of if_changed? [Michael Schuerig] | |
43 | + | |
44 | +*0.2.3* | |
45 | + | |
46 | +* (12 Nov 2005) fixed bug with old behavior of #blank? [Michael Schuerig] | |
47 | +* (12 Nov 2005) updated tests to use ActiveRecord Schema | |
48 | + | |
49 | +*0.2.2* | |
50 | + | |
51 | +* (3 Nov 2005) added documentation note to #acts_as_versioned [Martin Jul] | |
52 | + | |
53 | +*0.2.1* | |
54 | + | |
55 | +* (6 Oct 2005) renamed dirty? to changed? to keep it uniform. it was aliased to keep it backwards compatible. | |
56 | + | |
57 | +*0.2* | |
58 | + | |
59 | +* (6 Oct 2005) added find_versions and find_version class methods. | |
60 | + | |
61 | +* (6 Oct 2005) removed transaction from create_versioned_table(). | |
62 | + this way you can specify your own transaction around a group of operations. | |
63 | + | |
64 | +* (30 Sep 2005) fixed bug where find_versions() would order by 'version' twice. (found by Joe Clark) | |
65 | + | |
66 | +* (26 Sep 2005) added :sequence_name option to acts_as_versioned to set the sequence name on the versioned model | |
67 | + | |
68 | +*0.1.3* (18 Sep 2005) | |
69 | + | |
70 | +* First RubyForge release | |
71 | + | |
72 | +*0.1.2* | |
73 | + | |
74 | +* check if module is already included when acts_as_versioned is called | |
75 | + | |
76 | +*0.1.1* | |
77 | + | |
78 | +* Adding tests and rdocs | |
79 | + | |
80 | +*0.1* | |
81 | + | |
82 | +* Initial transfer from Rails ticket: http://dev.rubyonrails.com/ticket/1974 | |
0 | 83 | \ No newline at end of file | ... | ... |
... | ... | @@ -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 | 21 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,28 @@ |
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 | +Rubyforge project | |
12 | + | |
13 | +* http://rubyforge.org/projects/ar-versioned | |
14 | + | |
15 | +RDocs | |
16 | + | |
17 | +* http://ar-versioned.rubyforge.org | |
18 | + | |
19 | +Subversion | |
20 | + | |
21 | +* http://techno-weenie.net/svn/projects/acts_as_versioned | |
22 | + | |
23 | +Collaboa | |
24 | + | |
25 | +* http://collaboa.techno-weenie.net/repository/browse/acts_as_versioned | |
26 | + | |
27 | +Special thanks to Dreamer on ##rubyonrails for help in early testing. His ServerSideWiki (http://serversidewiki.com) | |
28 | +was the first project to use acts_as_versioned <em>in the wild</em>. | |
0 | 29 | \ No newline at end of file | ... | ... |
... | ... | @@ -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,180 @@ |
1 | +require 'rubygems' | |
2 | + | |
3 | +require 'rake/rdoctask' | |
4 | +require 'rake/packagetask' | |
5 | +require 'rake/gempackagetask' | |
6 | +require 'rake/testtask' | |
7 | +require 'rake/contrib/rubyforgepublisher' | |
8 | + | |
9 | +PKG_NAME = 'acts_as_versioned' | |
10 | +PKG_VERSION = '0.3.1' | |
11 | +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" | |
12 | +PROD_HOST = "technoweenie@bidwell.textdrive.com" | |
13 | +RUBY_FORGE_PROJECT = 'ar-versioned' | |
14 | +RUBY_FORGE_USER = 'technoweenie' | |
15 | + | |
16 | +desc 'Default: run unit tests.' | |
17 | +task :default => :test | |
18 | + | |
19 | +desc 'Test the calculations plugin.' | |
20 | +Rake::TestTask.new(:test) do |t| | |
21 | + t.libs << 'lib' | |
22 | + t.pattern = 'test/**/*_test.rb' | |
23 | + t.verbose = true | |
24 | +end | |
25 | + | |
26 | +desc 'Generate documentation for the calculations plugin.' | |
27 | +Rake::RDocTask.new(:rdoc) do |rdoc| | |
28 | + rdoc.rdoc_dir = 'rdoc' | |
29 | + rdoc.title = "#{PKG_NAME} -- Simple versioning with active record models" | |
30 | + rdoc.options << '--line-numbers --inline-source' | |
31 | + rdoc.rdoc_files.include('README', 'CHANGELOG', 'RUNNING_UNIT_TESTS') | |
32 | + rdoc.rdoc_files.include('lib/**/*.rb') | |
33 | +end | |
34 | + | |
35 | +spec = Gem::Specification.new do |s| | |
36 | + s.name = PKG_NAME | |
37 | + s.version = PKG_VERSION | |
38 | + s.platform = Gem::Platform::RUBY | |
39 | + s.summary = "Simple versioning with active record models" | |
40 | + s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG RUNNING_UNIT_TESTS) | |
41 | + s.files.delete "acts_as_versioned_plugin.sqlite.db" | |
42 | + s.files.delete "acts_as_versioned_plugin.sqlite3.db" | |
43 | + s.files.delete "test/debug.log" | |
44 | + s.require_path = 'lib' | |
45 | + s.autorequire = 'acts_as_versioned' | |
46 | + s.has_rdoc = true | |
47 | + s.test_files = Dir['test/**/*_test.rb'] | |
48 | + s.add_dependency 'activerecord', '>= 1.10.1' | |
49 | + s.add_dependency 'activesupport', '>= 1.1.1' | |
50 | + s.author = "Rick Olson" | |
51 | + s.email = "technoweenie@gmail.com" | |
52 | + s.homepage = "http://techno-weenie.net" | |
53 | +end | |
54 | + | |
55 | +Rake::GemPackageTask.new(spec) do |pkg| | |
56 | + pkg.need_tar = true | |
57 | +end | |
58 | + | |
59 | +desc "Publish the API documentation" | |
60 | +task :pdoc => [:rdoc] do | |
61 | + Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload | |
62 | +end | |
63 | + | |
64 | +desc 'Publish the gem and API docs' | |
65 | +task :publish => [:pdoc, :rubyforge_upload] | |
66 | + | |
67 | +desc "Publish the release files to RubyForge." | |
68 | +task :rubyforge_upload => :package do | |
69 | + files = %w(gem tgz).map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" } | |
70 | + | |
71 | + if RUBY_FORGE_PROJECT then | |
72 | + require 'net/http' | |
73 | + require 'open-uri' | |
74 | + | |
75 | + project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/" | |
76 | + project_data = open(project_uri) { |data| data.read } | |
77 | + group_id = project_data[/[?&]group_id=(\d+)/, 1] | |
78 | + raise "Couldn't get group id" unless group_id | |
79 | + | |
80 | + # This echos password to shell which is a bit sucky | |
81 | + if ENV["RUBY_FORGE_PASSWORD"] | |
82 | + password = ENV["RUBY_FORGE_PASSWORD"] | |
83 | + else | |
84 | + print "#{RUBY_FORGE_USER}@rubyforge.org's password: " | |
85 | + password = STDIN.gets.chomp | |
86 | + end | |
87 | + | |
88 | + login_response = Net::HTTP.start("rubyforge.org", 80) do |http| | |
89 | + data = [ | |
90 | + "login=1", | |
91 | + "form_loginname=#{RUBY_FORGE_USER}", | |
92 | + "form_pw=#{password}" | |
93 | + ].join("&") | |
94 | + http.post("/account/login.php", data) | |
95 | + end | |
96 | + | |
97 | + cookie = login_response["set-cookie"] | |
98 | + raise "Login failed" unless cookie | |
99 | + headers = { "Cookie" => cookie } | |
100 | + | |
101 | + release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}" | |
102 | + release_data = open(release_uri, headers) { |data| data.read } | |
103 | + package_id = release_data[/[?&]package_id=(\d+)/, 1] | |
104 | + raise "Couldn't get package id" unless package_id | |
105 | + | |
106 | + first_file = true | |
107 | + release_id = "" | |
108 | + | |
109 | + files.each do |filename| | |
110 | + basename = File.basename(filename) | |
111 | + file_ext = File.extname(filename) | |
112 | + file_data = File.open(filename, "rb") { |file| file.read } | |
113 | + | |
114 | + puts "Releasing #{basename}..." | |
115 | + | |
116 | + release_response = Net::HTTP.start("rubyforge.org", 80) do |http| | |
117 | + release_date = Time.now.strftime("%Y-%m-%d %H:%M") | |
118 | + type_map = { | |
119 | + ".zip" => "3000", | |
120 | + ".tgz" => "3110", | |
121 | + ".gz" => "3110", | |
122 | + ".gem" => "1400" | |
123 | + }; type_map.default = "9999" | |
124 | + type = type_map[file_ext] | |
125 | + boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor" | |
126 | + | |
127 | + query_hash = if first_file then | |
128 | + { | |
129 | + "group_id" => group_id, | |
130 | + "package_id" => package_id, | |
131 | + "release_name" => PKG_FILE_NAME, | |
132 | + "release_date" => release_date, | |
133 | + "type_id" => type, | |
134 | + "processor_id" => "8000", # Any | |
135 | + "release_notes" => "", | |
136 | + "release_changes" => "", | |
137 | + "preformatted" => "1", | |
138 | + "submit" => "1" | |
139 | + } | |
140 | + else | |
141 | + { | |
142 | + "group_id" => group_id, | |
143 | + "release_id" => release_id, | |
144 | + "package_id" => package_id, | |
145 | + "step2" => "1", | |
146 | + "type_id" => type, | |
147 | + "processor_id" => "8000", # Any | |
148 | + "submit" => "Add This File" | |
149 | + } | |
150 | + end | |
151 | + | |
152 | + query = "?" + query_hash.map do |(name, value)| | |
153 | + [name, URI.encode(value)].join("=") | |
154 | + end.join("&") | |
155 | + | |
156 | + data = [ | |
157 | + "--" + boundary, | |
158 | + "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"", | |
159 | + "Content-Type: application/octet-stream", | |
160 | + "Content-Transfer-Encoding: binary", | |
161 | + "", file_data, "" | |
162 | + ].join("\x0D\x0A") | |
163 | + | |
164 | + release_headers = headers.merge( | |
165 | + "Content-Type" => "multipart/form-data; boundary=#{boundary}" | |
166 | + ) | |
167 | + | |
168 | + target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php" | |
169 | + http.post(target + query, data, release_headers) | |
170 | + end | |
171 | + | |
172 | + if first_file then | |
173 | + release_id = release_response.body[/release_id=(\d+)/, 1] | |
174 | + raise("Couldn't get release id") unless release_id | |
175 | + end | |
176 | + | |
177 | + first_file = false | |
178 | + end | |
179 | + end | |
180 | +end | |
0 | 181 | \ No newline at end of file | ... | ... |
vendor/plugins/acts_as_versioned/acts_as_versioned.gemspec
0 → 100644
... | ... | @@ -0,0 +1,29 @@ |
1 | +# -*- encoding: utf-8 -*- | |
2 | + | |
3 | +Gem::Specification.new do |s| | |
4 | + s.name = %q{acts_as_versioned} | |
5 | + s.version = "0.5.2" | |
6 | + | |
7 | + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= | |
8 | + s.authors = ["technoweenie"] | |
9 | + s.date = %q{2009-01-20} | |
10 | + s.description = %q{TODO} | |
11 | + s.email = %q{technoweenie@bidwell.textdrive.com} | |
12 | + s.files = ["VERSION.yml", "lib/acts_as_versioned.rb", "test/abstract_unit.rb", "test/database.yml", "test/fixtures", "test/fixtures/authors.yml", "test/fixtures/landmark.rb", "test/fixtures/landmark_versions.yml", "test/fixtures/landmarks.yml", "test/fixtures/locked_pages.yml", "test/fixtures/locked_pages_revisions.yml", "test/fixtures/migrations", "test/fixtures/migrations/1_add_versioned_tables.rb", "test/fixtures/page.rb", "test/fixtures/page_versions.yml", "test/fixtures/pages.yml", "test/fixtures/widget.rb", "test/migration_test.rb", "test/schema.rb", "test/versioned_test.rb"] | |
13 | + s.has_rdoc = true | |
14 | + s.homepage = %q{http://github.com/technoweenie/acts_as_versioned} | |
15 | + s.rdoc_options = ["--inline-source", "--charset=UTF-8"] | |
16 | + s.require_paths = ["lib"] | |
17 | + s.rubygems_version = %q{1.3.1} | |
18 | + s.summary = %q{TODO} | |
19 | + | |
20 | + if s.respond_to? :specification_version then | |
21 | + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION | |
22 | + s.specification_version = 2 | |
23 | + | |
24 | + if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then | |
25 | + else | |
26 | + end | |
27 | + else | |
28 | + end | |
29 | +end | ... | ... |
vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb
0 → 100644
... | ... | @@ -0,0 +1,486 @@ |
1 | +# Copyright (c) 2005 Rick Olson | |
2 | +# | |
3 | +# Permission is hereby granted, free of charge, to any person obtaining | |
4 | +# a copy of this software and associated documentation files (the | |
5 | +# "Software"), to deal in the Software without restriction, including | |
6 | +# without limitation the rights to use, copy, modify, merge, publish, | |
7 | +# distribute, sublicense, and/or sell copies of the Software, and to | |
8 | +# permit persons to whom the Software is furnished to do so, subject to | |
9 | +# the following conditions: | |
10 | +# | |
11 | +# The above copyright notice and this permission notice shall be | |
12 | +# included in all copies or substantial portions of the Software. | |
13 | +# | |
14 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
15 | +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
16 | +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
17 | +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
18 | +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
19 | +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
20 | +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
21 | + | |
22 | +module ActiveRecord #:nodoc: | |
23 | + module Acts #:nodoc: | |
24 | + # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a | |
25 | + # versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version | |
26 | + # column is present as well. | |
27 | + # | |
28 | + # The class for the versioned model is derived the first time it is seen. Therefore, if you change your database schema you have to restart | |
29 | + # your container for the changes to be reflected. In development mode this usually means restarting WEBrick. | |
30 | + # | |
31 | + # class Page < ActiveRecord::Base | |
32 | + # # assumes pages_versions table | |
33 | + # acts_as_versioned | |
34 | + # end | |
35 | + # | |
36 | + # Example: | |
37 | + # | |
38 | + # page = Page.create(:title => 'hello world!') | |
39 | + # page.version # => 1 | |
40 | + # | |
41 | + # page.title = 'hello world' | |
42 | + # page.save | |
43 | + # page.version # => 2 | |
44 | + # page.versions.size # => 2 | |
45 | + # | |
46 | + # page.revert_to(1) # using version number | |
47 | + # page.title # => 'hello world!' | |
48 | + # | |
49 | + # page.revert_to(page.versions.last) # using versioned instance | |
50 | + # page.title # => 'hello world' | |
51 | + # | |
52 | + # page.versions.earliest # efficient query to find the first version | |
53 | + # page.versions.latest # efficient query to find the most recently created version | |
54 | + # | |
55 | + # | |
56 | + # Simple Queries to page between versions | |
57 | + # | |
58 | + # page.versions.before(version) | |
59 | + # page.versions.after(version) | |
60 | + # | |
61 | + # Access the previous/next versions from the versioned model itself | |
62 | + # | |
63 | + # version = page.versions.latest | |
64 | + # version.previous # go back one version | |
65 | + # version.next # go forward one version | |
66 | + # | |
67 | + # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options | |
68 | + module Versioned | |
69 | + CALLBACKS = [:set_new_version, :save_version, :save_version?] | |
70 | + def self.included(base) # :nodoc: | |
71 | + base.extend ClassMethods | |
72 | + end | |
73 | + | |
74 | + module ClassMethods | |
75 | + # == Configuration options | |
76 | + # | |
77 | + # * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example) | |
78 | + # * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example) | |
79 | + # * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example) | |
80 | + # * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type) | |
81 | + # * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version) | |
82 | + # * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model. | |
83 | + # * <tt>limit</tt> - number of revisions to keep, defaults to unlimited | |
84 | + # * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved. | |
85 | + # For finer control, pass either a Proc or modify Model#version_condition_met? | |
86 | + # | |
87 | + # acts_as_versioned :if => Proc.new { |auction| !auction.expired? } | |
88 | + # | |
89 | + # or... | |
90 | + # | |
91 | + # class Auction | |
92 | + # def version_condition_met? # totally bypasses the <tt>:if</tt> option | |
93 | + # !expired? | |
94 | + # end | |
95 | + # end | |
96 | + # | |
97 | + # * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes | |
98 | + # either a symbol or array of symbols. | |
99 | + # | |
100 | + # * <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 | |
101 | + # to create an anonymous mixin: | |
102 | + # | |
103 | + # class Auction | |
104 | + # acts_as_versioned do | |
105 | + # def started? | |
106 | + # !started_at.nil? | |
107 | + # end | |
108 | + # end | |
109 | + # end | |
110 | + # | |
111 | + # or... | |
112 | + # | |
113 | + # module AuctionExtension | |
114 | + # def started? | |
115 | + # !started_at.nil? | |
116 | + # end | |
117 | + # end | |
118 | + # class Auction | |
119 | + # acts_as_versioned :extend => AuctionExtension | |
120 | + # end | |
121 | + # | |
122 | + # Example code: | |
123 | + # | |
124 | + # @auction = Auction.find(1) | |
125 | + # @auction.started? | |
126 | + # @auction.versions.first.started? | |
127 | + # | |
128 | + # == Database Schema | |
129 | + # | |
130 | + # The model that you're versioning needs to have a 'version' attribute. The model is versioned | |
131 | + # into a table called #{model}_versions where the model name is singlular. The _versions table should | |
132 | + # contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field. | |
133 | + # | |
134 | + # A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance, | |
135 | + # then that field is reflected in the versioned model as 'versioned_type' by default. | |
136 | + # | |
137 | + # Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table | |
138 | + # method, perfect for a migration. It will also create the version column if the main model does not already have it. | |
139 | + # | |
140 | + # class AddVersions < ActiveRecord::Migration | |
141 | + # def self.up | |
142 | + # # create_versioned_table takes the same options hash | |
143 | + # # that create_table does | |
144 | + # Post.create_versioned_table | |
145 | + # end | |
146 | + # | |
147 | + # def self.down | |
148 | + # Post.drop_versioned_table | |
149 | + # end | |
150 | + # end | |
151 | + # | |
152 | + # == Changing What Fields Are Versioned | |
153 | + # | |
154 | + # By default, acts_as_versioned will version all but these fields: | |
155 | + # | |
156 | + # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column] | |
157 | + # | |
158 | + # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols. | |
159 | + # | |
160 | + # class Post < ActiveRecord::Base | |
161 | + # acts_as_versioned | |
162 | + # self.non_versioned_columns << 'comments_count' | |
163 | + # end | |
164 | + # | |
165 | + def acts_as_versioned(options = {}, &extension) | |
166 | + # don't allow multiple calls | |
167 | + return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods) | |
168 | + | |
169 | + send :include, ActiveRecord::Acts::Versioned::ActMethods | |
170 | + | |
171 | + cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column, | |
172 | + :version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns, | |
173 | + :version_association_options, :version_if_changed | |
174 | + | |
175 | + self.versioned_class_name = options[:class_name] || "Version" | |
176 | + self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key | |
177 | + self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}" | |
178 | + self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}" | |
179 | + self.version_column = options[:version_column] || 'version' | |
180 | + self.version_sequence_name = options[:sequence_name] | |
181 | + self.max_version_limit = options[:limit].to_i | |
182 | + self.version_condition = options[:if] || true | |
183 | + 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) | |
184 | + self.version_association_options = { | |
185 | + :class_name => "#{self.to_s}::#{versioned_class_name}", | |
186 | + :foreign_key => versioned_foreign_key, | |
187 | + :dependent => :delete_all | |
188 | + }.merge(options[:association_options] || {}) | |
189 | + | |
190 | + if block_given? | |
191 | + extension_module_name = "#{versioned_class_name}Extension" | |
192 | + silence_warnings do | |
193 | + self.const_set(extension_module_name, Module.new(&extension)) | |
194 | + end | |
195 | + | |
196 | + options[:extend] = self.const_get(extension_module_name) | |
197 | + end | |
198 | + | |
199 | + class_eval <<-CLASS_METHODS | |
200 | + has_many :versions, version_association_options do | |
201 | + # finds earliest version of this record | |
202 | + def earliest | |
203 | + @earliest ||= find(:first, :order => '#{version_column}') | |
204 | + end | |
205 | + | |
206 | + # find latest version of this record | |
207 | + def latest | |
208 | + @latest ||= find(:first, :order => '#{version_column} desc') | |
209 | + end | |
210 | + end | |
211 | + before_save :set_new_version | |
212 | + after_save :save_version | |
213 | + after_save :clear_old_versions | |
214 | + | |
215 | + unless options[:if_changed].nil? | |
216 | + self.track_altered_attributes = true | |
217 | + options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array) | |
218 | + self.version_if_changed = options[:if_changed].map(&:to_s) | |
219 | + end | |
220 | + | |
221 | + include options[:extend] if options[:extend].is_a?(Module) | |
222 | + CLASS_METHODS | |
223 | + | |
224 | + # create the dynamic versioned model | |
225 | + const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do | |
226 | + def self.reloadable? ; false ; end | |
227 | + # find first version before the given version | |
228 | + def self.before(version) | |
229 | + find :first, :order => 'version desc', | |
230 | + :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version] | |
231 | + end | |
232 | + | |
233 | + # find first version after the given version. | |
234 | + def self.after(version) | |
235 | + find :first, :order => 'version', | |
236 | + :conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version] | |
237 | + end | |
238 | + | |
239 | + def previous | |
240 | + self.class.before(self) | |
241 | + end | |
242 | + | |
243 | + def next | |
244 | + self.class.after(self) | |
245 | + end | |
246 | + | |
247 | + def versions_count | |
248 | + page.version | |
249 | + end | |
250 | + end | |
251 | + | |
252 | + versioned_class.cattr_accessor :original_class | |
253 | + versioned_class.original_class = self | |
254 | + versioned_class.set_table_name versioned_table_name | |
255 | + versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym, | |
256 | + :class_name => "::#{self.to_s}", | |
257 | + :foreign_key => versioned_foreign_key | |
258 | + versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module) | |
259 | + versioned_class.set_sequence_name version_sequence_name if version_sequence_name | |
260 | + end | |
261 | + end | |
262 | + | |
263 | + module ActMethods | |
264 | + def self.included(base) # :nodoc: | |
265 | + base.extend ClassMethods | |
266 | + end | |
267 | + | |
268 | + # Saves a version of the model in the versioned table. This is called in the after_save callback by default | |
269 | + def save_version | |
270 | + if @saving_version | |
271 | + @saving_version = nil | |
272 | + rev = self.class.versioned_class.new | |
273 | + clone_versioned_model(self, rev) | |
274 | + rev.send("#{self.class.version_column}=", send(self.class.version_column)) | |
275 | + rev.send("#{self.class.versioned_foreign_key}=", id) | |
276 | + rev.save | |
277 | + end | |
278 | + end | |
279 | + | |
280 | + # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>. | |
281 | + # Override this method to set your own criteria for clearing old versions. | |
282 | + def clear_old_versions | |
283 | + return if self.class.max_version_limit == 0 | |
284 | + excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit | |
285 | + if excess_baggage > 0 | |
286 | + self.class.versioned_class.delete_all ["#{self.class.version_column} <= ? and #{self.class.versioned_foreign_key} = ?", excess_baggage, id] | |
287 | + end | |
288 | + end | |
289 | + | |
290 | + # Reverts a model to a given version. Takes either a version number or an instance of the versioned model | |
291 | + def revert_to(version) | |
292 | + if version.is_a?(self.class.versioned_class) | |
293 | + return false unless version.send(self.class.versioned_foreign_key) == id and !version.new_record? | |
294 | + else | |
295 | + return false unless version = versions.send("find_by_#{self.class.version_column}", version) | |
296 | + end | |
297 | + self.clone_versioned_model(version, self) | |
298 | + send("#{self.class.version_column}=", version.send(self.class.version_column)) | |
299 | + true | |
300 | + end | |
301 | + | |
302 | + # Reverts a model to a given version and saves the model. | |
303 | + # Takes either a version number or an instance of the versioned model | |
304 | + def revert_to!(version) | |
305 | + revert_to(version) ? save_without_revision : false | |
306 | + end | |
307 | + | |
308 | + # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created. | |
309 | + def save_without_revision | |
310 | + save_without_revision! | |
311 | + true | |
312 | + rescue | |
313 | + false | |
314 | + end | |
315 | + | |
316 | + def save_without_revision! | |
317 | + without_locking do | |
318 | + without_revision do | |
319 | + save! | |
320 | + end | |
321 | + end | |
322 | + end | |
323 | + | |
324 | + def altered? | |
325 | + track_altered_attributes ? (version_if_changed - changed).length < version_if_changed.length : changed? | |
326 | + end | |
327 | + | |
328 | + # Clones a model. Used when saving a new version or reverting a model's version. | |
329 | + def clone_versioned_model(orig_model, new_model) | |
330 | + self.class.versioned_columns.each do |col| | |
331 | + new_model.send("#{col.name}=", orig_model.send(col.name)) if orig_model.has_attribute?(col.name) | |
332 | + end | |
333 | + | |
334 | + if orig_model.is_a?(self.class.versioned_class) | |
335 | + new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column] | |
336 | + elsif new_model.is_a?(self.class.versioned_class) | |
337 | + new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column] | |
338 | + end | |
339 | + end | |
340 | + | |
341 | + # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>. | |
342 | + def save_version? | |
343 | + version_condition_met? && altered? | |
344 | + end | |
345 | + | |
346 | + # Checks condition set in the :if option to check whether a revision should be created or not. Override this for | |
347 | + # custom version condition checking. | |
348 | + def version_condition_met? | |
349 | + case | |
350 | + when version_condition.is_a?(Symbol) | |
351 | + send(version_condition) | |
352 | + when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1) | |
353 | + version_condition.call(self) | |
354 | + else | |
355 | + version_condition | |
356 | + end | |
357 | + end | |
358 | + | |
359 | + # Executes the block with the versioning callbacks disabled. | |
360 | + # | |
361 | + # @foo.without_revision do | |
362 | + # @foo.save | |
363 | + # end | |
364 | + # | |
365 | + def without_revision(&block) | |
366 | + self.class.without_revision(&block) | |
367 | + end | |
368 | + | |
369 | + # Turns off optimistic locking for the duration of the block | |
370 | + # | |
371 | + # @foo.without_locking do | |
372 | + # @foo.save | |
373 | + # end | |
374 | + # | |
375 | + def without_locking(&block) | |
376 | + self.class.without_locking(&block) | |
377 | + end | |
378 | + | |
379 | + def empty_callback() end #:nodoc: | |
380 | + | |
381 | + protected | |
382 | + # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version. | |
383 | + def set_new_version | |
384 | + @saving_version = new_record? || save_version? | |
385 | + self.send("#{self.class.version_column}=", next_version) if new_record? || (!locking_enabled? && save_version?) | |
386 | + end | |
387 | + | |
388 | + # Gets the next available version for the current record, or 1 for a new record | |
389 | + def next_version | |
390 | + (new_record? ? 0 : versions.calculate(:max, version_column).to_i) + 1 | |
391 | + end | |
392 | + | |
393 | + module ClassMethods | |
394 | + # Returns an array of columns that are versioned. See non_versioned_columns | |
395 | + def versioned_columns | |
396 | + @versioned_columns ||= columns.select { |c| !non_versioned_columns.include?(c.name) } | |
397 | + end | |
398 | + | |
399 | + # Returns an instance of the dynamic versioned model | |
400 | + def versioned_class | |
401 | + const_get versioned_class_name | |
402 | + end | |
403 | + | |
404 | + # Rake migration task to create the versioned table using options passed to acts_as_versioned | |
405 | + def create_versioned_table(create_table_options = {}) | |
406 | + # create version column in main table if it does not exist | |
407 | + if !self.content_columns.find { |c| [version_column.to_s, 'lock_version'].include? c.name } | |
408 | + self.connection.add_column table_name, version_column, :integer | |
409 | + self.reset_column_information | |
410 | + end | |
411 | + | |
412 | + return if connection.table_exists?(versioned_table_name) | |
413 | + | |
414 | + self.connection.create_table(versioned_table_name, create_table_options) do |t| | |
415 | + t.column versioned_foreign_key, :integer | |
416 | + t.column version_column, :integer | |
417 | + end | |
418 | + | |
419 | + self.versioned_columns.each do |col| | |
420 | + self.connection.add_column versioned_table_name, col.name, col.type, | |
421 | + :limit => col.limit, | |
422 | + :default => col.default, | |
423 | + :scale => col.scale, | |
424 | + :precision => col.precision | |
425 | + end | |
426 | + | |
427 | + if type_col = self.columns_hash[inheritance_column] | |
428 | + self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type, | |
429 | + :limit => type_col.limit, | |
430 | + :default => type_col.default, | |
431 | + :scale => type_col.scale, | |
432 | + :precision => type_col.precision | |
433 | + end | |
434 | + | |
435 | + self.connection.add_index versioned_table_name, versioned_foreign_key | |
436 | + end | |
437 | + | |
438 | + # Rake migration task to drop the versioned table | |
439 | + def drop_versioned_table | |
440 | + self.connection.drop_table versioned_table_name | |
441 | + end | |
442 | + | |
443 | + # Executes the block with the versioning callbacks disabled. | |
444 | + # | |
445 | + # Foo.without_revision do | |
446 | + # @foo.save | |
447 | + # end | |
448 | + # | |
449 | + def without_revision(&block) | |
450 | + class_eval do | |
451 | + CALLBACKS.each do |attr_name| | |
452 | + alias_method "orig_#{attr_name}".to_sym, attr_name | |
453 | + alias_method attr_name, :empty_callback | |
454 | + end | |
455 | + end | |
456 | + block.call | |
457 | + ensure | |
458 | + class_eval do | |
459 | + CALLBACKS.each do |attr_name| | |
460 | + alias_method attr_name, "orig_#{attr_name}".to_sym | |
461 | + end | |
462 | + end | |
463 | + end | |
464 | + | |
465 | + # Turns off optimistic locking for the duration of the block | |
466 | + # | |
467 | + # Foo.without_locking do | |
468 | + # @foo.save | |
469 | + # end | |
470 | + # | |
471 | + def without_locking(&block) | |
472 | + current = ActiveRecord::Base.lock_optimistically | |
473 | + ActiveRecord::Base.lock_optimistically = false if current | |
474 | + begin | |
475 | + block.call | |
476 | + ensure | |
477 | + ActiveRecord::Base.lock_optimistically = true if current | |
478 | + end | |
479 | + end | |
480 | + end | |
481 | + end | |
482 | + end | |
483 | + end | |
484 | +end | |
485 | + | |
486 | +ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned | ... | ... |
... | ... | @@ -0,0 +1,48 @@ |
1 | +$:.unshift(File.dirname(__FILE__) + '/../../../rails/activesupport/lib') | |
2 | +$:.unshift(File.dirname(__FILE__) + '/../../../rails/activerecord/lib') | |
3 | +$:.unshift(File.dirname(__FILE__) + '/../lib') | |
4 | +require 'test/unit' | |
5 | +begin | |
6 | + require 'active_support' | |
7 | + require 'active_record' | |
8 | + require 'active_record/fixtures' | |
9 | +rescue LoadError | |
10 | + require 'rubygems' | |
11 | + retry | |
12 | +end | |
13 | + | |
14 | +begin | |
15 | + require 'ruby-debug' | |
16 | + Debugger.start | |
17 | +rescue LoadError | |
18 | +end | |
19 | + | |
20 | +require 'acts_as_versioned' | |
21 | + | |
22 | +config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) | |
23 | +ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log") | |
24 | +ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']} | |
25 | +ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) | |
26 | + | |
27 | +load(File.dirname(__FILE__) + "/schema.rb") | |
28 | + | |
29 | +# set up custom sequence on widget_versions for DBs that support sequences | |
30 | +if ENV['DB'] == 'postgresql' | |
31 | + ActiveRecord::Base.connection.execute "DROP SEQUENCE widgets_seq;" rescue nil | |
32 | + ActiveRecord::Base.connection.remove_column :widget_versions, :id | |
33 | + ActiveRecord::Base.connection.execute "CREATE SEQUENCE widgets_seq START 101;" | |
34 | + ActiveRecord::Base.connection.execute "ALTER TABLE widget_versions ADD COLUMN id INTEGER PRIMARY KEY DEFAULT nextval('widgets_seq');" | |
35 | +end | |
36 | + | |
37 | +Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/" | |
38 | +$:.unshift(Test::Unit::TestCase.fixture_path) | |
39 | + | |
40 | +class Test::Unit::TestCase #:nodoc: | |
41 | + # Turn off transactional fixtures if you're working with MyISAM tables in MySQL | |
42 | + self.use_transactional_fixtures = true | |
43 | + | |
44 | + # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david) | |
45 | + self.use_instantiated_fixtures = false | |
46 | + | |
47 | + # Add more helper methods to be used by all tests here... | |
48 | +end | |
0 | 49 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,18 @@ |
1 | +sqlite: | |
2 | + :adapter: sqlite | |
3 | + :dbfile: acts_as_versioned_plugin.sqlite.db | |
4 | +sqlite3: | |
5 | + :adapter: sqlite3 | |
6 | + :dbfile: acts_as_versioned_plugin.sqlite3.db | |
7 | +postgresql: | |
8 | + :adapter: postgresql | |
9 | + :username: postgres | |
10 | + :password: postgres | |
11 | + :database: acts_as_versioned_plugin_test | |
12 | + :min_messages: ERROR | |
13 | +mysql: | |
14 | + :adapter: mysql | |
15 | + :host: localhost | |
16 | + :username: rails | |
17 | + :password: | |
18 | + :database: acts_as_versioned_plugin_test | |
0 | 19 | \ No newline at end of file | ... | ... |
vendor/plugins/acts_as_versioned/test/fixtures/authors.yml
0 → 100644
vendor/plugins/acts_as_versioned/test/fixtures/landmark.rb
0 → 100644
vendor/plugins/acts_as_versioned/test/fixtures/landmark_versions.yml
0 → 100644
vendor/plugins/acts_as_versioned/test/fixtures/landmarks.yml
0 → 100644
vendor/plugins/acts_as_versioned/test/fixtures/locked_pages.yml
0 → 100644
vendor/plugins/acts_as_versioned/test/fixtures/locked_pages_revisions.yml
0 → 100644
... | ... | @@ -0,0 +1,27 @@ |
1 | +welcome_1: | |
2 | + id: 1 | |
3 | + page_id: 1 | |
4 | + title: Welcome to the weblg | |
5 | + lock_version: 23 | |
6 | + version_type: LockedPage | |
7 | + | |
8 | +welcome_2: | |
9 | + id: 2 | |
10 | + page_id: 1 | |
11 | + title: Welcome to the weblog | |
12 | + lock_version: 24 | |
13 | + version_type: LockedPage | |
14 | + | |
15 | +thinking_1: | |
16 | + id: 3 | |
17 | + page_id: 2 | |
18 | + title: So I was thinking!!! | |
19 | + lock_version: 23 | |
20 | + version_type: SpecialLockedPage | |
21 | + | |
22 | +thinking_2: | |
23 | + id: 4 | |
24 | + page_id: 2 | |
25 | + title: So I was thinking | |
26 | + lock_version: 24 | |
27 | + version_type: SpecialLockedPage | ... | ... |
vendor/plugins/acts_as_versioned/test/fixtures/migrations/1_add_versioned_tables.rb
0 → 100644
... | ... | @@ -0,0 +1,15 @@ |
1 | +class AddVersionedTables < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + create_table("things") do |t| | |
4 | + t.column :title, :text | |
5 | + t.column :price, :decimal, :precision => 7, :scale => 2 | |
6 | + t.column :type, :string | |
7 | + end | |
8 | + Thing.create_versioned_table | |
9 | + end | |
10 | + | |
11 | + def self.down | |
12 | + Thing.drop_versioned_table | |
13 | + drop_table "things" rescue nil | |
14 | + end | |
15 | +end | |
0 | 16 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,43 @@ |
1 | +class Page < ActiveRecord::Base | |
2 | + belongs_to :author | |
3 | + has_many :authors, :through => :versions, :order => 'name' | |
4 | + belongs_to :revisor, :class_name => 'Author' | |
5 | + has_many :revisors, :class_name => 'Author', :through => :versions, :order => 'name' | |
6 | + acts_as_versioned :if => :feeling_good? do | |
7 | + def self.included(base) | |
8 | + base.cattr_accessor :feeling_good | |
9 | + base.feeling_good = true | |
10 | + base.belongs_to :author | |
11 | + base.belongs_to :revisor, :class_name => 'Author' | |
12 | + end | |
13 | + | |
14 | + def feeling_good? | |
15 | + @@feeling_good == true | |
16 | + end | |
17 | + end | |
18 | +end | |
19 | + | |
20 | +module LockedPageExtension | |
21 | + def hello_world | |
22 | + 'hello_world' | |
23 | + end | |
24 | +end | |
25 | + | |
26 | +class LockedPage < ActiveRecord::Base | |
27 | + acts_as_versioned \ | |
28 | + :inheritance_column => :version_type, | |
29 | + :foreign_key => :page_id, | |
30 | + :table_name => :locked_pages_revisions, | |
31 | + :class_name => 'LockedPageRevision', | |
32 | + :version_column => :lock_version, | |
33 | + :limit => 2, | |
34 | + :if_changed => :title, | |
35 | + :extend => LockedPageExtension | |
36 | +end | |
37 | + | |
38 | +class SpecialLockedPage < LockedPage | |
39 | +end | |
40 | + | |
41 | +class Author < ActiveRecord::Base | |
42 | + has_many :pages | |
43 | +end | |
0 | 44 | \ No newline at end of file | ... | ... |
vendor/plugins/acts_as_versioned/test/fixtures/page_versions.yml
0 → 100644
... | ... | @@ -0,0 +1,16 @@ |
1 | +welcome_2: | |
2 | + id: 1 | |
3 | + page_id: 1 | |
4 | + title: Welcome to the weblog | |
5 | + body: Such a lovely day | |
6 | + version: 24 | |
7 | + author_id: 1 | |
8 | + revisor_id: 1 | |
9 | +welcome_1: | |
10 | + id: 2 | |
11 | + page_id: 1 | |
12 | + title: Welcome to the weblg | |
13 | + body: Such a lovely day | |
14 | + version: 23 | |
15 | + author_id: 2 | |
16 | + revisor_id: 2 | ... | ... |
vendor/plugins/acts_as_versioned/test/fixtures/pages.yml
0 → 100644
vendor/plugins/acts_as_versioned/test/fixtures/widget.rb
0 → 100644
... | ... | @@ -0,0 +1,46 @@ |
1 | +require File.join(File.dirname(__FILE__), 'abstract_unit') | |
2 | + | |
3 | +if ActiveRecord::Base.connection.supports_migrations? | |
4 | + class Thing < ActiveRecord::Base | |
5 | + attr_accessor :version | |
6 | + acts_as_versioned | |
7 | + end | |
8 | + | |
9 | + class MigrationTest < Test::Unit::TestCase | |
10 | + self.use_transactional_fixtures = false | |
11 | + def teardown | |
12 | + if ActiveRecord::Base.connection.respond_to?(:initialize_schema_information) | |
13 | + ActiveRecord::Base.connection.initialize_schema_information | |
14 | + ActiveRecord::Base.connection.update "UPDATE schema_info SET version = 0" | |
15 | + else | |
16 | + ActiveRecord::Base.connection.initialize_schema_migrations_table | |
17 | + ActiveRecord::Base.connection.assume_migrated_upto_version(0) | |
18 | + end | |
19 | + | |
20 | + Thing.connection.drop_table "things" rescue nil | |
21 | + Thing.connection.drop_table "thing_versions" rescue nil | |
22 | + Thing.reset_column_information | |
23 | + end | |
24 | + | |
25 | + def test_versioned_migration | |
26 | + assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' } | |
27 | + # take 'er up | |
28 | + ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/') | |
29 | + t = Thing.create :title => 'blah blah', :price => 123.45, :type => 'Thing' | |
30 | + assert_equal 1, t.versions.size | |
31 | + | |
32 | + # check that the price column has remembered its value correctly | |
33 | + assert_equal t.price, t.versions.first.price | |
34 | + assert_equal t.title, t.versions.first.title | |
35 | + assert_equal t[:type], t.versions.first[:type] | |
36 | + | |
37 | + # make sure that the precision of the price column has been preserved | |
38 | + assert_equal 7, Thing::Version.columns.find{|c| c.name == "price"}.precision | |
39 | + assert_equal 2, Thing::Version.columns.find{|c| c.name == "price"}.scale | |
40 | + | |
41 | + # now lets take 'er back down | |
42 | + ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/') | |
43 | + assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' } | |
44 | + end | |
45 | + end | |
46 | +end | ... | ... |
... | ... | @@ -0,0 +1,82 @@ |
1 | +ActiveRecord::Schema.define(:version => 0) do | |
2 | + create_table :pages, :force => true do |t| | |
3 | + t.column :version, :integer | |
4 | + t.column :title, :string, :limit => 255 | |
5 | + t.column :body, :text | |
6 | + t.column :created_on, :datetime | |
7 | + t.column :updated_on, :datetime | |
8 | + t.column :author_id, :integer | |
9 | + t.column :revisor_id, :integer | |
10 | + end | |
11 | + | |
12 | + create_table :page_versions, :force => true do |t| | |
13 | + t.column :page_id, :integer | |
14 | + t.column :version, :integer | |
15 | + t.column :title, :string, :limit => 255 | |
16 | + t.column :body, :text | |
17 | + t.column :created_on, :datetime | |
18 | + t.column :updated_on, :datetime | |
19 | + t.column :author_id, :integer | |
20 | + t.column :revisor_id, :integer | |
21 | + end | |
22 | + | |
23 | + add_index :page_versions, [:page_id, :version], :unique => true | |
24 | + | |
25 | + create_table :authors, :force => true do |t| | |
26 | + t.column :page_id, :integer | |
27 | + t.column :name, :string | |
28 | + end | |
29 | + | |
30 | + create_table :locked_pages, :force => true do |t| | |
31 | + t.column :lock_version, :integer | |
32 | + t.column :title, :string, :limit => 255 | |
33 | + t.column :body, :text | |
34 | + t.column :type, :string, :limit => 255 | |
35 | + end | |
36 | + | |
37 | + create_table :locked_pages_revisions, :force => true do |t| | |
38 | + t.column :page_id, :integer | |
39 | + t.column :lock_version, :integer | |
40 | + t.column :title, :string, :limit => 255 | |
41 | + t.column :body, :text | |
42 | + t.column :version_type, :string, :limit => 255 | |
43 | + t.column :updated_at, :datetime | |
44 | + end | |
45 | + | |
46 | + add_index :locked_pages_revisions, [:page_id, :lock_version], :unique => true | |
47 | + | |
48 | + create_table :widgets, :force => true do |t| | |
49 | + t.column :name, :string, :limit => 50 | |
50 | + t.column :foo, :string | |
51 | + t.column :version, :integer | |
52 | + t.column :updated_at, :datetime | |
53 | + end | |
54 | + | |
55 | + create_table :widget_versions, :force => true do |t| | |
56 | + t.column :widget_id, :integer | |
57 | + t.column :name, :string, :limit => 50 | |
58 | + t.column :version, :integer | |
59 | + t.column :updated_at, :datetime | |
60 | + end | |
61 | + | |
62 | + add_index :widget_versions, [:widget_id, :version], :unique => true | |
63 | + | |
64 | + create_table :landmarks, :force => true do |t| | |
65 | + t.column :name, :string | |
66 | + t.column :latitude, :float | |
67 | + t.column :longitude, :float | |
68 | + t.column :doesnt_trigger_version,:string | |
69 | + t.column :version, :integer | |
70 | + end | |
71 | + | |
72 | + create_table :landmark_versions, :force => true do |t| | |
73 | + t.column :landmark_id, :integer | |
74 | + t.column :name, :string | |
75 | + t.column :latitude, :float | |
76 | + t.column :longitude, :float | |
77 | + t.column :doesnt_trigger_version,:string | |
78 | + t.column :version, :integer | |
79 | + end | |
80 | + | |
81 | + add_index :landmark_versions, [:landmark_id, :version], :unique => true | |
82 | +end | ... | ... |
... | ... | @@ -0,0 +1,370 @@ |
1 | +require File.join(File.dirname(__FILE__), 'abstract_unit') | |
2 | +require File.join(File.dirname(__FILE__), 'fixtures/page') | |
3 | +require File.join(File.dirname(__FILE__), 'fixtures/widget') | |
4 | + | |
5 | +class VersionedTest < Test::Unit::TestCase | |
6 | + fixtures :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions | |
7 | + set_fixture_class :page_versions => Page::Version | |
8 | + | |
9 | + def test_saves_versioned_copy | |
10 | + p = Page.create! :title => 'first title', :body => 'first body' | |
11 | + assert !p.new_record? | |
12 | + assert_equal 1, p.versions.size | |
13 | + assert_equal 1, p.version | |
14 | + assert_instance_of Page.versioned_class, p.versions.first | |
15 | + end | |
16 | + | |
17 | + def test_saves_without_revision | |
18 | + p = pages(:welcome) | |
19 | + old_versions = p.versions.count | |
20 | + | |
21 | + p.save_without_revision | |
22 | + | |
23 | + p.without_revision do | |
24 | + p.update_attributes :title => 'changed' | |
25 | + end | |
26 | + | |
27 | + assert_equal old_versions, p.versions.count | |
28 | + end | |
29 | + | |
30 | + def test_rollback_with_version_number | |
31 | + p = pages(:welcome) | |
32 | + assert_equal 24, p.version | |
33 | + assert_equal 'Welcome to the weblog', p.title | |
34 | + | |
35 | + assert p.revert_to!(23), "Couldn't revert to 23" | |
36 | + assert_equal 23, p.version | |
37 | + assert_equal 'Welcome to the weblg', p.title | |
38 | + end | |
39 | + | |
40 | + def test_versioned_class_name | |
41 | + assert_equal 'Version', Page.versioned_class_name | |
42 | + assert_equal 'LockedPageRevision', LockedPage.versioned_class_name | |
43 | + end | |
44 | + | |
45 | + def test_versioned_class | |
46 | + assert_equal Page::Version, Page.versioned_class | |
47 | + assert_equal LockedPage::LockedPageRevision, LockedPage.versioned_class | |
48 | + end | |
49 | + | |
50 | + def test_special_methods | |
51 | + assert_nothing_raised { pages(:welcome).feeling_good? } | |
52 | + assert_nothing_raised { pages(:welcome).versions.first.feeling_good? } | |
53 | + assert_nothing_raised { locked_pages(:welcome).hello_world } | |
54 | + assert_nothing_raised { locked_pages(:welcome).versions.first.hello_world } | |
55 | + end | |
56 | + | |
57 | + def test_rollback_with_version_class | |
58 | + p = pages(:welcome) | |
59 | + assert_equal 24, p.version | |
60 | + assert_equal 'Welcome to the weblog', p.title | |
61 | + | |
62 | + assert p.revert_to!(p.versions.find_by_version(23)), "Couldn't revert to 23" | |
63 | + assert_equal 23, p.version | |
64 | + assert_equal 'Welcome to the weblg', p.title | |
65 | + end | |
66 | + | |
67 | + def test_rollback_fails_with_invalid_revision | |
68 | + p = locked_pages(:welcome) | |
69 | + assert !p.revert_to!(locked_pages(:thinking)) | |
70 | + end | |
71 | + | |
72 | + def test_saves_versioned_copy_with_options | |
73 | + p = LockedPage.create! :title => 'first title' | |
74 | + assert !p.new_record? | |
75 | + assert_equal 1, p.versions.size | |
76 | + assert_instance_of LockedPage.versioned_class, p.versions.first | |
77 | + end | |
78 | + | |
79 | + def test_rollback_with_version_number_with_options | |
80 | + p = locked_pages(:welcome) | |
81 | + assert_equal 'Welcome to the weblog', p.title | |
82 | + assert_equal 'LockedPage', p.versions.first.version_type | |
83 | + | |
84 | + assert p.revert_to!(p.versions.first.lock_version), "Couldn't revert to 23" | |
85 | + assert_equal 'Welcome to the weblg', p.title | |
86 | + assert_equal 'LockedPage', p.versions.first.version_type | |
87 | + end | |
88 | + | |
89 | + def test_rollback_with_version_class_with_options | |
90 | + p = locked_pages(:welcome) | |
91 | + assert_equal 'Welcome to the weblog', p.title | |
92 | + assert_equal 'LockedPage', p.versions.first.version_type | |
93 | + | |
94 | + assert p.revert_to!(p.versions.first), "Couldn't revert to 1" | |
95 | + assert_equal 'Welcome to the weblg', p.title | |
96 | + assert_equal 'LockedPage', p.versions.first.version_type | |
97 | + end | |
98 | + | |
99 | + def test_saves_versioned_copy_with_sti | |
100 | + p = SpecialLockedPage.create! :title => 'first title' | |
101 | + assert !p.new_record? | |
102 | + assert_equal 1, p.versions.size | |
103 | + assert_instance_of LockedPage.versioned_class, p.versions.first | |
104 | + assert_equal 'SpecialLockedPage', p.versions.first.version_type | |
105 | + end | |
106 | + | |
107 | + def test_rollback_with_version_number_with_sti | |
108 | + p = locked_pages(:thinking) | |
109 | + assert_equal 'So I was thinking', p.title | |
110 | + | |
111 | + assert p.revert_to!(p.versions.first.lock_version), "Couldn't revert to 1" | |
112 | + assert_equal 'So I was thinking!!!', p.title | |
113 | + assert_equal 'SpecialLockedPage', p.versions.first.version_type | |
114 | + end | |
115 | + | |
116 | + def test_lock_version_works_with_versioning | |
117 | + p = locked_pages(:thinking) | |
118 | + p2 = LockedPage.find(p.id) | |
119 | + | |
120 | + p.title = 'fresh title' | |
121 | + p.save | |
122 | + assert_equal 2, p.versions.size # limit! | |
123 | + | |
124 | + assert_raises(ActiveRecord::StaleObjectError) do | |
125 | + p2.title = 'stale title' | |
126 | + p2.save | |
127 | + end | |
128 | + end | |
129 | + | |
130 | + def test_version_if_condition | |
131 | + p = Page.create! :title => "title" | |
132 | + assert_equal 1, p.version | |
133 | + | |
134 | + Page.feeling_good = false | |
135 | + p.save | |
136 | + assert_equal 1, p.version | |
137 | + Page.feeling_good = true | |
138 | + end | |
139 | + | |
140 | + def test_version_if_condition2 | |
141 | + # set new if condition | |
142 | + Page.class_eval do | |
143 | + def new_feeling_good() title[0..0] == 'a'; end | |
144 | + alias_method :old_feeling_good, :feeling_good? | |
145 | + alias_method :feeling_good?, :new_feeling_good | |
146 | + end | |
147 | + | |
148 | + p = Page.create! :title => "title" | |
149 | + assert_equal 1, p.version # version does not increment | |
150 | + assert_equal 1, p.versions.count | |
151 | + | |
152 | + p.update_attributes(:title => 'new title') | |
153 | + assert_equal 1, p.version # version does not increment | |
154 | + assert_equal 1, p.versions.count | |
155 | + | |
156 | + p.update_attributes(:title => 'a title') | |
157 | + assert_equal 2, p.version | |
158 | + assert_equal 2, p.versions.count | |
159 | + | |
160 | + # reset original if condition | |
161 | + Page.class_eval { alias_method :feeling_good?, :old_feeling_good } | |
162 | + end | |
163 | + | |
164 | + def test_version_if_condition_with_block | |
165 | + # set new if condition | |
166 | + old_condition = Page.version_condition | |
167 | + Page.version_condition = Proc.new { |page| page.title[0..0] == 'b' } | |
168 | + | |
169 | + p = Page.create! :title => "title" | |
170 | + assert_equal 1, p.version # version does not increment | |
171 | + assert_equal 1, p.versions.count | |
172 | + | |
173 | + p.update_attributes(:title => 'a title') | |
174 | + assert_equal 1, p.version # version does not increment | |
175 | + assert_equal 1, p.versions.count | |
176 | + | |
177 | + p.update_attributes(:title => 'b title') | |
178 | + assert_equal 2, p.version | |
179 | + assert_equal 2, p.versions.count | |
180 | + | |
181 | + # reset original if condition | |
182 | + Page.version_condition = old_condition | |
183 | + end | |
184 | + | |
185 | + def test_version_no_limit | |
186 | + p = Page.create! :title => "title", :body => 'first body' | |
187 | + p.save | |
188 | + p.save | |
189 | + 5.times do |i| | |
190 | + p.title = "title#{i}" | |
191 | + p.save | |
192 | + assert_equal "title#{i}", p.title | |
193 | + assert_equal (i+2), p.version | |
194 | + end | |
195 | + end | |
196 | + | |
197 | + def test_version_max_limit | |
198 | + p = LockedPage.create! :title => "title" | |
199 | + p.update_attributes(:title => "title1") | |
200 | + p.update_attributes(:title => "title2") | |
201 | + 5.times do |i| | |
202 | + p.title = "title#{i}" | |
203 | + p.save | |
204 | + assert_equal "title#{i}", p.title | |
205 | + assert_equal (i+4), p.lock_version | |
206 | + assert p.versions(true).size <= 2, "locked version can only store 2 versions" | |
207 | + end | |
208 | + end | |
209 | + | |
210 | + def test_track_altered_attributes_default_value | |
211 | + assert !Page.track_altered_attributes | |
212 | + assert LockedPage.track_altered_attributes | |
213 | + assert SpecialLockedPage.track_altered_attributes | |
214 | + end | |
215 | + | |
216 | + def test_track_altered_attributes | |
217 | + p = LockedPage.create! :title => "title" | |
218 | + assert_equal 1, p.lock_version | |
219 | + assert_equal 1, p.versions(true).size | |
220 | + | |
221 | + p.body = 'whoa' | |
222 | + assert !p.save_version? | |
223 | + p.save | |
224 | + assert_equal 2, p.lock_version # still increments version because of optimistic locking | |
225 | + assert_equal 1, p.versions(true).size | |
226 | + | |
227 | + p.title = 'updated title' | |
228 | + assert p.save_version? | |
229 | + p.save | |
230 | + assert_equal 3, p.lock_version | |
231 | + assert_equal 1, p.versions(true).size # version 1 deleted | |
232 | + | |
233 | + p.title = 'updated title!' | |
234 | + assert p.save_version? | |
235 | + p.save | |
236 | + assert_equal 4, p.lock_version | |
237 | + assert_equal 2, p.versions(true).size # version 1 deleted | |
238 | + end | |
239 | + | |
240 | + def test_find_versions | |
241 | + assert_equal 1, locked_pages(:welcome).versions.find(:all, :conditions => ['title LIKE ?', '%weblog%']).size | |
242 | + end | |
243 | + | |
244 | + def test_find_version | |
245 | + assert_equal page_versions(:welcome_1), pages(:welcome).versions.find_by_version(23) | |
246 | + end | |
247 | + | |
248 | + def test_with_sequence | |
249 | + assert_equal 'widgets_seq', Widget.versioned_class.sequence_name | |
250 | + 3.times { Widget.create! :name => 'new widget' } | |
251 | + assert_equal 3, Widget.count | |
252 | + assert_equal 3, Widget.versioned_class.count | |
253 | + end | |
254 | + | |
255 | + def test_has_many_through | |
256 | + assert_equal [authors(:caged), authors(:mly)], pages(:welcome).authors | |
257 | + end | |
258 | + | |
259 | + def test_has_many_through_with_custom_association | |
260 | + assert_equal [authors(:caged), authors(:mly)], pages(:welcome).revisors | |
261 | + end | |
262 | + | |
263 | + def test_referential_integrity | |
264 | + pages(:welcome).destroy | |
265 | + assert_equal 0, Page.count | |
266 | + assert_equal 0, Page::Version.count | |
267 | + end | |
268 | + | |
269 | + def test_association_options | |
270 | + association = Page.reflect_on_association(:versions) | |
271 | + options = association.options | |
272 | + assert_equal :delete_all, options[:dependent] | |
273 | + | |
274 | + association = Widget.reflect_on_association(:versions) | |
275 | + options = association.options | |
276 | + assert_equal :nullify, options[:dependent] | |
277 | + assert_equal 'version desc', options[:order] | |
278 | + assert_equal 'widget_id', options[:foreign_key] | |
279 | + | |
280 | + widget = Widget.create! :name => 'new widget' | |
281 | + assert_equal 1, Widget.count | |
282 | + assert_equal 1, Widget.versioned_class.count | |
283 | + widget.destroy | |
284 | + assert_equal 0, Widget.count | |
285 | + assert_equal 1, Widget.versioned_class.count | |
286 | + end | |
287 | + | |
288 | + def test_versioned_records_should_belong_to_parent | |
289 | + page = pages(:welcome) | |
290 | + page_version = page.versions.last | |
291 | + assert_equal page, page_version.page | |
292 | + end | |
293 | + | |
294 | + def test_unaltered_attributes | |
295 | + landmarks(:washington).attributes = landmarks(:washington).attributes.except("id") | |
296 | + assert !landmarks(:washington).changed? | |
297 | + end | |
298 | + | |
299 | + def test_unchanged_string_attributes | |
300 | + landmarks(:washington).attributes = landmarks(:washington).attributes.except("id").inject({}) { |params, (key, value)| params.update(key => value.to_s) } | |
301 | + assert !landmarks(:washington).changed? | |
302 | + end | |
303 | + | |
304 | + def test_should_find_earliest_version | |
305 | + assert_equal page_versions(:welcome_1), pages(:welcome).versions.earliest | |
306 | + end | |
307 | + | |
308 | + def test_should_find_latest_version | |
309 | + assert_equal page_versions(:welcome_2), pages(:welcome).versions.latest | |
310 | + end | |
311 | + | |
312 | + def test_should_find_previous_version | |
313 | + assert_equal page_versions(:welcome_1), page_versions(:welcome_2).previous | |
314 | + assert_equal page_versions(:welcome_1), pages(:welcome).versions.before(page_versions(:welcome_2)) | |
315 | + end | |
316 | + | |
317 | + def test_should_find_next_version | |
318 | + assert_equal page_versions(:welcome_2), page_versions(:welcome_1).next | |
319 | + assert_equal page_versions(:welcome_2), pages(:welcome).versions.after(page_versions(:welcome_1)) | |
320 | + end | |
321 | + | |
322 | + def test_should_find_version_count | |
323 | + assert_equal 2, pages(:welcome).versions.size | |
324 | + end | |
325 | + | |
326 | + def test_if_changed_creates_version_if_a_listed_column_is_changed | |
327 | + landmarks(:washington).name = "Washington" | |
328 | + assert landmarks(:washington).changed? | |
329 | + assert landmarks(:washington).altered? | |
330 | + end | |
331 | + | |
332 | + def test_if_changed_creates_version_if_all_listed_columns_are_changed | |
333 | + landmarks(:washington).name = "Washington" | |
334 | + landmarks(:washington).latitude = 1.0 | |
335 | + landmarks(:washington).longitude = 1.0 | |
336 | + assert landmarks(:washington).changed? | |
337 | + assert landmarks(:washington).altered? | |
338 | + end | |
339 | + | |
340 | + def test_if_changed_does_not_create_new_version_if_unlisted_column_is_changed | |
341 | + landmarks(:washington).doesnt_trigger_version = "This should not trigger version" | |
342 | + assert landmarks(:washington).changed? | |
343 | + assert !landmarks(:washington).altered? | |
344 | + end | |
345 | + | |
346 | + def test_without_locking_temporarily_disables_optimistic_locking | |
347 | + enabled1 = false | |
348 | + block_called = false | |
349 | + | |
350 | + ActiveRecord::Base.lock_optimistically = true | |
351 | + LockedPage.without_locking do | |
352 | + enabled1 = ActiveRecord::Base.lock_optimistically | |
353 | + block_called = true | |
354 | + end | |
355 | + enabled2 = ActiveRecord::Base.lock_optimistically | |
356 | + | |
357 | + assert block_called | |
358 | + assert !enabled1 | |
359 | + assert enabled2 | |
360 | + end | |
361 | + | |
362 | + def test_without_locking_reverts_optimistic_locking_settings_if_block_raises_exception | |
363 | + assert_raises(RuntimeError) do | |
364 | + LockedPage.without_locking do | |
365 | + raise RuntimeError, "oh noes" | |
366 | + end | |
367 | + end | |
368 | + assert ActiveRecord::Base.lock_optimistically | |
369 | + end | |
370 | +end | |
0 | 371 | \ No newline at end of file | ... | ... |