undestroy 0.0.2 → 0.1.0
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/ARCH.md +13 -24
- data/README.md +72 -27
- data/lib/undestroy/binding.rb +1 -0
- data/lib/undestroy/binding/active_record.rb +53 -11
- data/lib/undestroy/binding/active_record/migration_statement.rb +118 -0
- data/lib/undestroy/binding/active_record/restorable.rb +28 -0
- data/lib/undestroy/config.rb +32 -8
- data/lib/undestroy/config/field.rb +20 -0
- data/lib/undestroy/restore.rb +44 -0
- data/lib/undestroy/version.rb +1 -1
- data/lib/undestroy/without_binding.rb +1 -0
- data/test/fixtures/ar.rb +8 -4
- data/test/fixtures/load_test/models1/test_model001.rb +2 -0
- data/test/fixtures/load_test/models1/test_model002.rb +2 -0
- data/test/fixtures/load_test/models1/test_module001.rb +2 -0
- data/test/fixtures/load_test/models1/test_module001/test_model003.rb +2 -0
- data/test/fixtures/load_test/models2/test_model001.rb +2 -0
- data/test/fixtures/load_test/models2/test_model002.rb +2 -0
- data/test/fixtures/load_test/models2/test_module001.rb +2 -0
- data/test/fixtures/load_test/models2/test_module001/test_model003.rb +2 -0
- data/test/helper.rb +12 -0
- data/test/helpers/model_loading.rb +20 -0
- data/test/integration/active_record_test.rb +50 -1
- data/test/irb.rb +2 -0
- data/test/unit/archive_test.rb +1 -1
- data/test/unit/binding/active_record/migration_statement_test.rb +306 -0
- data/test/unit/binding/active_record/restorable_test.rb +132 -0
- data/test/unit/binding/active_record_test.rb +187 -19
- data/test/unit/config/field_test.rb +86 -0
- data/test/unit/config_test.rb +114 -8
- data/test/unit/restore_test.rb +95 -0
- metadata +35 -3
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'assert'
|
2
|
+
|
3
|
+
module Undestroy::Binding::ActiveRecord::Restorable::Test
|
4
|
+
|
5
|
+
class Base < Undestroy::Test::Base
|
6
|
+
desc 'Binding::ActiveRecord::Restorable module'
|
7
|
+
subject { Undestroy::Binding::ActiveRecord::Restorable }
|
8
|
+
|
9
|
+
setup do
|
10
|
+
@ar = Undestroy::Test::ARMain
|
11
|
+
@receiver = Class.new(@ar) do
|
12
|
+
cattr_accessor :calls
|
13
|
+
self.calls = []
|
14
|
+
|
15
|
+
def destroy
|
16
|
+
calls << [:destroy]
|
17
|
+
self.freeze
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@receiver.table_name = "foobar"
|
21
|
+
@receiver.class_attribute :undestroy_model_binding, :instance_writer => false
|
22
|
+
@receiver.undestroy_model_binding = Undestroy::Binding::ActiveRecord.new(@receiver)
|
23
|
+
@receiver.connection.create_table :foobar do |t|
|
24
|
+
t.string :name
|
25
|
+
end
|
26
|
+
|
27
|
+
@restore = Class.new do
|
28
|
+
cattr_accessor :calls
|
29
|
+
self.calls = []
|
30
|
+
def initialize(*args)
|
31
|
+
calls << [:init, args]
|
32
|
+
end
|
33
|
+
def run
|
34
|
+
calls << [:run]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@receiver.undestroy_model_binding.config.internals[:restore] = @restore
|
38
|
+
end
|
39
|
+
|
40
|
+
teardown do
|
41
|
+
@receiver.connection.drop_table :foobar
|
42
|
+
end
|
43
|
+
|
44
|
+
def assert_ran_restore(object)
|
45
|
+
assert_equal [:init, [{ :target => object, :config => @receiver.undestroy_model_binding.config }]],
|
46
|
+
@restore.calls[0]
|
47
|
+
assert_equal [:run], @restore.calls[1]
|
48
|
+
assert_equal 2, @restore.calls.size
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class RestoreBaseMethods < Base
|
54
|
+
desc 'restore and restore__copy methods'
|
55
|
+
|
56
|
+
setup do
|
57
|
+
@receiver.send :include, subject
|
58
|
+
end
|
59
|
+
|
60
|
+
should "be instance methods" do
|
61
|
+
assert_respond_to :restore_copy, @receiver.new
|
62
|
+
assert_respond_to :restore, @receiver.new
|
63
|
+
end
|
64
|
+
|
65
|
+
should "create a config[:restore] instance passing self and the binding's config and call run on restore_copy" do
|
66
|
+
object = @receiver.new
|
67
|
+
object.restore_copy
|
68
|
+
|
69
|
+
assert_ran_restore(object)
|
70
|
+
|
71
|
+
# No Destroy
|
72
|
+
assert_equal 0, @receiver.calls.size
|
73
|
+
end
|
74
|
+
|
75
|
+
should "run restore_copy and then destroy when restore called" do
|
76
|
+
object = @receiver.new
|
77
|
+
object.restore
|
78
|
+
|
79
|
+
assert_ran_restore(object)
|
80
|
+
|
81
|
+
assert_equal [:destroy], @receiver.calls[0]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class RestoreRelationMethods < Base
|
86
|
+
desc 'restore_all method for relation'
|
87
|
+
|
88
|
+
setup do
|
89
|
+
@receiver.send :include, subject
|
90
|
+
end
|
91
|
+
|
92
|
+
should "include RelationExtensions on the singleton_class for @relation" do
|
93
|
+
assert_respond_to :restore_all, @receiver.send(:relation)
|
94
|
+
assert_includes subject::RelationExtensions, @receiver.send(:relation).singleton_class.ancestors
|
95
|
+
end
|
96
|
+
|
97
|
+
should "call restore on all items in the query" do
|
98
|
+
query = [@receiver.new, @receiver.new, @receiver.new]
|
99
|
+
relation = @receiver.send(:relation)
|
100
|
+
relation.instance_variable_set(:@records, query)
|
101
|
+
relation.instance_variable_set(:@loaded, true)
|
102
|
+
relation.restore_all
|
103
|
+
assert_equal [[:destroy], [:destroy], [:destroy]], @receiver.calls
|
104
|
+
end
|
105
|
+
|
106
|
+
should "reset the relation" do
|
107
|
+
relation = @receiver.send(:relation)
|
108
|
+
relation.instance_variable_set(:@records, [@receiver.new])
|
109
|
+
relation.instance_variable_set(:@loaded, true)
|
110
|
+
relation.restore_all
|
111
|
+
assert_equal [], relation.instance_variable_get(:@records)
|
112
|
+
end
|
113
|
+
|
114
|
+
should "return an array of restored records" do
|
115
|
+
records = [@receiver.new]
|
116
|
+
relation = @receiver.send(:relation)
|
117
|
+
relation.instance_variable_set(:@records, records)
|
118
|
+
relation.instance_variable_set(:@loaded, true)
|
119
|
+
assert_equal records, relation.restore_all
|
120
|
+
end
|
121
|
+
|
122
|
+
should "not be visible on other Relation instances" do
|
123
|
+
model = Class.new(@ar)
|
124
|
+
model.table_name = 'test'
|
125
|
+
@ar.connection.create_table :test
|
126
|
+
assert_not_respond_to :restore_all, model.send(:relation)
|
127
|
+
@ar.connection.drop_table :test
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
@@ -25,7 +25,15 @@ module Undestroy::Binding::ActiveRecord::Test
|
|
25
25
|
desc 'basic instance'
|
26
26
|
subject { Undestroy::Binding::ActiveRecord.new @model }
|
27
27
|
|
28
|
-
should have_accessors :config, :model
|
28
|
+
should have_accessors :config, :model, :active
|
29
|
+
|
30
|
+
should "have instance method :active? as alias for :active" do
|
31
|
+
obj = subject
|
32
|
+
obj.active = false
|
33
|
+
assert_equal false, obj.active?
|
34
|
+
obj.active = true
|
35
|
+
assert_equal true, obj.active?
|
36
|
+
end
|
29
37
|
end
|
30
38
|
|
31
39
|
class AddClassMethod < Base
|
@@ -36,20 +44,13 @@ module Undestroy::Binding::ActiveRecord::Test
|
|
36
44
|
undef_method :undestroy_model_binding if respond_to?(:undestroy_model_binding)
|
37
45
|
remove_possible_method :undestroy_model_binding=
|
38
46
|
class << self
|
39
|
-
undef_method :undestroy_model_binding
|
40
47
|
undef_method :undestroy
|
41
48
|
end
|
42
49
|
end
|
43
50
|
@model._destroy_callbacks = []
|
44
51
|
end
|
45
52
|
|
46
|
-
should "add
|
47
|
-
subject.add @model
|
48
|
-
assert_respond_to :undestroy_model_binding, @model
|
49
|
-
assert_respond_to :undestroy_model_binding=, @model
|
50
|
-
end
|
51
|
-
|
52
|
-
should "add undestroy class method to AR::Base initializing this binding" do
|
53
|
+
should "add undestroy class method to klass initializing this binding" do
|
53
54
|
subject.add @model
|
54
55
|
assert_respond_to :undestroy, @model
|
55
56
|
|
@@ -59,8 +60,31 @@ module Undestroy::Binding::ActiveRecord::Test
|
|
59
60
|
assert_equal Hash.new, @model.undestroy_model_binding.config.fields
|
60
61
|
end
|
61
62
|
|
62
|
-
should "
|
63
|
+
should "allow adding to other classes" do
|
64
|
+
new_model = Class.new(@model)
|
65
|
+
subject.add(new_model)
|
66
|
+
assert_respond_to :undestroy, new_model
|
67
|
+
assert_not_respond_to :undestroy, @model
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
class UndestroyExtensionMethod < AddClassMethod
|
73
|
+
desc 'added undestroy method'
|
74
|
+
|
75
|
+
setup do
|
63
76
|
subject.add @model
|
77
|
+
end
|
78
|
+
|
79
|
+
should "add class_attr called `undestroy_model_binding`" do
|
80
|
+
assert_not_respond_to :undestroy_model_binding, @model
|
81
|
+
assert_not_respond_to :undestroy_model_binding=, @model
|
82
|
+
@model.undestroy
|
83
|
+
assert_respond_to :undestroy_model_binding, @model
|
84
|
+
assert_respond_to :undestroy_model_binding=, @model
|
85
|
+
end
|
86
|
+
|
87
|
+
should "add before_destroy callback when undestroy called" do
|
64
88
|
archive_class = Undestroy::Test::Fixtures::Archive
|
65
89
|
@model.undestroy :internals => { :archive => archive_class }
|
66
90
|
|
@@ -75,20 +99,94 @@ module Undestroy::Binding::ActiveRecord::Test
|
|
75
99
|
end
|
76
100
|
|
77
101
|
should "only add callbacks once" do
|
78
|
-
subject.add @model
|
79
102
|
@model.undestroy
|
80
103
|
@model.undestroy
|
81
104
|
assert_equal 1, @model._destroy_callbacks.size
|
82
105
|
end
|
83
106
|
|
84
|
-
should "
|
85
|
-
|
86
|
-
|
87
|
-
assert_respond_to :
|
88
|
-
|
107
|
+
should "add archived class method" do
|
108
|
+
assert_not_respond_to :archived, @model
|
109
|
+
@model.undestroy
|
110
|
+
assert_respond_to :archived, @model
|
111
|
+
end
|
112
|
+
|
113
|
+
should "add archived method that returns configured target_class after undestroy configured" do
|
114
|
+
@model.undestroy
|
115
|
+
assert_equal @model.undestroy_model_binding.config.target_class, @model.archived
|
116
|
+
end
|
117
|
+
|
118
|
+
should "add restore method" do
|
119
|
+
assert_not_respond_to :restore, @model
|
120
|
+
@model.undestroy
|
121
|
+
assert_respond_to :restore, @model
|
122
|
+
end
|
123
|
+
|
124
|
+
should "add restore method that passes args to target_class find and then calls 'restore' on each record" do
|
125
|
+
@model.undestroy
|
126
|
+
fixture = Class.new do
|
127
|
+
cattr_accessor :calls, :return_val
|
128
|
+
self.calls = []
|
129
|
+
self.return_val = nil
|
130
|
+
|
131
|
+
def self.find(*args)
|
132
|
+
self.calls << [:find, args]
|
133
|
+
self.return_val
|
134
|
+
end
|
135
|
+
|
136
|
+
def restore
|
137
|
+
self.calls << [:restore]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
@model.undestroy_model_binding.config.target_class = fixture
|
142
|
+
|
143
|
+
fixture.return_val = fixture.new
|
144
|
+
@model.restore(:foo, :bar => :baz)
|
145
|
+
|
146
|
+
assert_equal [:find, [:foo, { :bar => :baz }]], fixture.calls[0]
|
147
|
+
assert_equal [:restore], fixture.calls[1]
|
148
|
+
|
149
|
+
fixture.calls = []
|
150
|
+
|
151
|
+
fixture.return_val = [fixture.new, fixture.new]
|
152
|
+
@model.restore
|
153
|
+
|
154
|
+
assert_equal [:restore], fixture.calls[1]
|
155
|
+
assert_equal [:restore], fixture.calls[2]
|
156
|
+
assert_nil fixture.calls[3]
|
157
|
+
end
|
158
|
+
|
159
|
+
should "add destroy! instance method that calls destroy without archival" do
|
160
|
+
@model.class_eval do
|
161
|
+
cattr_accessor :calls
|
162
|
+
self.calls = []
|
163
|
+
|
164
|
+
def destroy
|
165
|
+
calls << [:destroy, undestroy_model_binding.active]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
@model.undestroy
|
169
|
+
assert_respond_to :destroy!, @model.new
|
170
|
+
|
171
|
+
@model.new.destroy!
|
172
|
+
|
173
|
+
assert_equal [:destroy, false], @model.calls[0]
|
89
174
|
end
|
90
175
|
end
|
91
176
|
|
177
|
+
class LoadModelsClassMethod < Base
|
178
|
+
desc 'load_models class method'
|
179
|
+
include Undestroy::Test::Helpers::ModelLoading
|
180
|
+
|
181
|
+
should "force all models to load recursively in supplied path" do
|
182
|
+
path = Undestroy::Test.fixtures_path('load_test', 'models1')
|
183
|
+
assert_loads_models(path) do
|
184
|
+
subject.load_models(path)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
92
190
|
class InitMethod < Base
|
93
191
|
desc 'init method'
|
94
192
|
|
@@ -111,24 +209,36 @@ module Undestroy::Binding::ActiveRecord::Test
|
|
111
209
|
assert_instance_of Undestroy::Config, binding.config
|
112
210
|
end
|
113
211
|
|
212
|
+
should "default :active attr to true" do
|
213
|
+
assert subject.new(@model).active, "Should be active by default"
|
214
|
+
end
|
215
|
+
|
114
216
|
should "merge config options onto global config" do
|
115
217
|
Undestroy::Config.configure do |config|
|
116
218
|
config.fields = {}
|
117
219
|
end
|
118
220
|
assert_equal Hash.new, subject.new(@model).config.fields
|
119
221
|
assert_not_equal subject.new(@model).config.object_id, Undestroy::Config.config.object_id
|
222
|
+
assert_not_equal subject.new(@model).config.fields.object_id, Undestroy::Config.config.fields.object_id
|
120
223
|
assert_equal({ :foo => :bar }, subject.new(@model, :fields => { :foo => :bar }).config.fields)
|
121
224
|
end
|
122
225
|
|
226
|
+
should "accept block and pass config object to it" do
|
227
|
+
binding = subject.new @model, {} do |config|
|
228
|
+
config.fields = {}
|
229
|
+
end
|
230
|
+
assert_equal Hash.new, binding.config.fields
|
231
|
+
end
|
232
|
+
|
123
233
|
should "set config.source_class to value of model" do
|
124
234
|
binding = subject.new(@model)
|
125
235
|
assert_equal @model, binding.config.source_class
|
126
236
|
end
|
127
237
|
|
128
|
-
should "default :table_name to '
|
238
|
+
should "default :table_name to '{config.prefix}{source.table_name}'" do
|
129
239
|
@model.table_name = :foobar
|
130
|
-
binding = subject.new(@model)
|
131
|
-
assert_equal '
|
240
|
+
binding = subject.new(@model, :prefix => "prefix_archive_")
|
241
|
+
assert_equal 'prefix_archive_foobar', binding.config.table_name
|
132
242
|
end
|
133
243
|
|
134
244
|
should "create a target_class if none provided" do
|
@@ -162,6 +272,24 @@ module Undestroy::Binding::ActiveRecord::Test
|
|
162
272
|
assert_equal 'tmp/alt.db', binding.config.target_class.connection_config[:database]
|
163
273
|
end
|
164
274
|
|
275
|
+
should "create class_attribute undestroy_model_binding with no instance writer" do
|
276
|
+
binding = subject.new(@model)
|
277
|
+
assert_respond_to :undestroy_model_binding, binding.config.target_class
|
278
|
+
assert_respond_to :undestroy_model_binding=, binding.config.target_class
|
279
|
+
assert_respond_to :undestroy_model_binding, binding.config.target_class.new
|
280
|
+
assert_not_respond_to :undestroy_model_binding=, binding.config.target_class.new
|
281
|
+
end
|
282
|
+
|
283
|
+
should "set self to the undestroy_model_binding on the target_class" do
|
284
|
+
binding = subject.new(@model)
|
285
|
+
assert_equal binding, binding.config.target_class.undestroy_model_binding
|
286
|
+
end
|
287
|
+
|
288
|
+
should "include Restorable mixin" do
|
289
|
+
binding = subject.new(@model)
|
290
|
+
assert_includes Undestroy::Binding::ActiveRecord::Restorable, binding.config.target_class.ancestors
|
291
|
+
end
|
292
|
+
|
165
293
|
end
|
166
294
|
|
167
295
|
class BeforeDestroy < Base
|
@@ -184,5 +312,45 @@ module Undestroy::Binding::ActiveRecord::Test
|
|
184
312
|
end
|
185
313
|
end
|
186
314
|
|
315
|
+
class PrefixTableNameMethod < Base
|
316
|
+
desc 'prefix_table_name method'
|
317
|
+
subject { @binding ||= Undestroy::Binding::ActiveRecord.new(@model) }
|
318
|
+
|
319
|
+
should "return {config.prefix}{source.table_name}" do
|
320
|
+
subject.config.prefix = "archive_prefix_"
|
321
|
+
assert_equal "archive_prefix_foo", subject.prefix_table_name("foo")
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
class DeactivatedMethod < Base
|
326
|
+
desc 'deactivated method'
|
327
|
+
subject { @binding ||= Undestroy::Binding::ActiveRecord.new(@model) }
|
328
|
+
|
329
|
+
should "set activated to false and yield to block" do
|
330
|
+
called = false
|
331
|
+
block = proc {
|
332
|
+
called = true
|
333
|
+
assert !subject.active, "Binding should be deactivated"
|
334
|
+
}
|
335
|
+
subject.deactivated(&block)
|
336
|
+
assert called, "Block should be called"
|
337
|
+
end
|
338
|
+
|
339
|
+
should "always set activated back to original value" do
|
340
|
+
block = proc {}
|
341
|
+
raise_block = proc { raise "FOO" }
|
342
|
+
|
343
|
+
subject.deactivated(&block)
|
344
|
+
assert subject.active, "Should be activated"
|
345
|
+
|
346
|
+
assert_raises { subject.deactivated(&raise_block) }
|
347
|
+
assert subject.active, "Should be activated"
|
348
|
+
|
349
|
+
subject.active = false
|
350
|
+
subject.deactivated(&block)
|
351
|
+
assert !subject.active, "Should not be activated"
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
187
355
|
end
|
188
356
|
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'assert'
|
2
|
+
|
3
|
+
class Undestroy::Config::Field::Test
|
4
|
+
|
5
|
+
class Base < Undestroy::Test::Base
|
6
|
+
desc 'Undestroy::Config::Field class'
|
7
|
+
subject { @subject_class }
|
8
|
+
|
9
|
+
setup do
|
10
|
+
@subject_class = Undestroy::Config::Field
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class BasicInstance < Base
|
15
|
+
desc 'basic instance'
|
16
|
+
subject { @subject_class.new :foo, :foo, 'foo' }
|
17
|
+
|
18
|
+
should have_accessors(:name, :type, :raw_value)
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class InitMethod < Base
|
23
|
+
desc 'initialize method'
|
24
|
+
|
25
|
+
should "have 2 required params" do
|
26
|
+
assert_raises(ArgumentError) { subject.new }
|
27
|
+
assert_not_raises { subject.new :foo, :bar, 'val' }
|
28
|
+
assert_not_raises { subject.new(:foo, :bar) { 'val' } }
|
29
|
+
end
|
30
|
+
|
31
|
+
should "store name in name attr" do
|
32
|
+
obj = subject.new :field, :string, 'val'
|
33
|
+
assert_equal :field, obj.name
|
34
|
+
end
|
35
|
+
|
36
|
+
should "store type in type attr" do
|
37
|
+
obj = subject.new :field, :string, 'val'
|
38
|
+
assert_equal :string, obj.type
|
39
|
+
end
|
40
|
+
|
41
|
+
should "store value in raw_value attr if present" do
|
42
|
+
obj = subject.new :field, :string, 'val'
|
43
|
+
assert_equal 'val', obj.raw_value
|
44
|
+
end
|
45
|
+
|
46
|
+
should "store optional block in raw_value" do
|
47
|
+
block = proc { "foo" }
|
48
|
+
obj = subject.new :field, :string, 'val', &block
|
49
|
+
assert_equal block, obj.raw_value
|
50
|
+
end
|
51
|
+
|
52
|
+
should "raise if a block and a value are not passed" do
|
53
|
+
assert_raises(ArgumentError) { subject.new :field, :string }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ValueMethod < Base
|
58
|
+
desc 'value method'
|
59
|
+
|
60
|
+
should "return raw_value if not callable" do
|
61
|
+
obj = subject.new :field, :string, 'val'
|
62
|
+
assert_equal 'val', obj.value
|
63
|
+
end
|
64
|
+
|
65
|
+
should "evaluate raw_value with passed args" do
|
66
|
+
block = proc { |args| args }
|
67
|
+
block2 = proc { |arg1, arg2| [arg1, arg2] }
|
68
|
+
obj = subject.new :field, :string, &block
|
69
|
+
obj2 = subject.new :field, :string, &block2
|
70
|
+
assert_equal 1, obj.value(1)
|
71
|
+
assert_equal [1, 2], obj2.value(1, 2)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class SortOperator < Base
|
76
|
+
desc 'sort operator'
|
77
|
+
|
78
|
+
should "sort by alphanumeric field name" do
|
79
|
+
names = ["orange", :apple, "banana", "orange2"]
|
80
|
+
fields = names.collect { |name| Undestroy::Config::Field.new(name, :type, 'val') }
|
81
|
+
assert_equal names.collect(&:to_s).sort, fields.sort.collect { |f| f.name.to_s }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|