Commit be0b67607147eb670d86c8dbffb3f3575a3c9f7a
Exists in
master
and in
1 other branch
Merge branch 'question-editing'
Showing
29 changed files
with
1638 additions
and
2 deletions
Show diff stats
app/models/question.rb
1 | class Question < ActiveRecord::Base | 1 | class Question < ActiveRecord::Base |
2 | + acts_as_versioned | ||
3 | + | ||
2 | require 'set' | 4 | require 'set' |
3 | include Utility | 5 | include Utility |
4 | extend ActiveSupport::Memoizable | 6 | extend ActiveSupport::Memoizable |
5 | - | 7 | + |
6 | belongs_to :creator, :class_name => "Visitor", :foreign_key => "creator_id" | 8 | belongs_to :creator, :class_name => "Visitor", :foreign_key => "creator_id" |
7 | belongs_to :site, :class_name => "User", :foreign_key => "site_id" | 9 | belongs_to :site, :class_name => "User", :foreign_key => "site_id" |
8 | 10 |
@@ -0,0 +1,20 @@ | @@ -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,7 +9,7 @@ | ||
9 | # | 9 | # |
10 | # It's strongly recommended to check this file into your version control system. | 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 | create_table "appearances", :force => true do |t| | 14 | create_table "appearances", :force => true do |t| |
15 | t.integer "voter_id" | 15 | t.integer "voter_id" |
@@ -142,6 +142,29 @@ ActiveRecord::Schema.define(:version => 20110131154228) do | @@ -142,6 +142,29 @@ ActiveRecord::Schema.define(:version => 20110131154228) do | ||
142 | add_index "prompts", ["question_id"], :name => "index_prompts_on_question_id" | 142 | add_index "prompts", ["question_id"], :name => "index_prompts_on_question_id" |
143 | add_index "prompts", ["right_choice_id"], :name => "index_prompts_on_right_choice_id" | 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 | create_table "questions", :force => true do |t| | 168 | create_table "questions", :force => true do |t| |
146 | t.integer "creator_id" | 169 | t.integer "creator_id" |
147 | t.string "name", :default => "" | 170 | t.string "name", :default => "" |
@@ -159,6 +182,7 @@ ActiveRecord::Schema.define(:version => 20110131154228) do | @@ -159,6 +182,7 @@ ActiveRecord::Schema.define(:version => 20110131154228) do | ||
159 | t.integer "inactive_choices_count", :default => 0 | 182 | t.integer "inactive_choices_count", :default => 0 |
160 | t.boolean "uses_catchup", :default => true | 183 | t.boolean "uses_catchup", :default => true |
161 | t.boolean "show_results", :default => true | 184 | t.boolean "show_results", :default => true |
185 | + t.integer "version" | ||
162 | end | 186 | end |
163 | 187 | ||
164 | create_table "skips", :force => true do |t| | 188 | create_table "skips", :force => true do |t| |
spec/models/question_spec.rb
@@ -31,6 +31,13 @@ describe Question do | @@ -31,6 +31,13 @@ describe Question do | ||
31 | aoiquestion.median_votes_per_session.should == 2 | 31 | aoiquestion.median_votes_per_session.should == 2 |
32 | end | 32 | end |
33 | 33 | ||
34 | + it "should create a new revision if modified" do | ||
35 | + oldVer = @question.version | ||
36 | + @question.name = "some new name" | ||
37 | + @question.save | ||
38 | + @question.version.should == oldVer + 1 | ||
39 | + end | ||
40 | + | ||
34 | it "should create a new instance given valid attributes" do | 41 | it "should create a new instance given valid attributes" do |
35 | # Factory.attributes_for does not return associations, this is a good enough substitute | 42 | # Factory.attributes_for does not return associations, this is a good enough substitute |
36 | Question.create!(Factory.build(:question).attributes.symbolize_keys) | 43 | Question.create!(Factory.build(:question).attributes.symbolize_keys) |
@@ -0,0 +1,82 @@ | @@ -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 | \ No newline at end of file | 83 | \ No newline at end of file |
@@ -0,0 +1,20 @@ | @@ -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 | \ No newline at end of file | 21 | \ No newline at end of file |
@@ -0,0 +1,28 @@ | @@ -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 | \ No newline at end of file | 29 | \ No newline at end of file |
@@ -0,0 +1,41 @@ | @@ -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 @@ | @@ -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 | \ No newline at end of file | 181 | \ No newline at end of file |
vendor/plugins/acts_as_versioned/acts_as_versioned.gemspec
0 → 100644
@@ -0,0 +1,29 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 | \ No newline at end of file | 49 | \ No newline at end of file |
@@ -0,0 +1,18 @@ | @@ -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 | \ No newline at end of file | 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 @@ | @@ -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 @@ | @@ -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 | \ No newline at end of file | 16 | \ No newline at end of file |
@@ -0,0 +1,43 @@ | @@ -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 | \ No newline at end of file | 44 | \ No newline at end of file |
vendor/plugins/acts_as_versioned/test/fixtures/page_versions.yml
0 → 100644
@@ -0,0 +1,16 @@ | @@ -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,6 @@ | @@ -0,0 +1,6 @@ | ||
1 | +class Widget < ActiveRecord::Base | ||
2 | + acts_as_versioned :sequence_name => 'widgets_seq', :association_options => { | ||
3 | + :dependent => :nullify, :order => 'version desc' | ||
4 | + } | ||
5 | + non_versioned_columns << 'foo' | ||
6 | +end | ||
0 | \ No newline at end of file | 7 | \ No newline at end of file |
@@ -0,0 +1,46 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 | \ No newline at end of file | 371 | \ No newline at end of file |