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 +18 -0
- data/README.markdown +109 -0
- data/Rakefile +48 -0
- data/acts_as_archive.gemspec +29 -0
- data/bin/acts_as_archive +6 -0
- data/init.rb +1 -0
- data/lib/acts_as_archive.rb +2 -0
- data/lib/acts_as_archive/base.rb +34 -0
- data/lib/acts_as_archive/base/destroy.rb +66 -0
- data/lib/acts_as_archive/base/restore.rb +35 -0
- data/lib/acts_as_archive/base/table.rb +65 -0
- data/lib/acts_as_archive/migration.rb +47 -0
- data/rails/init.rb +16 -0
- data/spec/acts_as_archive/base/destroy_spec.rb +98 -0
- data/spec/acts_as_archive/base/restore_spec.rb +58 -0
- data/spec/acts_as_archive/base/table_spec.rb +74 -0
- data/spec/acts_as_archive/base_spec.rb +24 -0
- data/spec/acts_as_archive/migration_spec.rb +37 -0
- data/spec/db/config/database.yml +7 -0
- data/spec/db/migrate/001_add_to_articles.rb +9 -0
- data/spec/db/migrate_2/001_add_to_articles.rb +9 -0
- data/spec/db/models/article.rb +3 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +100 -0
- metadata +78 -0
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
|
data/bin/acts_as_archive
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/rails/init"
|
@@ -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
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
+
|