acts_as_archive 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+