sideshowbandana-acts_as_archive 0.2.6

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/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .DS_Store
2
+ *.gem
3
+ *.gemspec
4
+ coverage
5
+ pkg
6
+ spec/db/log/*.log
7
+ tmp
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,119 @@
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
+ <pre>
12
+ sudo gem install acts_as_archive
13
+ </pre>
14
+
15
+ **environment.rb**:
16
+
17
+ <pre>
18
+ config.gem 'acts_as_archive'
19
+ </pre>
20
+
21
+ Update models
22
+ -------------
23
+
24
+ Add <code>acts\_as\_archive</code> to your models:
25
+
26
+ <pre>
27
+ class Article < ActiveRecord::Base
28
+ acts_as_archive
29
+ end
30
+ </pre>
31
+
32
+ <a name="create_archive_tables"></a>
33
+
34
+ Create archive tables
35
+ ---------------------
36
+
37
+ Add this line to a migration:
38
+
39
+ <pre>
40
+ ActsAsArchive.update Article, Comment
41
+ </pre>
42
+
43
+ Replace <code>Article, Comment</code> with your own models that use <code>acts_as_archive</code>.
44
+
45
+ Archive tables mirror your table's structure, but with an additional <code>deleted_at</code> column.
46
+
47
+ There is an [alternate way to create archive tables](http://wiki.github.com/winton/acts_as_archive/alternatives-to-migrations) if you don't like migrations.
48
+
49
+ That's it!
50
+ ----------
51
+
52
+ Use <code>destroy</code>, <code>delete</code>, and <code>delete_all</code> like you normally would.
53
+
54
+ Records move into the archive table instead of being destroyed.
55
+
56
+ What if my schema changes?
57
+ --------------------------
58
+
59
+ New migrations are automatically applied to the archive table.
60
+
61
+ No action is necessary on your part.
62
+
63
+ Query the archive
64
+ -----------------
65
+
66
+ Add <code>::Archive</code> to your ActiveRecord class:
67
+
68
+ <pre>
69
+ Article::Archive.find(:first)
70
+ </pre>
71
+
72
+ Restore from the archive
73
+ ------------------------
74
+
75
+ Use <code>restore\_all</code> to copy archived records back to your table:
76
+
77
+ <pre>
78
+ Article.restore_all([ 'id = ?', 1 ])
79
+ </pre>
80
+
81
+ Auto-migrate from acts\_as\_paranoid
82
+ ------------------------------------
83
+
84
+ If you previously used <code>acts\_as\_paranoid</code>, the <code>ActsAsArchive.update</code>
85
+ call will automatically move your deleted records to the archive table
86
+ (see <a href="#create_archive_tables">_Create archive tables_</a>).
87
+
88
+ Original <code>deleted_at</code> values are preserved.
89
+
90
+ Add indexes to the archive table
91
+ --------------------------------
92
+
93
+ To keep insertions fast, there are no indexes on your archive table by default.
94
+
95
+ If you are querying your archive a lot, you will want to add indexes:
96
+
97
+ <pre>
98
+ class Article < ActiveRecord::Base
99
+ acts_as_archive :indexes => [ :id, :created_at, :deleted_at ]
100
+ end
101
+ </pre>
102
+
103
+ Call <code>ActsAsArchive.update</code> upon adding new indexes
104
+ (see <a href="#create_archive_tables">_Create archive tables_</a>).
105
+
106
+ Delete records without archiving
107
+ --------------------------------
108
+
109
+ To destroy a record without archiving:
110
+
111
+ <pre>
112
+ article.destroy!
113
+ </pre>
114
+
115
+ To delete multiple records without archiving:
116
+
117
+ <pre>
118
+ Article.delete_all!(["id in (?)", [1,2,3]])
119
+ </pre>
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require "#{File.dirname(__FILE__)}/require"
2
+ Require.rakefile!
3
+
4
+ # desc "Generate gemspec"
5
+ # task :gemspec do
6
+ # File.open("#{Rake.original_dir}/acts_as_archive.gemspec", 'w') do |f|
7
+ # f.write(Require.gemspec.to_ruby)
8
+ # end
9
+ # end
10
+
11
+ begin
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gemspec|
14
+ gemspec.name = "sideshowbandana-acts_as_archive"
15
+ gemspec.summary = "Don't delete your records, move them to a different table"
16
+ gemspec.description = "Like acts_as_paranoid, but doesn't mess with your SQL queries."
17
+ gemspec.email = "kyle.humberto@gmail.com"
18
+ gemspec.homepage = "http://github.com/sideshowbandana/acts_as_archive"
19
+ gemspec.authors = ["Kyle Barton"]
20
+ end
21
+ rescue LoadError
22
+ puts "Jeweler not available. Install it with: gem install jeweler"
23
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.6
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{acts_as_archive}
5
+ s.version = "0.2.5"
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{2010-05-07}
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 = ["bin", "bin/acts_as_archive", "init.rb", "lib", "lib/acts_as_archive", "lib/acts_as_archive/base", "lib/acts_as_archive/base/adapters", "lib/acts_as_archive/base/adapters/mysql.rb", "lib/acts_as_archive/base/adapters/postgresql.rb", "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", "require.rb", "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.mysql.yml", "spec/db/config/database.postgresql.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
+ s.add_runtime_dependency(%q<require>, ["= 0.2.1"])
26
+ else
27
+ s.add_dependency(%q<require>, ["= 0.2.1"])
28
+ end
29
+ else
30
+ s.add_dependency(%q<require>, ["= 0.2.1"])
31
+ end
32
+ end
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ puts `script/runner "ActsAsArchive.update #{ARGV.join ', '}"`
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1,19 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../require")
2
+ Require.lib!
3
+
4
+ module ActsAsArchive
5
+
6
+ def self.update(*models)
7
+ models.each do |klass|
8
+ if klass.respond_to?(:acts_as_archive?) && klass.acts_as_archive?
9
+ time = Benchmark.measure do
10
+ klass.create_archive_table
11
+ klass.migrate_from_acts_as_paranoid
12
+ klass.create_archive_indexes
13
+ end
14
+ $stdout.puts "-- ActsAsArchive.update(#{models.join(', ')})"
15
+ $stdout.puts " -> #{"%.4fs" % time.real}"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ module ActsAsArchive
2
+
3
+ module Base
4
+ def self.included(base)
5
+ base.extend ActMethods
6
+ end
7
+
8
+ module ActMethods
9
+ def acts_as_archive(options={})
10
+ class_eval <<-end_eval
11
+
12
+ def self.acts_as_archive?
13
+ self.to_s == #{self.to_s.inspect}
14
+ end
15
+
16
+ def self.archive_indexes
17
+ #{Array(options[:indexes]).collect(&:to_s).inspect}
18
+ end
19
+
20
+ class Archive < ActiveRecord::Base
21
+ self.record_timestamps = false
22
+ self.table_name = "archived_#{self.table_name}"
23
+ end
24
+ end_eval
25
+ include Destroy
26
+ include Restore
27
+ include Table
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module ActsAsArchive
2
+ module Base
3
+ module Adapters
4
+ module MySQL
5
+
6
+ private
7
+
8
+ def archive_table_indexed_columns
9
+ index_query = "SHOW INDEX FROM archived_#{table_name}"
10
+ indexes = connection.select_all(index_query).collect do |r|
11
+ r["Column_name"]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,40 @@
1
+ module ActsAsArchive
2
+ module Base
3
+ module Adapters
4
+ module PostgreSQL
5
+
6
+ private
7
+
8
+ def archive_table_indexed_columns
9
+ # This query comes courtesy of cope360:
10
+ # http://stackoverflow.com/questions/2204058/show-which-columns-an-index-is-on-in-postgresql/2213199#2213199
11
+ index_query = <<-SQL
12
+ select
13
+ t.relname as table_name,
14
+ i.relname as index_name,
15
+ a.attname as column_name
16
+ from
17
+ pg_class t,
18
+ pg_class i,
19
+ pg_index ix,
20
+ pg_attribute a
21
+ where
22
+ t.oid = ix.indrelid
23
+ and i.oid = ix.indexrelid
24
+ and a.attrelid = t.oid
25
+ and a.attnum = ANY(ix.indkey)
26
+ and t.relkind = 'r'
27
+ and t.relname = 'archived_#{table_name}'
28
+ order by
29
+ t.relname,
30
+ i.relname
31
+ SQL
32
+
33
+ indexes = connection.select_all(index_query).collect do |r|
34
+ r["column_name"]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,71 @@
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
+
32
+ insert_cols.map! { |col| connection.quote_column_name(col) }
33
+ select_cols.map! { |col| col =~ /^\'/ ? col : connection.quote_column_name(col) }
34
+
35
+ connection.execute(%{
36
+ INSERT INTO archived_#{table_name} (#{insert_cols.join(', ')})
37
+ SELECT #{select_cols.join(', ')}
38
+ FROM #{table_name}
39
+ #{where}
40
+ })
41
+ connection.execute("DELETE FROM #{table_name} #{where}")
42
+ end
43
+
44
+ def delete_all(conditions=nil)
45
+ copy_to_archive(conditions)
46
+ end
47
+ end
48
+
49
+ module InstanceMethods
50
+ def destroy_without_callbacks
51
+ unless new_record?
52
+ self.class.copy_to_archive("#{self.class.primary_key} = #{id}")
53
+ end
54
+ @destroyed = true
55
+ freeze
56
+ end
57
+
58
+ def destroy!
59
+ transaction { destroy_with_callbacks! }
60
+ end
61
+
62
+ def destroy_with_callbacks!
63
+ return false if callback(:before_destroy) == false
64
+ result = destroy_without_callbacks!
65
+ callback(:after_destroy)
66
+ result
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,36 @@
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
+ col_names.map! { |col| connection.quote_column_name(col) }
18
+ connection.execute(%{
19
+ INSERT INTO #{table_name} (#{col_names.join(', ')})
20
+ SELECT #{col_names.join(', ')}
21
+ FROM archived_#{table_name}
22
+ #{where}
23
+ })
24
+ connection.execute("DELETE FROM archived_#{table_name} #{where}")
25
+ end
26
+
27
+ def restore_all(conditions=nil)
28
+ copy_from_archive(conditions)
29
+ end
30
+ end
31
+
32
+ module InstanceMethods
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,109 @@
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
+
10
+ if base.connection.class.to_s.include?('Mysql')
11
+ base.send :extend, ActsAsArchive::Base::Adapters::MySQL
12
+ elsif base.connection.class.to_s.include?('PostgreSQL')
13
+ base.send :extend, ActsAsArchive::Base::Adapters::PostgreSQL
14
+ else
15
+ raise 'acts_as_archive does not support this database adapter'
16
+ end
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+
22
+ def archive_table_exists?
23
+ connection.table_exists?("archived_#{table_name}")
24
+ end
25
+
26
+ def create_archive_table
27
+ if table_exists? && !archive_table_exists?
28
+ connection.execute(%{
29
+ CREATE TABLE archived_#{table_name}
30
+ #{"ENGINE=InnoDB" if connection.class.to_s.include?('Mysql')}
31
+ AS SELECT * from #{table_name}
32
+ WHERE false;
33
+ })
34
+ columns = connection.columns("archived_#{table_name}").collect(&:name)
35
+ unless columns.include?('deleted_at')
36
+ connection.add_column("archived_#{table_name}", :deleted_at, :datetime)
37
+ end
38
+ end
39
+ end
40
+
41
+ def create_archive_indexes
42
+ if archive_table_exists?
43
+ indexes = archive_table_indexed_columns
44
+
45
+ (archive_indexes - indexes).each do |index|
46
+ connection.add_index("archived_#{table_name}", index)
47
+ end
48
+ (indexes - archive_indexes).each do |index|
49
+ connection.remove_index("archived_#{table_name}", index)
50
+ end
51
+ end
52
+ end
53
+
54
+
55
+ def migrate_from_acts_as_paranoid
56
+ if column_names.include?('deleted_at')
57
+ if table_exists? && archive_table_exists?
58
+ condition = "deleted_at IS NOT NULL"
59
+ if self.count_by_sql("SELECT COUNT(*) FROM #{table_name} WHERE #{condition}") > 0
60
+ # Base::Destroy.copy_to_archive
61
+ copy_to_archive(condition, true)
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def archive_table_indexed_columns
70
+ case connection.class.to_s
71
+ when "ActiveRecord::ConnectionAdapters::MysqlAdapter"
72
+ index_query = "SHOW INDEX FROM archived_#{table_name}"
73
+ indexes = connection.select_all(index_query).collect do |r|
74
+ r["Column_name"]
75
+ end
76
+ when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
77
+ #postgresql is...slightly...more complicated
78
+ index_query = <<EOS
79
+ SELECT c2.relname as index_name
80
+ FROM pg_catalog.pg_class c,
81
+ pg_catalog.pg_class c2,
82
+ pg_catalog.pg_index i
83
+ WHERE c.oid = (SELECT c.oid
84
+ FROM pg_catalog.pg_class c
85
+ WHERE c.relname ~ '^(archived_#{table_name})$')
86
+ AND c.oid = i.indrelid
87
+ AND i.indexrelid = c2.oid
88
+ EOS
89
+ indexes = connection.select_all(index_query).collect do |r|
90
+ r["index_name"]
91
+ end
92
+
93
+ # HACK: reverse engineer the column name
94
+ # This sucks, but acts_as_archive only adds indexes on single columns anyway so it should work OK
95
+ # and getting the columns indexed is INCREDIBLY complicated in PostgreSQL.
96
+ indexes.map do |index|
97
+ index.split("_on_").last
98
+ end
99
+ else
100
+ raise "Unsupported Database"
101
+ end
102
+ end
103
+ end
104
+
105
+ module InstanceMethods
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,48 @@
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
+ unless method_defined?(:method_missing_without_archive)
10
+ alias_method_chain :method_missing, :archive
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ def method_missing_with_archive(method, *arguments, &block)
20
+ args = Marshal.load(Marshal.dump(arguments))
21
+ result = method_missing_without_archive(method, *arguments, &block)
22
+ # Don't change the archive's deleted_at column
23
+ unless args.include?(:deleted_at) || args.include?('deleted_at')
24
+ supported = [
25
+ :add_column, :add_timestamps, :change_column,
26
+ :change_column_default, :change_table,
27
+ :drop_table, :remove_column, :remove_columns,
28
+ :remove_timestamps, :rename_column, :rename_table
29
+ ]
30
+ if !args.empty? && supported.include?(method)
31
+ connection = ActiveRecord::Base.connection
32
+ args[0] = "archived_" + ActiveRecord::Migrator.proper_table_name(args[0])
33
+ if method == :rename_table
34
+ args[1] = "archived_" + args[1].to_s
35
+ end
36
+ if connection.table_exists?(args[0])
37
+ connection.send(method, *args, &block)
38
+ end
39
+ end
40
+ end
41
+ result
42
+ end
43
+ end
44
+
45
+ module InstanceMethods
46
+ end
47
+ end
48
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,5 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../require")
2
+ Require.rails_init!
3
+
4
+ ActiveRecord::Base.send(:include, ActsAsArchive::Base)
5
+ ActiveRecord::Migration.send(:include, ActsAsArchive::Migration)
data/require.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ gem 'require'
3
+ require 'require'
4
+
5
+ Require do
6
+ gem(:activerecord) { require 'active_record' }
7
+ gem :require, '=0.2.1'
8
+ gem(:rake, '=0.8.7') { require 'rake' }
9
+ gem :rspec, '=1.3.0'
10
+
11
+ gemspec do
12
+ author 'Winton Welsh'
13
+ dependencies do
14
+ gem :require
15
+ end
16
+ email 'mail@wintoni.us'
17
+ name 'acts_as_archive'
18
+ homepage "http://github.com/winton/#{name}"
19
+ summary "Don't delete your records, move them to a different table"
20
+ version '0.2.5'
21
+ end
22
+
23
+ lib do
24
+ require "lib/acts_as_archive/base"
25
+ require "lib/acts_as_archive/base/adapters/mysql"
26
+ require "lib/acts_as_archive/base/adapters/postgresql"
27
+ require "lib/acts_as_archive/base/destroy"
28
+ require "lib/acts_as_archive/base/restore"
29
+ require "lib/acts_as_archive/base/table"
30
+ require "lib/acts_as_archive/migration"
31
+ end
32
+
33
+ rails_init { require 'lib/acts_as_archive' }
34
+
35
+ rakefile do
36
+ gem(:rake) { require 'rake/gempackagetask' }
37
+ gem(:rspec) { require 'spec/rake/spectask' }
38
+ require 'require/tasks'
39
+ end
40
+
41
+ spec_helper do
42
+ require 'require/spec_helper'
43
+ gem :activerecord
44
+ require 'logger'
45
+ require 'yaml'
46
+ require 'pp'
47
+ require 'rails/init'
48
+ end
49
+ end
@@ -0,0 +1,117 @@
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 'destroy!' do
25
+
26
+ before(:all) do
27
+ create_records
28
+ @article = Article.first
29
+ end
30
+
31
+ it "should really destroy a records" do
32
+ @article.destroy!
33
+ Article::Archive.count.should == 0
34
+ end
35
+
36
+ end
37
+
38
+ describe 'delete_all' do
39
+
40
+ before(:all) do
41
+ @articles = create_records
42
+ end
43
+
44
+ describe 'with conditions' do
45
+
46
+ before(:all) do
47
+ # Mini delete_all parameter test
48
+ Article.delete_all [ 'id = ?', @articles[0].id ]
49
+ Article.delete_all "id = #{@articles[1].id}"
50
+ end
51
+
52
+ it "should move some records to the archive table" do
53
+ Article.count.should == 3
54
+ Article::Archive.count.should == 2
55
+ end
56
+
57
+ it "should preserve record attributes" do
58
+ 2.times do |x|
59
+ original = @articles[x]
60
+ copy = Article::Archive.find(original.id)
61
+ article_match?(original, copy)
62
+ end
63
+ end
64
+ end
65
+
66
+ describe 'without conditions' do
67
+
68
+ before(:all) do
69
+ Article.delete_all
70
+ end
71
+
72
+ it "should move all records to the archive table" do
73
+ Article.count.should == 0
74
+ Article::Archive.count.should == 5
75
+ end
76
+
77
+ it "should preserve record attributes" do
78
+ 5.times do |x|
79
+ original = @articles[x]
80
+ copy = Article::Archive.find(original.id)
81
+ article_match?(original, copy)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ [ :destroy, :delete ].each do |d|
88
+
89
+ describe d do
90
+
91
+ before(:all) do
92
+ @articles = create_records
93
+ Article.find(@articles[0..1].collect(&:id)).each do |a|
94
+ a.send(d)
95
+ end
96
+ end
97
+
98
+ it "should move some records to the archive table" do
99
+ Article.count.should == 3
100
+ Article::Archive.count.should == 2
101
+ end
102
+
103
+ it "should preserve record attributes" do
104
+ 2.times do |x|
105
+ original = @articles[x]
106
+ copy = Article::Archive.find(original.id)
107
+ article_match?(original, copy)
108
+ end
109
+ end
110
+
111
+ it "should mark the object as destroyed" do
112
+ @articles[3].send(d)
113
+ @articles[3].destroyed?.should == true
114
+ end
115
+ end
116
+ end
117
+ 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.to_set.should == [ "id", "deleted_at" ].to_set
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.to_s(:db))
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,6 @@
1
+ test:
2
+ adapter: mysql
3
+ database: acts_as_archive
4
+ username: root
5
+ password: Seeyal8r
6
+ host: localhost
@@ -0,0 +1,6 @@
1
+ test:
2
+ adapter: postgresql
3
+ database: acts_as_archive
4
+ username: postgres
5
+ password:
6
+ host: localhost
File without changes
@@ -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,90 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../require")
2
+ Require.spec_helper!
3
+
4
+ Spec::Runner.configure do |config|
5
+ end
6
+
7
+ def db_type
8
+ ENV['DB_TYPE'] ? ENV['DB_TYPE'] : 'mysql'
9
+ end
10
+
11
+ def article_match?(original, copy)
12
+ copy.id.should == original.id
13
+ copy.title.should == original.title
14
+ copy.body.should == original.body
15
+ if copy.has_attribute?(:deleted_at)
16
+ copy.deleted_at.strftime('%j%H%M').should == Time.now.utc.strftime('%j%H%M')
17
+ end
18
+ end
19
+
20
+ def columns(table)
21
+ connection.columns(table).collect(&:name)
22
+ end
23
+
24
+ def connection
25
+ ActiveRecord::Base.connection
26
+ end
27
+
28
+ def create_records(klass=Article, values={})
29
+ articles = []
30
+ table = klass.table_name
31
+ cols = columns(table)
32
+ connection.execute("DELETE FROM #{table}")
33
+ (1..5).collect do |x|
34
+ vals = cols.collect do |c|
35
+ if values.keys.include?(c.intern)
36
+ values[c.intern] ? "'#{values[c.intern]}'" : "NULL"
37
+ else
38
+ case c.intern
39
+ when :id; x
40
+ when :deleted_at; 'NULL'
41
+ when :read; true
42
+ else
43
+ "'#{c.capitalize} #{x}'"
44
+ end
45
+ end
46
+ end
47
+ connection.execute(%{
48
+ INSERT INTO #{table} (#{cols.collect { |c| "#{connection.quote_column_name(c)}" }.join(', ')})
49
+ VALUES (#{vals.join(', ')})
50
+ })
51
+ klass.find(x)
52
+ end
53
+ end
54
+
55
+ def establish_test_db
56
+ # Establish connection
57
+ unless ActiveRecord::Base.connected?
58
+ config = YAML::load(File.open("#{SPEC}/db/config/database.#{db_type}.yml"))
59
+ ActiveRecord::Base.configurations = config
60
+ ActiveRecord::Base.establish_connection(config['test'])
61
+ end
62
+ # Establish logger
63
+ logger_file = File.open("#{SPEC}/db/log/test.log", 'a')
64
+ logger_file.sync = true
65
+ @logger = Logger.new(logger_file)
66
+ ActiveRecord::Base.logger = @logger
67
+ # The database should have only a simple articles table
68
+ connection.execute("DROP TABLE IF EXISTS articles")
69
+ connection.execute("DROP TABLE IF EXISTS archived_articles")
70
+ connection.execute("DROP TABLE IF EXISTS schema_migrations")
71
+ connection.create_table(:articles) do |t|
72
+ t.string :title
73
+ t.string :body
74
+ t.boolean :read # break mysql w/o quotation
75
+ end
76
+ # Load the model
77
+ load "#{SPEC}/db/models/article.rb"
78
+ end
79
+
80
+ def indexes
81
+ Article.send(:archive_table_indexed_columns)
82
+ end
83
+
84
+ def migrate_up(directory='migrate')
85
+ @old_article_columns = columns("articles")
86
+ @old_archive_columns = columns("archived_articles")
87
+ ActiveRecord::Migrator.migrate("#{SPEC}/db/#{directory}")
88
+ @new_article_columns = columns("articles")
89
+ @new_archive_columns = columns("archived_articles")
90
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sideshowbandana-acts_as_archive
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 6
10
+ version: 0.2.6
11
+ platform: ruby
12
+ authors:
13
+ - Kyle Barton
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-04 00:00:00 -07:00
19
+ default_executable: acts_as_archive
20
+ dependencies: []
21
+
22
+ description: Like acts_as_paranoid, but doesn't mess with your SQL queries.
23
+ email: kyle.humberto@gmail.com
24
+ executables:
25
+ - acts_as_archive
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.markdown
30
+ files:
31
+ - .gitignore
32
+ - MIT-LICENSE
33
+ - README.markdown
34
+ - Rakefile
35
+ - VERSION
36
+ - acts_as_archive.gemspec
37
+ - bin/acts_as_archive
38
+ - init.rb
39
+ - lib/acts_as_archive.rb
40
+ - lib/acts_as_archive/base.rb
41
+ - lib/acts_as_archive/base/adapters/mysql.rb
42
+ - lib/acts_as_archive/base/adapters/postgresql.rb
43
+ - lib/acts_as_archive/base/destroy.rb
44
+ - lib/acts_as_archive/base/restore.rb
45
+ - lib/acts_as_archive/base/table.rb
46
+ - lib/acts_as_archive/migration.rb
47
+ - rails/init.rb
48
+ - require.rb
49
+ - spec/acts_as_archive/base/destroy_spec.rb
50
+ - spec/acts_as_archive/base/restore_spec.rb
51
+ - spec/acts_as_archive/base/table_spec.rb
52
+ - spec/acts_as_archive/base_spec.rb
53
+ - spec/acts_as_archive/migration_spec.rb
54
+ - spec/db/config/database.mysql.yml
55
+ - spec/db/config/database.postgresql.yml
56
+ - spec/db/log/.gitignore
57
+ - spec/db/migrate/001_add_to_articles.rb
58
+ - spec/db/migrate_2/001_add_to_articles.rb
59
+ - spec/db/models/article.rb
60
+ - spec/spec.opts
61
+ - spec/spec_helper.rb
62
+ has_rdoc: true
63
+ homepage: http://github.com/sideshowbandana/acts_as_archive
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --charset=UTF-8
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project:
92
+ rubygems_version: 1.3.7
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Don't delete your records, move them to a different table
96
+ test_files:
97
+ - spec/spec_helper.rb
98
+ - spec/db/migrate_2/001_add_to_articles.rb
99
+ - spec/db/models/article.rb
100
+ - spec/db/migrate/001_add_to_articles.rb
101
+ - spec/acts_as_archive/base_spec.rb
102
+ - spec/acts_as_archive/migration_spec.rb
103
+ - spec/acts_as_archive/base/destroy_spec.rb
104
+ - spec/acts_as_archive/base/table_spec.rb
105
+ - spec/acts_as_archive/base/restore_spec.rb