undestroy 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/ARCH.md +84 -0
- data/Gemfile +2 -1
- data/LICENSE +1 -1
- data/README.md +58 -48
- data/lib/undestroy.rb +3 -4
- data/lib/undestroy/archive.rb +36 -0
- data/lib/undestroy/binding.rb +8 -0
- data/lib/undestroy/binding/active_record.rb +63 -0
- data/lib/undestroy/config.rb +54 -0
- data/lib/undestroy/transfer.rb +22 -0
- data/lib/undestroy/version.rb +2 -1
- data/lib/undestroy/with_binding.rb +5 -0
- data/lib/undestroy/without_binding.rb +10 -0
- data/test/fixtures/active_record_models.rb +42 -0
- data/test/fixtures/ar.rb +41 -0
- data/test/fixtures/archive.rb +21 -0
- data/test/helper.rb +42 -1
- data/test/integration/active_record_test.rb +180 -0
- data/test/unit/archive_test.rb +115 -0
- data/test/unit/binding/active_record_test.rb +188 -0
- data/test/unit/config_test.rb +146 -0
- data/test/unit/transfer_test.rb +65 -0
- data/undestroy.gemspec +2 -1
- metadata +29 -6
- data/test/unit/undestroy_test.rb +0 -10
@@ -0,0 +1,42 @@
|
|
1
|
+
module Undestroy::Test::Fixtures::ActiveRecordModels
|
2
|
+
|
3
|
+
class Blog < Undestroy::Test::ARMain
|
4
|
+
undestroy
|
5
|
+
has_many :posts, :dependent => :destroy
|
6
|
+
end
|
7
|
+
|
8
|
+
class Post < Undestroy::Test::ARMain
|
9
|
+
undestroy
|
10
|
+
belongs_to :blog
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.add_blog_tables(model)
|
14
|
+
model.connection.create_table :blogs do |t|
|
15
|
+
t.string :name
|
16
|
+
end
|
17
|
+
model.connection.create_table :archive_blogs do |t|
|
18
|
+
t.string :name
|
19
|
+
t.datetime :deleted_at
|
20
|
+
end
|
21
|
+
|
22
|
+
model.connection.create_table :posts do |t|
|
23
|
+
t.integer :blog_id
|
24
|
+
t.string :title
|
25
|
+
t.text :body
|
26
|
+
end
|
27
|
+
model.connection.create_table :archive_posts do |t|
|
28
|
+
t.datetime :deleted_at
|
29
|
+
t.integer :blog_id
|
30
|
+
t.string :title
|
31
|
+
t.text :body
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.remove_blog_tables(model)
|
36
|
+
model.connection.drop_table :blogs
|
37
|
+
model.connection.drop_table :archive_blogs
|
38
|
+
model.connection.drop_table :posts
|
39
|
+
model.connection.drop_table :archive_posts
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/test/fixtures/ar.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
class Undestroy::Test::Fixtures::ARFixture
|
3
|
+
attr_accessor :attributes
|
4
|
+
attr_reader :saved
|
5
|
+
|
6
|
+
alias :saved? :saved
|
7
|
+
|
8
|
+
def initialize(attributes={})
|
9
|
+
@saved = false
|
10
|
+
self.attributes = attributes.dup
|
11
|
+
self.attributes.delete(:id)
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](key)
|
15
|
+
self.attributes[key.to_sym]
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(key, val)
|
19
|
+
self.attributes[key.to_sym] = val
|
20
|
+
end
|
21
|
+
|
22
|
+
# Method missing won't catch this one
|
23
|
+
def id
|
24
|
+
self.attributes[:id]
|
25
|
+
end
|
26
|
+
|
27
|
+
def save
|
28
|
+
@saved = true
|
29
|
+
end
|
30
|
+
|
31
|
+
# Shortcut to build an instance for testing purposes.
|
32
|
+
def self.construct(attributes={})
|
33
|
+
self.new.tap do |fixture|
|
34
|
+
attributes.each do |field, value|
|
35
|
+
fixture[field] = value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
class Undestroy::Test::Fixtures::Archive
|
3
|
+
def initialize(args)
|
4
|
+
@@data[:args] = args
|
5
|
+
end
|
6
|
+
|
7
|
+
def run
|
8
|
+
@@data[:calls] << [:run]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.data
|
12
|
+
@@data
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.reset
|
16
|
+
@@data = { :calls => [] }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Undestroy::Test::Fixtures::Archive.reset
|
21
|
+
|
data/test/helper.rb
CHANGED
@@ -1,2 +1,43 @@
|
|
1
|
-
require '
|
1
|
+
require 'undestroy'
|
2
|
+
|
3
|
+
ActiveRecord::Base.configurations = {
|
4
|
+
'main' => {
|
5
|
+
:adapter => 'sqlite3',
|
6
|
+
:database => 'tmp/main.db'
|
7
|
+
},
|
8
|
+
'alt' => {
|
9
|
+
:adapter => 'sqlite3',
|
10
|
+
:database => 'tmp/alt.db'
|
11
|
+
},
|
12
|
+
}
|
13
|
+
|
14
|
+
module Undestroy::Test
|
15
|
+
|
16
|
+
class Base < Assert::Context
|
17
|
+
|
18
|
+
teardown_once do
|
19
|
+
`rm -f tmp/*.db`
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
ActiveRecord::Base.establish_connection 'main'
|
25
|
+
class ARMain < ActiveRecord::Base
|
26
|
+
self.abstract_class = true
|
27
|
+
end
|
28
|
+
|
29
|
+
class ARAlt < ActiveRecord::Base
|
30
|
+
self.abstract_class = true
|
31
|
+
establish_connection 'alt'
|
32
|
+
end
|
33
|
+
|
34
|
+
module Integration
|
35
|
+
end
|
36
|
+
|
37
|
+
module Fixtures
|
38
|
+
autoload :ActiveRecordModels, 'test/fixtures/active_record_models'
|
39
|
+
autoload :ARFixture, 'test/fixtures/ar'
|
40
|
+
autoload :Archive, 'test/fixtures/archive'
|
41
|
+
end
|
42
|
+
end
|
2
43
|
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'assert'
|
2
|
+
|
3
|
+
module Undestroy::Test::Integration::ActiveRecordTest
|
4
|
+
|
5
|
+
class Base < Undestroy::Test::Base
|
6
|
+
desc 'ActiveRecord integration'
|
7
|
+
|
8
|
+
setup do
|
9
|
+
@ar = Undestroy::Test::ARMain
|
10
|
+
@ar_alt = Undestroy::Test::ARAlt
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class ActiveRecordExtension < Base
|
16
|
+
desc 'extensions'
|
17
|
+
|
18
|
+
should "add extensions to AR" do
|
19
|
+
assert_respond_to :undestroy_model_binding, ActiveRecord::Base
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class BasicModelWithoutUndestroy < Base
|
24
|
+
desc 'basic model without Undestroy'
|
25
|
+
|
26
|
+
setup do
|
27
|
+
@ar.connection.create_table :basic_model_table do |t|
|
28
|
+
t.string :name
|
29
|
+
end
|
30
|
+
@model = Class.new(@ar)
|
31
|
+
@model.table_name = 'basic_model_table'
|
32
|
+
end
|
33
|
+
|
34
|
+
teardown do
|
35
|
+
@ar.connection.drop_table :basic_model_table
|
36
|
+
end
|
37
|
+
|
38
|
+
should "successfully traverse the model lifecycle" do
|
39
|
+
record = @model.create!(:name => 'bar')
|
40
|
+
assert @model.first
|
41
|
+
assert_not_raises { record.destroy }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class BasicModel < Base
|
46
|
+
desc 'basic model with Undestroy'
|
47
|
+
|
48
|
+
setup do
|
49
|
+
@ar.connection.create_table :basic_model_table do |t|
|
50
|
+
t.string :name
|
51
|
+
end
|
52
|
+
@ar.connection.create_table :archive_basic_model_table do |t|
|
53
|
+
t.string :name
|
54
|
+
t.datetime :deleted_at
|
55
|
+
end
|
56
|
+
@model = Class.new(@ar)
|
57
|
+
@model.table_name = 'basic_model_table'
|
58
|
+
end
|
59
|
+
|
60
|
+
teardown do
|
61
|
+
@ar.connection.drop_table :basic_model_table
|
62
|
+
@ar.connection.drop_table :archive_basic_model_table
|
63
|
+
end
|
64
|
+
|
65
|
+
should "create an archive record on destroy" do
|
66
|
+
@model.undestroy
|
67
|
+
target_class = @model.undestroy_model_binding.config.target_class
|
68
|
+
|
69
|
+
@model.create(:name => "foo")
|
70
|
+
original = @model.first
|
71
|
+
original.destroy
|
72
|
+
archive = target_class.first
|
73
|
+
|
74
|
+
assert original
|
75
|
+
assert archive
|
76
|
+
assert_equal original.id, archive.id
|
77
|
+
assert_equal 'foo', archive.name
|
78
|
+
assert_kind_of Time, archive.deleted_at
|
79
|
+
assert (Time.now - archive.deleted_at) < 1.second
|
80
|
+
assert_equal 0, @model.all.size
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class BasicModelWithDifferentBase < Base
|
85
|
+
desc 'basic model with Undestroy and alternate abstract class'
|
86
|
+
|
87
|
+
setup do
|
88
|
+
@ar.connection.create_table :basic_model_table do |t|
|
89
|
+
t.string :name
|
90
|
+
end
|
91
|
+
@ar_alt.connection.create_table :archive_basic_model_table do |t|
|
92
|
+
t.string :name
|
93
|
+
t.datetime :deleted_at
|
94
|
+
end
|
95
|
+
@model = Class.new(@ar)
|
96
|
+
@model.table_name = 'basic_model_table'
|
97
|
+
|
98
|
+
@model.undestroy :abstract_class => @ar_alt
|
99
|
+
@target_class = @model.undestroy_model_binding.config.target_class
|
100
|
+
end
|
101
|
+
|
102
|
+
teardown do
|
103
|
+
@ar.connection.drop_table :basic_model_table
|
104
|
+
@ar_alt.connection.drop_table :archive_basic_model_table
|
105
|
+
end
|
106
|
+
|
107
|
+
should "create an archive record on destroy" do
|
108
|
+
@model.create!(:name => "bar")
|
109
|
+
original = @model.first
|
110
|
+
original.destroy
|
111
|
+
archive = @target_class.first
|
112
|
+
|
113
|
+
assert_not @model.first
|
114
|
+
assert original
|
115
|
+
assert archive
|
116
|
+
assert_equal original.id, archive.id
|
117
|
+
assert_equal 'bar', archive.name
|
118
|
+
assert_kind_of Time, archive.deleted_at
|
119
|
+
assert (Time.now - archive.deleted_at) < 1.second
|
120
|
+
assert_equal 1, @target_class.all.size
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class DependentDestroy < Base
|
125
|
+
desc 'model with dependent undestroy enabled models'
|
126
|
+
|
127
|
+
setup do
|
128
|
+
@fixtures = Undestroy::Test::Fixtures::ActiveRecordModels
|
129
|
+
|
130
|
+
@blog = @fixtures::Blog
|
131
|
+
@post = @fixtures::Post
|
132
|
+
@fixtures.add_blog_tables(@blog)
|
133
|
+
|
134
|
+
@archive_blog = @blog.undestroy_model_binding.config.target_class
|
135
|
+
@archive_post = @post.undestroy_model_binding.config.target_class
|
136
|
+
end
|
137
|
+
|
138
|
+
teardown do
|
139
|
+
@fixtures.remove_blog_tables(@blog)
|
140
|
+
end
|
141
|
+
|
142
|
+
should "archive orphan blog" do
|
143
|
+
blog = @blog.create!(:name => "Foo Blog")
|
144
|
+
assert @blog.first
|
145
|
+
blog.destroy
|
146
|
+
assert_not @blog.first
|
147
|
+
assert @archive_blog.first
|
148
|
+
assert_equal "Foo Blog", @archive_blog.first.name
|
149
|
+
end
|
150
|
+
|
151
|
+
should "archive post" do
|
152
|
+
blog = @blog.create!(:name => "Bar Blog")
|
153
|
+
post = @post.create!(:title => "Foo Post", :body => "text", :blog => blog)
|
154
|
+
assert blog and post
|
155
|
+
post.destroy
|
156
|
+
archive = @archive_post.first
|
157
|
+
|
158
|
+
assert_not @post.first
|
159
|
+
assert archive
|
160
|
+
assert_equal post.id, archive.id
|
161
|
+
assert_equal "text", archive.body
|
162
|
+
assert_equal "Foo Post", archive.title
|
163
|
+
assert_equal blog.id, archive.blog_id
|
164
|
+
end
|
165
|
+
|
166
|
+
should "remove all posts and archive them when the blog is destroyed" do
|
167
|
+
blog = @blog.create!(:name => "My Verbose Blog")
|
168
|
+
posts = (1..10).collect do |i|
|
169
|
+
@post.create!(:title => "Article #{i}", :body => "Some text", :blog => blog)
|
170
|
+
end
|
171
|
+
blog.destroy
|
172
|
+
|
173
|
+
assert_equal 0, @blog.all.size
|
174
|
+
assert_equal 0, @post.all.size
|
175
|
+
assert_equal 1, @archive_blog.all.size
|
176
|
+
assert_equal 10, @archive_post.all.size
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'assert'
|
2
|
+
|
3
|
+
module Undestroy::Archive::Test
|
4
|
+
class Base < Undestroy::Test::Base
|
5
|
+
subject { Undestroy::Archive }
|
6
|
+
desc 'Undestroy::Archive class'
|
7
|
+
|
8
|
+
setup do
|
9
|
+
@default_init = {
|
10
|
+
:source => Undestroy::Test::Fixtures::ARFixture.construct(:id => 1, :name => "Foo"),
|
11
|
+
:config => Undestroy::Config.new
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def archive_instance(args={})
|
16
|
+
Undestroy::Archive.new(@default_init.merge(args))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class BasicInstance < Base
|
21
|
+
subject { archive_instance }
|
22
|
+
desc 'basic instance'
|
23
|
+
|
24
|
+
should have_accessors :source, :config, :transfer
|
25
|
+
end
|
26
|
+
|
27
|
+
class InitMethod < Base
|
28
|
+
desc 'initialize method'
|
29
|
+
|
30
|
+
should "require :source and :config options in hash argument" do
|
31
|
+
assert_raises(ArgumentError) { subject.new }
|
32
|
+
assert_raises(ArgumentError) { subject.new :source => nil }
|
33
|
+
assert_raises(ArgumentError) { subject.new :config => nil }
|
34
|
+
end
|
35
|
+
|
36
|
+
should "set the source to source attr" do
|
37
|
+
obj = archive_instance :source => "foo"
|
38
|
+
assert_equal "foo", obj.source
|
39
|
+
end
|
40
|
+
|
41
|
+
should "set the config to config attr" do
|
42
|
+
obj = archive_instance :config => "foo"
|
43
|
+
assert_equal "foo", obj.config
|
44
|
+
end
|
45
|
+
|
46
|
+
should "set optional :transfer to transfer attr" do
|
47
|
+
obj = archive_instance :transfer => "foo"
|
48
|
+
assert_equal "foo", obj.transfer
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class TransferMethod < BasicInstance
|
53
|
+
desc 'transfer method'
|
54
|
+
|
55
|
+
setup do
|
56
|
+
@archive = subject
|
57
|
+
@archive.config.target_class = Undestroy::Test::Fixtures::ARFixture
|
58
|
+
end
|
59
|
+
|
60
|
+
should "return instance of config.internals[:transfer]" do
|
61
|
+
assert_instance_of @archive.config.internals[:transfer], @archive.transfer
|
62
|
+
end
|
63
|
+
|
64
|
+
should "cache the created instance" do
|
65
|
+
assert_equal @archive.transfer.object_id, @archive.transfer.object_id
|
66
|
+
end
|
67
|
+
|
68
|
+
should "set fields on instance to config.fields.merge(source.attributes) with evaled procs" do
|
69
|
+
target = @archive.transfer.target
|
70
|
+
assert_instance_of @archive.config.target_class, @archive.transfer.target
|
71
|
+
assert_equal 1, target.attributes[:id]
|
72
|
+
assert_equal "Foo", target.attributes[:name]
|
73
|
+
assert_instance_of Time, target.attributes[:deleted_at]
|
74
|
+
end
|
75
|
+
|
76
|
+
should "eval lambdas with source instance as argument" do
|
77
|
+
val = nil
|
78
|
+
@archive.config.fields[:test] = proc { |arg| val = arg; "FOO" }
|
79
|
+
target = @archive.transfer.target
|
80
|
+
assert_equal @archive.source, val
|
81
|
+
assert_equal "FOO", target.attributes[:test]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class RunMethod < Base
|
86
|
+
desc 'run method'
|
87
|
+
|
88
|
+
should "exist" do
|
89
|
+
assert_respond_to :run, archive_instance
|
90
|
+
end
|
91
|
+
|
92
|
+
should "call run on the transfer model" do
|
93
|
+
foo = Class.new do
|
94
|
+
@@called = false
|
95
|
+
def initialize(options={})
|
96
|
+
end
|
97
|
+
|
98
|
+
def run
|
99
|
+
@@called = true
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.called
|
103
|
+
@@called
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
archive = archive_instance(:transfer => foo.new)
|
108
|
+
|
109
|
+
assert !foo.called
|
110
|
+
archive.run
|
111
|
+
assert foo.called
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|