acts_as_archive 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2009 Winton Welsh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,109 @@
1
+ ActsAsArchive
2
+ =============
3
+
4
+ Don't delete your records, move them to a different table.
5
+
6
+ Like <code>acts\_as\_paranoid</code>, but doesn't mess with your SQL queries.
7
+
8
+ Install
9
+ -------
10
+
11
+ script/plugin:
12
+
13
+ <pre>
14
+ script/plugin install git://github.com/winton/acts_as_archive.git
15
+ </pre>
16
+
17
+ rubygems:
18
+
19
+ <pre>
20
+ # terminal
21
+ sudo gem install winton-acts_as_archive
22
+
23
+ # environment.rb
24
+ config.gem "winton-acts_as_archive", :lib => "acts_as_archive", :source => "http://gems.github.com"
25
+ </pre>
26
+
27
+ Update models
28
+ -------------
29
+
30
+ Add <code>acts\_as\_archive</code> to your models:
31
+
32
+ <pre>
33
+ class Article < ActiveRecord::Base
34
+ acts_as_archive
35
+ end
36
+ </pre>
37
+
38
+ <a name="run_acts_as_archive"></a>
39
+
40
+ Run acts\_as\_archive
41
+ ---------------------
42
+
43
+ Terminal:
44
+
45
+ <pre>
46
+ cd your_rails_app
47
+ acts_as_archive
48
+ </pre>
49
+
50
+ This command creates your archive tables (<code>archived_articles</code> as per the example).
51
+
52
+ Archive tables mirror your table's structure, but with an additional <code>deleted_at</code> column.
53
+
54
+ Run this command every time you add <code>acts\_as\_archive</code> to a new model.
55
+
56
+ That's it!
57
+ ----------
58
+
59
+ Use <code>destroy</code>, <code>delete</code>, and <code>delete_all</code> like you normally would.
60
+
61
+ Records move into the archive table instead of being destroyed.
62
+
63
+ What if my schema changes?
64
+ --------------------------
65
+
66
+ Any new migrations on your table are automatically applied to the archive table.
67
+
68
+ Query the archive
69
+ -----------------
70
+
71
+ Add <code>::Archive</code> to your ActiveRecord class:
72
+
73
+ <pre>
74
+ Article::Archive.find(:first)
75
+ </pre>
76
+
77
+ Restore from the archive
78
+ ------------------------
79
+
80
+ Use <code>restore\_all</code> to copy archived records back to your table:
81
+
82
+ <pre>
83
+ Article.restore_all([ 'id = ?', 1 ])
84
+ </pre>
85
+
86
+ Auto-migrate from acts\_as\_paranoid
87
+ ------------------------------------
88
+
89
+ If you previously used <code>acts\_as\_paranoid</code>, running the <code>acts\_as\_archive</code>
90
+ command will automatically move your deleted records to the archive table
91
+ (see <a href="#run_acts_as_archive">_Run acts\_as\_archive_</a>).
92
+
93
+ Original <code>deleted_at</code> values are preserved.
94
+
95
+ Add indexes to the archive table
96
+ --------------------------------
97
+
98
+ To keep insertions fast, there are no indexes on your archive table by default.
99
+
100
+ If you are querying your archive a lot, you will want to add indexes:
101
+
102
+ <pre>
103
+ class Article < ActiveRecord::Base
104
+ acts_as_archive :indexes => [ :id, :created_at, :deleted_at ]
105
+ end
106
+ </pre>
107
+
108
+ Run the <code>acts\_as\_archive</code> command any time you add new indexes
109
+ (see <a href="#run_acts_as_archive">_Run acts\_as\_archive_</a>).
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/gempackagetask'
4
+ require 'spec/rake/spectask'
5
+
6
+ GEM_NAME = 'acts_as_archive'
7
+ PKG_FILES = FileList['**/*'] - FileList['coverage', 'coverage/**/*', 'pkg', 'pkg/**/*', 'spec/db/log/*.log']
8
+
9
+ spec = Gem::Specification.new do |s|
10
+ s.author = "Winton Welsh"
11
+ s.email = "mail@wintoni.us"
12
+ s.executables << GEM_NAME
13
+ s.extra_rdoc_files = [ "README.markdown" ]
14
+ s.files = PKG_FILES.to_a
15
+ s.homepage = "http://github.com/winton/#{GEM_NAME}"
16
+ s.name = GEM_NAME
17
+ s.platform = Gem::Platform::RUBY
18
+ s.require_path = "lib"
19
+ s.summary = "Don't delete your records, move them to a different table"
20
+ s.version = "0.1.4"
21
+ end
22
+
23
+ desc "Package gem"
24
+ Rake::GemPackageTask.new(spec) do |pkg|
25
+ pkg.gem_spec = spec
26
+ end
27
+
28
+ desc "Install gem"
29
+ task :install do
30
+ Rake::Task['gem'].invoke
31
+ `sudo gem uninstall #{GEM_NAME} -x`
32
+ `sudo gem install pkg/#{GEM_NAME}*.gem`
33
+ `rm -Rf pkg`
34
+ end
35
+
36
+ desc "Generate gemspec"
37
+ task :gemspec do
38
+ File.open("#{File.dirname(__FILE__)}/#{GEM_NAME}.gemspec", 'w') do |f|
39
+ f.write(spec.to_ruby)
40
+ end
41
+ end
42
+
43
+ desc "Run specs"
44
+ Spec::Rake::SpecTask.new do |t|
45
+ t.rcov = true
46
+ t.spec_opts = ["--format", "specdoc", "--colour"]
47
+ t.spec_files = FileList["spec/**/*_spec.rb"]
48
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{acts_as_archive}
5
+ s.version = "0.1.4"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Winton Welsh"]
9
+ s.date = %q{2009-10-14}
10
+ s.default_executable = %q{acts_as_archive}
11
+ s.email = %q{mail@wintoni.us}
12
+ s.executables = ["acts_as_archive"]
13
+ s.extra_rdoc_files = ["README.markdown"]
14
+ s.files = ["acts_as_archive.gemspec", "bin", "bin/acts_as_archive", "init.rb", "lib", "lib/acts_as_archive", "lib/acts_as_archive/base", "lib/acts_as_archive/base/destroy.rb", "lib/acts_as_archive/base/restore.rb", "lib/acts_as_archive/base/table.rb", "lib/acts_as_archive/base.rb", "lib/acts_as_archive/migration.rb", "lib/acts_as_archive.rb", "MIT-LICENSE", "rails", "rails/init.rb", "Rakefile", "README.markdown", "spec", "spec/acts_as_archive", "spec/acts_as_archive/base", "spec/acts_as_archive/base/destroy_spec.rb", "spec/acts_as_archive/base/restore_spec.rb", "spec/acts_as_archive/base/table_spec.rb", "spec/acts_as_archive/base_spec.rb", "spec/acts_as_archive/migration_spec.rb", "spec/db", "spec/db/config", "spec/db/config/database.yml", "spec/db/log", "spec/db/migrate", "spec/db/migrate/001_add_to_articles.rb", "spec/db/migrate_2", "spec/db/migrate_2/001_add_to_articles.rb", "spec/db/models", "spec/db/models/article.rb", "spec/spec.opts", "spec/spec_helper.rb"]
15
+ s.homepage = %q{http://github.com/winton/acts_as_archive}
16
+ s.require_paths = ["lib"]
17
+ s.rubygems_version = %q{1.3.5}
18
+ s.summary = %q{Don't delete your records, move them to a different table}
19
+
20
+ if s.respond_to? :specification_version then
21
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
22
+ s.specification_version = 3
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
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.push File.expand_path("#{File.dirname(__FILE__)}/../lib")
4
+ require 'acts_as_archive'
5
+
6
+ puts `script/runner "ActsAsArchive.update"`
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + "/acts_as_archive/base"
2
+ require File.dirname(__FILE__) + "/acts_as_archive/migration"
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + "/base/destroy"
2
+ require File.dirname(__FILE__) + "/base/restore"
3
+ require File.dirname(__FILE__) + "/base/table"
4
+
5
+ module ActsAsArchive
6
+ module Base
7
+ def self.included(base)
8
+ base.extend ActMethods
9
+ end
10
+
11
+ module ActMethods
12
+ def acts_as_archive(options={})
13
+ class_eval <<-end_eval
14
+
15
+ def self.acts_as_archive?
16
+ self.to_s == #{self.to_s.inspect}
17
+ end
18
+
19
+ def self.archive_indexes
20
+ #{Array(options[:indexes]).collect(&:to_s).inspect}
21
+ end
22
+
23
+ class Archive < ActiveRecord::Base
24
+ self.record_timestamps = false
25
+ self.table_name = "archived_#{self.table_name}"
26
+ end
27
+ end_eval
28
+ include Destroy
29
+ include Restore
30
+ include Table
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,66 @@
1
+ module ActsAsArchive
2
+ module Base
3
+ module Destroy
4
+
5
+ def self.included(base)
6
+ unless base.included_modules.include?(InstanceMethods)
7
+ base.class_eval do
8
+ alias_method :destroy_without_callbacks!, :destroy_without_callbacks
9
+ class <<self
10
+ alias_method :delete_all!, :delete_all
11
+ end
12
+ end
13
+ base.send :extend, ClassMethods
14
+ base.send :include, InstanceMethods
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def copy_to_archive(conditions, import=false)
20
+ add_conditions!(where = '', conditions)
21
+ insert_cols = column_names.clone
22
+ select_cols = column_names.clone
23
+ if insert_cols.include?('deleted_at')
24
+ unless import
25
+ select_cols[select_cols.index('deleted_at')] = "'#{Time.now.utc.to_s(:db)}'"
26
+ end
27
+ else
28
+ insert_cols << 'deleted_at'
29
+ select_cols << "'#{Time.now.utc.to_s(:db)}'"
30
+ end
31
+ connection.execute(%{
32
+ INSERT INTO archived_#{table_name} (#{insert_cols.join(', ')})
33
+ SELECT #{select_cols.join(', ')}
34
+ FROM #{table_name}
35
+ #{where}
36
+ })
37
+ connection.execute("DELETE FROM #{table_name} #{where}")
38
+ end
39
+
40
+ def delete_all(conditions=nil)
41
+ copy_to_archive(conditions)
42
+ end
43
+ end
44
+
45
+ module InstanceMethods
46
+ def destroy_without_callbacks
47
+ unless new_record?
48
+ self.class.copy_to_archive("#{self.class.primary_key} = #{id}")
49
+ end
50
+ freeze
51
+ end
52
+
53
+ def destroy!
54
+ transaction { destroy_with_callbacks! }
55
+ end
56
+
57
+ def destroy_with_callbacks!
58
+ return false if callback(:before_destroy) == false
59
+ result = destroy_without_callbacks!
60
+ callback(:after_destroy)
61
+ result
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,35 @@
1
+ module ActsAsArchive
2
+ module Base
3
+ module Restore
4
+
5
+ def self.included(base)
6
+ unless base.included_modules.include?(InstanceMethods)
7
+ base.send :extend, ClassMethods
8
+ base.send :include, InstanceMethods
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ def copy_from_archive(conditions)
15
+ add_conditions!(where = '', conditions)
16
+ col_names = column_names - [ 'deleted_at' ]
17
+ connection.execute(%{
18
+ INSERT INTO #{table_name} (#{col_names.join(', ')})
19
+ SELECT #{col_names.join(', ')}
20
+ FROM archived_#{table_name}
21
+ #{where}
22
+ })
23
+ connection.execute("DELETE FROM archived_#{table_name} #{where}")
24
+ end
25
+
26
+ def restore_all(conditions=nil)
27
+ copy_from_archive(conditions)
28
+ end
29
+ end
30
+
31
+ module InstanceMethods
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,65 @@
1
+ module ActsAsArchive
2
+ module Base
3
+ module Table
4
+
5
+ def self.included(base)
6
+ unless base.included_modules.include?(InstanceMethods)
7
+ base.send :extend, ClassMethods
8
+ base.send :include, InstanceMethods
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ def archive_table_exists?
15
+ connection.table_exists?("archived_#{table_name}")
16
+ end
17
+
18
+ def create_archive_table
19
+ if table_exists? && !archive_table_exists?
20
+ connection.execute(%{
21
+ CREATE TABLE archived_#{table_name}
22
+ ENGINE=InnoDB
23
+ AS SELECT * from #{table_name}
24
+ WHERE false;
25
+ })
26
+ columns = connection.columns("archived_#{table_name}").collect(&:name)
27
+ unless columns.include?('deleted_at')
28
+ connection.add_column("archived_#{table_name}", :deleted_at, :datetime)
29
+ end
30
+ end
31
+ end
32
+
33
+ def create_archive_indexes
34
+ if archive_table_exists?
35
+ indexes = "SHOW INDEX FROM archived_#{table_name}"
36
+ indexes = connection.select_all(indexes).collect do |r|
37
+ r["Column_name"]
38
+ end
39
+ (archive_indexes - indexes).each do |index|
40
+ connection.add_index("archived_#{table_name}", index)
41
+ end
42
+ (indexes - archive_indexes).each do |index|
43
+ connection.remove_index("archived_#{table_name}", index)
44
+ end
45
+ end
46
+ end
47
+
48
+ def migrate_from_acts_as_paranoid
49
+ if column_names.include?('deleted_at')
50
+ if table_exists? && archive_table_exists?
51
+ condition = "deleted_at IS NOT NULL"
52
+ if self.count_by_sql("SELECT COUNT(*) FROM #{table_name} WHERE #{condition}") > 0
53
+ # Base::Destroy.copy_to_archive
54
+ copy_to_archive(condition, true)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ module InstanceMethods
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,47 @@
1
+ module ActsAsArchive
2
+ module Migration
3
+
4
+ def self.included(base)
5
+ unless base.included_modules.include?(InstanceMethods)
6
+ base.send :extend, ClassMethods
7
+ base.class_eval do
8
+ class <<self
9
+ alias_method :method_missing_without_archive, :method_missing
10
+ alias_method :method_missing, :method_missing_with_archive
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ def method_missing_with_archive(method, *arguments, &block)
19
+ args = Marshal.load(Marshal.dump(arguments))
20
+ method_missing_without_archive(method, *arguments, &block)
21
+ supported = [
22
+ :add_column, :add_timestamps, :change_column,
23
+ :change_column_default, :change_table,
24
+ :drop_table, :remove_column, :remove_columns,
25
+ :remove_timestamps, :rename_column, :rename_table
26
+ ]
27
+ if args.include?(:deleted_at) || args.include?('deleted_at')
28
+ # Don't change the archive's deleted_at column
29
+ return
30
+ end
31
+ if !args.empty? && supported.include?(method)
32
+ connection = ActiveRecord::Base.connection
33
+ args[0] = "archived_" + ActiveRecord::Migrator.proper_table_name(args[0])
34
+ if method == :rename_table
35
+ args[1] = "archived_" + args[1]
36
+ end
37
+ if connection.table_exists?(args[0])
38
+ connection.send(method, *args, &block)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ module InstanceMethods
45
+ end
46
+ end
47
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,16 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/acts_as_archive")
2
+
3
+ ActiveRecord::Base.send(:include, ActsAsArchive::Base)
4
+ ActiveRecord::Migration.send(:include, ActsAsArchive::Migration)
5
+
6
+ module ActsAsArchive
7
+ def self.update
8
+ Object.subclasses_of(ActiveRecord::Base).each do |klass|
9
+ if klass.respond_to?(:acts_as_archive?) && klass.acts_as_archive?
10
+ klass.create_archive_table
11
+ klass.migrate_from_acts_as_paranoid
12
+ klass.create_archive_indexes
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,98 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
2
+
3
+ describe ActsAsArchive::Base::Destroy do
4
+
5
+ before(:all) do
6
+ establish_test_db
7
+ Article.create_archive_table
8
+ end
9
+
10
+ describe 'delete_all!' do
11
+
12
+ before(:all) do
13
+ create_records
14
+ end
15
+
16
+ it "should really delete all records" do
17
+ Article.delete_all!
18
+ Article.count.should == 0
19
+ Article::Archive.count.should == 0
20
+ end
21
+
22
+ end
23
+
24
+ describe 'delete_all' do
25
+
26
+ before(:all) do
27
+ @articles = create_records
28
+ end
29
+
30
+ describe 'with conditions' do
31
+
32
+ before(:all) do
33
+ # Mini delete_all parameter test
34
+ Article.delete_all [ 'id = ?', @articles[0].id ]
35
+ Article.delete_all "id = #{@articles[1].id}"
36
+ end
37
+
38
+ it "should move some records to the archive table" do
39
+ Article.count.should == 3
40
+ Article::Archive.count.should == 2
41
+ end
42
+
43
+ it "should preserve record attributes" do
44
+ 2.times do |x|
45
+ original = @articles[x]
46
+ copy = Article::Archive.find(original.id)
47
+ article_match?(original, copy)
48
+ end
49
+ end
50
+ end
51
+
52
+ describe 'without conditions' do
53
+
54
+ before(:all) do
55
+ Article.delete_all
56
+ end
57
+
58
+ it "should move all records to the archive table" do
59
+ Article.count.should == 0
60
+ Article::Archive.count.should == 5
61
+ end
62
+
63
+ it "should preserve record attributes" do
64
+ 5.times do |x|
65
+ original = @articles[x]
66
+ copy = Article::Archive.find(original.id)
67
+ article_match?(original, copy)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ [ :destroy, :delete ].each do |d|
74
+
75
+ describe d do
76
+
77
+ before(:all) do
78
+ @articles = create_records
79
+ Article.find(@articles[0..1].collect(&:id)).each do |a|
80
+ a.send(d)
81
+ end
82
+ end
83
+
84
+ it "should move some records to the archive table" do
85
+ Article.count.should == 3
86
+ Article::Archive.count.should == 2
87
+ end
88
+
89
+ it "should preserve record attributes" do
90
+ 2.times do |x|
91
+ original = @articles[x]
92
+ copy = Article::Archive.find(original.id)
93
+ article_match?(original, copy)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
2
+
3
+ describe ActsAsArchive::Base::Restore do
4
+
5
+ before(:all) do
6
+ establish_test_db
7
+ Article.create_archive_table
8
+ end
9
+
10
+ describe 'restore_all' do
11
+
12
+ before(:all) do
13
+ @articles = create_records(Article::Archive)
14
+ end
15
+
16
+ describe 'with conditions' do
17
+
18
+ before(:all) do
19
+ # Mini restore parameter test
20
+ Article.restore_all [ 'id = ?', @articles[0].id ]
21
+ Article.restore_all "id = #{@articles[1].id}"
22
+ end
23
+
24
+ it "should move some records to the article table" do
25
+ Article::Archive.count.should == 3
26
+ Article.count.should == 2
27
+ end
28
+
29
+ it "should preserve record attributes" do
30
+ 2.times do |x|
31
+ original = @articles[x]
32
+ copy = Article.find(original.id)
33
+ article_match?(original, copy)
34
+ end
35
+ end
36
+ end
37
+
38
+ describe 'without conditions' do
39
+
40
+ before(:all) do
41
+ Article.restore_all
42
+ end
43
+
44
+ it "should move all records to the archive table" do
45
+ Article::Archive.count.should == 0
46
+ Article.count.should == 5
47
+ end
48
+
49
+ it "should preserve record attributes" do
50
+ 5.times do |x|
51
+ original = @articles[x]
52
+ copy = Article.find(original.id)
53
+ article_match?(original, copy)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,74 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
2
+
3
+ describe ActsAsArchive::Base::Table do
4
+
5
+ before(:all) do
6
+ establish_test_db
7
+ Article.create_archive_table
8
+ end
9
+
10
+ describe 'create_archive_table' do
11
+
12
+ before(:all) do
13
+ @article_columns = connection.columns("articles").collect(&:name)
14
+ @archive_columns = connection.columns("archived_articles").collect(&:name)
15
+ end
16
+
17
+ it "should create an archive table" do
18
+ connection.table_exists?("archived_articles").should == true
19
+ end
20
+
21
+ it "should create an archive table with the same structure as the original table" do
22
+ @article_columns.each do |col|
23
+ @archive_columns.include?(col).should == true
24
+ end
25
+ end
26
+
27
+ it "should add a deleted_at column to the archive table" do
28
+ (@archive_columns - @article_columns).should == [ 'deleted_at' ]
29
+ end
30
+ end
31
+
32
+ describe 'create_archive_indexes' do
33
+
34
+ before(:all) do
35
+ Article.create_archive_indexes
36
+ end
37
+
38
+ it "should create archive indexes" do
39
+ indexes.should == [ "id", "deleted_at" ]
40
+ end
41
+
42
+ it "should destroy archive indexes" do
43
+ Article.class_eval { acts_as_archive }
44
+ Article.create_archive_indexes
45
+ indexes.should == []
46
+ end
47
+ end
48
+
49
+ describe 'migrate_from_acts_as_paranoid' do
50
+
51
+ before(:all) do
52
+ connection.add_column(:articles, :deleted_at, :datetime)
53
+ Article.reset_column_information
54
+ end
55
+
56
+ before(:each) do
57
+ connection.execute("DELETE FROM #{Article::Archive.table_name}")
58
+ end
59
+
60
+ it "should move deleted records to the archive" do
61
+ create_records(Article, :deleted_at => Time.now.utc)
62
+ Article.migrate_from_acts_as_paranoid
63
+ Article.count.should == 0
64
+ Article::Archive.count.should == 5
65
+ end
66
+
67
+ it "should not move non-deleted records to the archive" do
68
+ create_records
69
+ Article.migrate_from_acts_as_paranoid
70
+ Article.count.should == 5
71
+ Article::Archive.count.should == 0
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+
3
+ describe ActsAsArchive::Base do
4
+
5
+ before(:all) do
6
+ establish_test_db
7
+ end
8
+
9
+ describe 'acts_as_archive' do
10
+
11
+ it "should add self.acts_as_archive? to the model" do
12
+ Article.respond_to?(:acts_as_archive?).should == true
13
+ end
14
+
15
+ it "should add self.archive_indexes to the model" do
16
+ Article.respond_to?(:archive_indexes).should == true
17
+ Article.archive_indexes.should == [ 'id', 'deleted_at' ]
18
+ end
19
+
20
+ it "should add Archive class to the model" do
21
+ defined?(Article::Archive).should == "constant"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe ActsAsArchive::Migration do
4
+
5
+ before(:each) do
6
+ establish_test_db
7
+ Article.create_archive_table
8
+ end
9
+
10
+ describe 'method_missing_with_archive' do
11
+
12
+ it 'should migrate both tables up' do
13
+ migrate_up
14
+ (@new_article_columns - @old_article_columns).should == [ 'permalink' ]
15
+ (@new_archive_columns - @old_archive_columns).should == [ 'permalink' ]
16
+ end
17
+
18
+ it 'should migrate both tables down' do
19
+ migrate_up
20
+ @old_article_columns = @new_article_columns
21
+ @old_archive_columns = @new_archive_columns
22
+ ActiveRecord::Migrator.migrate("#{SPEC}/db/migrate", 0)
23
+ @new_article_columns = columns("articles")
24
+ @new_archive_columns = columns("archived_articles")
25
+ (@old_article_columns - @new_article_columns).should == [ 'permalink' ]
26
+ (@old_archive_columns - @new_archive_columns).should == [ 'permalink' ]
27
+ end
28
+
29
+ it "should not touch the archive's deleted_at column" do
30
+ connection.add_column(:articles, :deleted_at, :datetime)
31
+ Article.reset_column_information
32
+ migrate_up("migrate_2")
33
+ (@old_article_columns - @new_article_columns).should == [ 'deleted_at' ]
34
+ (@old_archive_columns - @new_archive_columns).should == []
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ test:
2
+ adapter: mysql
3
+ database: acts_as_archive
4
+ username: root
5
+ password:
6
+ host: localhost
7
+ socket: /tmp/mysql.sock
@@ -0,0 +1,9 @@
1
+ class AddToArticles < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :articles, :permalink, :string
4
+ end
5
+
6
+ def self.down
7
+ remove_column :articles, :permalink
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class AddToArticles < ActiveRecord::Migration
2
+ def self.up
3
+ remove_column :articles, :deleted_at
4
+ end
5
+
6
+ def self.down
7
+ add_column :articles, :deleted_at, :string
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ class Article < ActiveRecord::Base
2
+ acts_as_archive :indexes => [ :id, :deleted_at ]
3
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,100 @@
1
+ $TESTING=true
2
+ SPEC = File.expand_path(File.dirname(__FILE__))
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+ require 'logger'
7
+ require 'yaml'
8
+ require 'pp'
9
+
10
+ require File.expand_path("#{SPEC}/../rails/init")
11
+
12
+ Spec::Runner.configure do |config|
13
+ end
14
+
15
+ def article_match?(original, copy)
16
+ copy.id.should == original.id
17
+ copy.title.should == original.title
18
+ copy.body.should == original.body
19
+ if copy.respond_to?(:deleted_at)
20
+ copy.deleted_at.strftime('%j%H%M').should == Time.now.utc.strftime('%j%H%M')
21
+ end
22
+ end
23
+
24
+ def columns(table)
25
+ connection.columns(table).collect(&:name)
26
+ end
27
+
28
+ def connection
29
+ ActiveRecord::Base.connection
30
+ end
31
+
32
+ def create_records(klass=Article, values={})
33
+ articles = []
34
+ table = klass.table_name
35
+ cols = columns(table)
36
+ connection.execute("DELETE FROM #{table}")
37
+ (1..5).collect do |x|
38
+ vals = cols.collect do |c|
39
+ if values.keys.include?(c.intern)
40
+ values[c.intern] ? "'#{values[c.intern]}'" : "NULL"
41
+ else
42
+ case c.intern
43
+ when :id; x
44
+ when :deleted_at; 'NULL'
45
+ else "'#{c.capitalize} #{x}'"
46
+ end
47
+ end
48
+ end
49
+ connection.execute(%{
50
+ INSERT INTO #{table} (#{cols.collect { |c| "`#{c}`" }.join(', ')})
51
+ VALUES (#{vals.join(', ')})
52
+ })
53
+ klass.find(x)
54
+ end
55
+ end
56
+
57
+ def debug(object)
58
+ puts "<pre>"
59
+ pp object
60
+ puts "</pre>"
61
+ end
62
+
63
+ def establish_test_db
64
+ # Establish connection
65
+ unless ActiveRecord::Base.connected?
66
+ config = YAML::load(File.open("#{SPEC}/db/config/database.yml"))
67
+ ActiveRecord::Base.configurations = config
68
+ ActiveRecord::Base.establish_connection(config['test'])
69
+ end
70
+ # Establish logger
71
+ logger_file = File.open("#{SPEC}/db/log/test.log", 'a')
72
+ logger_file.sync = true
73
+ @logger = Logger.new(logger_file)
74
+ ActiveRecord::Base.logger = @logger
75
+ # The database should have only a simple articles table
76
+ connection.execute("DROP TABLE IF EXISTS articles")
77
+ connection.execute("DROP TABLE IF EXISTS archived_articles")
78
+ connection.execute("DROP TABLE IF EXISTS schema_migrations")
79
+ connection.create_table(:articles) do |t|
80
+ t.string :title
81
+ t.string :body
82
+ end
83
+ # Load the model
84
+ load "#{SPEC}/db/models/article.rb"
85
+ end
86
+
87
+ def indexes
88
+ query = "SHOW INDEX FROM archived_#{Article.table_name}"
89
+ connection.select_all(query).collect do |r|
90
+ r["Column_name"]
91
+ end
92
+ end
93
+
94
+ def migrate_up(directory='migrate')
95
+ @old_article_columns = columns("articles")
96
+ @old_archive_columns = columns("archived_articles")
97
+ ActiveRecord::Migrator.migrate("#{SPEC}/db/#{directory}")
98
+ @new_article_columns = columns("articles")
99
+ @new_archive_columns = columns("archived_articles")
100
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_archive
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - Winton Welsh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-06 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: mail@wintoni.us
18
+ executables:
19
+ - acts_as_archive
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - acts_as_archive.gemspec
26
+ - bin/acts_as_archive
27
+ - init.rb
28
+ - lib/acts_as_archive/base/destroy.rb
29
+ - lib/acts_as_archive/base/restore.rb
30
+ - lib/acts_as_archive/base/table.rb
31
+ - lib/acts_as_archive/base.rb
32
+ - lib/acts_as_archive/migration.rb
33
+ - lib/acts_as_archive.rb
34
+ - MIT-LICENSE
35
+ - rails/init.rb
36
+ - Rakefile
37
+ - README.markdown
38
+ - spec/acts_as_archive/base/destroy_spec.rb
39
+ - spec/acts_as_archive/base/restore_spec.rb
40
+ - spec/acts_as_archive/base/table_spec.rb
41
+ - spec/acts_as_archive/base_spec.rb
42
+ - spec/acts_as_archive/migration_spec.rb
43
+ - spec/db/config/database.yml
44
+ - spec/db/migrate/001_add_to_articles.rb
45
+ - spec/db/migrate_2/001_add_to_articles.rb
46
+ - spec/db/models/article.rb
47
+ - spec/spec.opts
48
+ - spec/spec_helper.rb
49
+ has_rdoc: true
50
+ homepage: http://github.com/winton/acts_as_archive
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options: []
55
+
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.3.5
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Don't delete your records, move them to a different table
77
+ test_files: []
78
+