mongoid_archival 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.
- checksums.yaml +7 -0
- data/LICENSE +24 -0
- data/README.md +245 -0
- data/lib/mongoid/archivable.rb +158 -0
- data/lib/mongoid/archivable/configuration.rb +17 -0
- data/lib/mongoid/archivable/depending.rb +57 -0
- data/lib/mongoid/archivable/protected.rb +38 -0
- data/lib/mongoid/archivable/version.rb +7 -0
- data/lib/mongoid_archival.rb +3 -0
- data/perf/scope.rb +65 -0
- data/spec/app/models/address.rb +71 -0
- data/spec/app/models/appointment.rb +7 -0
- data/spec/app/models/archivable_phone.rb +25 -0
- data/spec/app/models/archivable_post.rb +66 -0
- data/spec/app/models/author.rb +7 -0
- data/spec/app/models/fish.rb +10 -0
- data/spec/app/models/person.rb +21 -0
- data/spec/app/models/phone.rb +11 -0
- data/spec/app/models/relations.rb +246 -0
- data/spec/app/models/sport.rb +5 -0
- data/spec/app/models/tag.rb +6 -0
- data/spec/app/models/title.rb +4 -0
- data/spec/mongoid/archive_spec.rb +341 -0
- data/spec/mongoid/configuration_spec.rb +59 -0
- data/spec/mongoid/document_spec.rb +21 -0
- data/spec/mongoid/nested_attributes_spec.rb +164 -0
- data/spec/mongoid/protected_spec.rb +44 -0
- data/spec/mongoid/restore_spec.rb +144 -0
- data/spec/mongoid/scopes_spec.rb +117 -0
- data/spec/mongoid/validatable/uniqueness_spec.rb +77 -0
- data/spec/spec_helper.rb +43 -0
- metadata +141 -0
@@ -0,0 +1,341 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Mongoid::Archivable#archive' do
|
4
|
+
describe '#archive' do
|
5
|
+
|
6
|
+
context 'when the document is a root' do
|
7
|
+
|
8
|
+
let(:post) do
|
9
|
+
ArchivablePost.create(title: 'testing')
|
10
|
+
end
|
11
|
+
|
12
|
+
before do
|
13
|
+
post.archive
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:raw) do
|
17
|
+
ArchivablePost.collection.find(_id: post.id).first
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'archives the document' do
|
21
|
+
expect(raw['archived_at']).to be_within(1).of(Time.now)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'is still marked as persisted' do
|
25
|
+
expect(post.persisted?).to eq(true)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does not return the document in a find' do
|
29
|
+
expect(ArchivablePost.find(post.id)).to eq post
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'executes the before archive callbacks' do
|
33
|
+
expect(post.before_archive_called).to be_truthy
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'executes the after archive callbacks' do
|
37
|
+
expect(post.after_archive_called).to be_truthy
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# context 'when the document is embedded' do
|
42
|
+
#
|
43
|
+
# let(:person) do
|
44
|
+
# Person.create
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# let(:phone) do
|
48
|
+
# person.archivable_phones.create(number: '911')
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# before do
|
52
|
+
# phone.archive
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# let(:raw) do
|
56
|
+
# Person.collection.find(_id: person.id).first
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# it 'archives the document' do
|
60
|
+
# expect(raw['archivable_phones'].first['archived_at']).to be_within(1).of(Time.now)
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# it 'does not return the document in a find' do
|
64
|
+
# expect {
|
65
|
+
# person.archivable_phones.find(phone.id)
|
66
|
+
# }.to raise_error(Mongoid::Errors::DocumentNotFound)
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# it 'does not include the document in the relation' do
|
70
|
+
# expect(person.archivable_phones.scoped).to be_empty
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# it 'executes the before archive callbacks' do
|
74
|
+
# expect(phone.before_archive_called).to be_truthy
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# it 'executes the after archive callbacks' do
|
78
|
+
# expect(phone.after_archive_called).to be_truthy
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
|
82
|
+
context 'when the document has a dependent: :archive relation' do
|
83
|
+
|
84
|
+
let(:post) do
|
85
|
+
ArchivablePost.create(title: 'test')
|
86
|
+
end
|
87
|
+
|
88
|
+
let!(:fish) do
|
89
|
+
post.fish = Fish.create
|
90
|
+
end
|
91
|
+
|
92
|
+
before do
|
93
|
+
post.archive
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'cascades the dependent option' do
|
97
|
+
expect(fish.reload.archived_at).to be_a(Time)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when the document has a dependent: :destroy relation' do
|
102
|
+
|
103
|
+
let(:post) do
|
104
|
+
ArchivablePost.create(title: 'test')
|
105
|
+
end
|
106
|
+
|
107
|
+
let!(:author) do
|
108
|
+
post.authors.create(name: 'poe')
|
109
|
+
end
|
110
|
+
|
111
|
+
before do
|
112
|
+
post.archive
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'ignores dependent :destroy option' do
|
116
|
+
expect(author.reload).to be_a(Author)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'when the document has a dependent: :restrict relation' do
|
121
|
+
|
122
|
+
let(:post) do
|
123
|
+
ArchivablePost.create(title: 'test')
|
124
|
+
end
|
125
|
+
|
126
|
+
let!(:title) do
|
127
|
+
post.titles.create
|
128
|
+
end
|
129
|
+
|
130
|
+
before do
|
131
|
+
begin
|
132
|
+
post.archive
|
133
|
+
rescue Mongoid::Errors::DeleteRestriction
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'does not archive the document' do
|
138
|
+
expect(post).not_to be_archived
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe '#archive_without_callbacks' do
|
144
|
+
|
145
|
+
context 'when the document is a root' do
|
146
|
+
|
147
|
+
let(:post) do
|
148
|
+
ArchivablePost.create(title: 'testing')
|
149
|
+
end
|
150
|
+
|
151
|
+
before do
|
152
|
+
post.archive_without_callbacks
|
153
|
+
end
|
154
|
+
|
155
|
+
let(:raw) do
|
156
|
+
ArchivablePost.collection.find(_id: post.id).first
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'archives the document' do
|
160
|
+
expect(raw['archived_at']).to be_within(1).of(Time.now)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'does not return the document in a find' do
|
164
|
+
expect { ArchivablePost.current.find(post.id) }.to raise_error(Mongoid::Errors::DocumentNotFound)
|
165
|
+
expect { ArchivablePost.find(post.id) }.to_not raise_error(Mongoid::Errors::DocumentNotFound)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# context 'when the document is embedded' do
|
170
|
+
#
|
171
|
+
# let(:person) do
|
172
|
+
# Person.create
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# let(:phone) do
|
176
|
+
# person.archivable_phones.create(number: '911')
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# before do
|
180
|
+
# phone.archive_without_callbacks
|
181
|
+
# end
|
182
|
+
#
|
183
|
+
# let(:raw) do
|
184
|
+
# Person.collection.find(_id: person.id).first
|
185
|
+
# end
|
186
|
+
#
|
187
|
+
# it 'archives the document' do
|
188
|
+
# expect(raw['archivable_phones'].first['archived_at']).to be_within(1).of(Time.now)
|
189
|
+
# end
|
190
|
+
#
|
191
|
+
# it 'does not return the document in a find' do
|
192
|
+
# expect {
|
193
|
+
# person.archivable_phones.find(phone.id)
|
194
|
+
# }.to raise_error(Mongoid::Errors::DocumentNotFound)
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# it 'does not include the document in the relation' do
|
198
|
+
# expect(person.archivable_phones.scoped).to be_empty
|
199
|
+
# end
|
200
|
+
# end
|
201
|
+
|
202
|
+
context 'when the document has a dependent relation' do
|
203
|
+
|
204
|
+
let(:post) do
|
205
|
+
ArchivablePost.create(title: 'test')
|
206
|
+
end
|
207
|
+
|
208
|
+
let!(:author) do
|
209
|
+
post.authors.create(name: 'poe')
|
210
|
+
end
|
211
|
+
|
212
|
+
before do
|
213
|
+
post.archive_without_callbacks
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'does not cascade the dependent option' do
|
217
|
+
expect {
|
218
|
+
author.reload
|
219
|
+
}.to_not raise_error(Mongoid::Errors::DocumentNotFound)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'when the document has a dependent: :restrict relation' do
|
224
|
+
|
225
|
+
let(:post) do
|
226
|
+
ArchivablePost.create(title: 'test')
|
227
|
+
end
|
228
|
+
|
229
|
+
let!(:title) do
|
230
|
+
post.titles.create
|
231
|
+
end
|
232
|
+
|
233
|
+
before do
|
234
|
+
begin
|
235
|
+
post.archive_without_callbacks
|
236
|
+
rescue Mongoid::Errors::DeleteRestriction
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'ignores restrict and archives the document' do
|
241
|
+
expect(post).to be_archived
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
describe '#archived?' do
|
247
|
+
|
248
|
+
context 'when the document is a root' do
|
249
|
+
|
250
|
+
let(:post) do
|
251
|
+
ArchivablePost.create(title: 'testing')
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'when the document is archived' do
|
255
|
+
|
256
|
+
before do
|
257
|
+
post.archive
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'returns true' do
|
261
|
+
expect(post).to be_archived
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'returns true for archived scope document' do
|
265
|
+
expect(ArchivablePost.archived.last).to be_archived
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
context 'when the document is embedded' do
|
271
|
+
|
272
|
+
let(:person) do
|
273
|
+
Person.create
|
274
|
+
end
|
275
|
+
|
276
|
+
let(:phone) do
|
277
|
+
person.archivable_phones.create(number: '911')
|
278
|
+
end
|
279
|
+
|
280
|
+
context 'when the document is hard deleted' do
|
281
|
+
|
282
|
+
before do
|
283
|
+
phone.destroy!
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'returns true' do
|
287
|
+
expect(phone).to be_destroyed
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
context 'when the document is archived' do
|
292
|
+
|
293
|
+
before do
|
294
|
+
phone.archive
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'returns true' do
|
298
|
+
expect(phone).to_not be_destroyed
|
299
|
+
expect(phone).to be_archived
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
context 'when the document has non-dependent relation' do
|
304
|
+
let(:post) do
|
305
|
+
ArchivablePost.create(title: 'test')
|
306
|
+
end
|
307
|
+
|
308
|
+
let!(:tag) do
|
309
|
+
post.tags.create(text: 'tagie')
|
310
|
+
end
|
311
|
+
|
312
|
+
before do
|
313
|
+
post.archive
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'does not cascades the dependent option' do
|
317
|
+
expect(tag.reload).to eq(tag)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
describe '#set' do
|
324
|
+
|
325
|
+
let!(:post) do
|
326
|
+
ArchivablePost.create
|
327
|
+
end
|
328
|
+
|
329
|
+
let(:time) do
|
330
|
+
20.days.ago
|
331
|
+
end
|
332
|
+
|
333
|
+
let!(:set) do
|
334
|
+
post.set(archived_at: time)
|
335
|
+
end
|
336
|
+
|
337
|
+
it 'persists the change' do
|
338
|
+
expect(post.reload.archived_at).to be_within(1).of(time)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Mongoid::Archivable::Configuration do
|
4
|
+
context 'configuring the archived_field setting' do
|
5
|
+
before do
|
6
|
+
Mongoid::Archivable.configure do |c|
|
7
|
+
c.archived_field = :my_field_name
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.configure' do
|
12
|
+
before do
|
13
|
+
class ArchivableConfigured
|
14
|
+
include Mongoid::Document
|
15
|
+
include Mongoid::Archivable
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'allows custom setting of the archived_field' do
|
20
|
+
archivable_configured = ArchivableConfigured.new
|
21
|
+
expect(archivable_configured.attribute_names).to include('my_field_name')
|
22
|
+
end
|
23
|
+
|
24
|
+
after(:each) do
|
25
|
+
Mongoid::Archivable.reset
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '.reset' do
|
30
|
+
before do
|
31
|
+
Mongoid::Archivable.reset
|
32
|
+
|
33
|
+
# the configuration gets set at include time
|
34
|
+
# so you need to reset before defining a new class
|
35
|
+
class ArchivableConfiguredReset
|
36
|
+
include Mongoid::Document
|
37
|
+
include Mongoid::Archivable
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'restores the archived_field to the default setting' do
|
42
|
+
archivable_configured = ArchivableConfiguredReset.new
|
43
|
+
expect(archivable_configured.attribute_names).to include('archived_at')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#archived_field' do
|
49
|
+
it 'initializes with default value set to :archived_at' do
|
50
|
+
expect(Mongoid::Archivable::Configuration.new.archived_field).to eq(:archived_at)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'can be updated' do
|
54
|
+
config = Mongoid::Archivable::Configuration.new
|
55
|
+
config.archived_field = :my_field_name
|
56
|
+
expect(config.archived_field).to eq(:my_field_name)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Mongoid::Archivable.archivable?' do
|
4
|
+
|
5
|
+
describe '.archivable?' do
|
6
|
+
|
7
|
+
context 'when Mongoid::Archivable is included' do
|
8
|
+
subject { ArchivablePost }
|
9
|
+
it 'returns true' do
|
10
|
+
expect(subject.archivable?).to eq true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when Mongoid::Archivable not included' do
|
15
|
+
subject { Author }
|
16
|
+
it 'returns true' do
|
17
|
+
expect(subject.respond_to?(:archivable?)).to eq false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Mongoid::Attributes::Nested do
|
4
|
+
describe '##{name}_attributes=' do
|
5
|
+
context 'when the parent document is new' do
|
6
|
+
context 'when the relation is an embeds many' do
|
7
|
+
context 'when ids are passed' do
|
8
|
+
|
9
|
+
let(:person) do
|
10
|
+
Person.create
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:address_one) do
|
14
|
+
Address.new(street: 'Unter den Linden')
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:address_two) do
|
18
|
+
Address.new(street: 'Kurfeurstendamm')
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:phone_one) do
|
22
|
+
ArchivablePhone.new(number: '1')
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:phone_two) do
|
26
|
+
ArchivablePhone.new(number: '2')
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
person.addresses << [ address_one, address_two ]
|
31
|
+
end
|
32
|
+
|
33
|
+
# context 'when archive attributes are passed' do
|
34
|
+
# context 'when the ids match' do
|
35
|
+
# context 'when allow_archive is true' do
|
36
|
+
# context 'when the child is archivable' do
|
37
|
+
#
|
38
|
+
# before(:all) do
|
39
|
+
# Person.send(:undef_method, :archivable_phones_attributes=)
|
40
|
+
# Person.accepts_nested_attributes_for :archivable_phones,
|
41
|
+
# allow_archive: true
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# after(:all) do
|
45
|
+
# Person.send(:undef_method, :archivable_phones_attributes=)
|
46
|
+
# Person.accepts_nested_attributes_for :archivable_phones
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# [ 1, '1', true, 'true' ].each do |truth|
|
50
|
+
#
|
51
|
+
# context 'when passed a #{truth} with archive' do
|
52
|
+
# context 'when the parent is persisted' do
|
53
|
+
#
|
54
|
+
# let!(:persisted) do
|
55
|
+
# Person.create do |p|
|
56
|
+
# p.archivable_phones << [ phone_one, phone_two ]
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# context 'when setting, pulling, and pushing in one op' do
|
61
|
+
#
|
62
|
+
# before do
|
63
|
+
# persisted.archivable_phones_attributes =
|
64
|
+
# {
|
65
|
+
# 'bar' => { 'id' => phone_one.id, '_archive' => truth },
|
66
|
+
# 'foo' => { 'id' => phone_two.id, 'number' => '3' },
|
67
|
+
# 'baz' => { 'number' => '4' }
|
68
|
+
# }
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# it 'removes the first document from the relation' do
|
72
|
+
# expect(persisted.archivable_phones.size).to eq(2)
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# it 'does not delete the unmarked document' do
|
76
|
+
# expect(persisted.archivable_phones.first.number).to eq('3')
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# it 'adds the new document to the relation' do
|
80
|
+
# expect(persisted.archivable_phones.last.number).to eq('4')
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# it 'has the proper persisted count' do
|
84
|
+
# expect(persisted.archivable_phones.count).to eq(1)
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# it 'archives the removed document' do
|
88
|
+
# expect(phone_one).to be_archiveed
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# context 'when saving the parent' do
|
92
|
+
#
|
93
|
+
# before do
|
94
|
+
# persisted.save
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# it 'deletes the marked document from the relation' do
|
98
|
+
# expect(persisted.reload.archivable_phones.count).to eq(2)
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# it 'does not delete the unmarked document' do
|
102
|
+
# expect(persisted.reload.archivable_phones.first.number).to eq('3')
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# it 'persists the new document to the relation' do
|
106
|
+
# expect(persisted.reload.archivable_phones.last.number).to eq('4')
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# context 'when the child has defaults' do
|
116
|
+
#
|
117
|
+
# before(:all) do
|
118
|
+
# Person.accepts_nested_attributes_for :appointments, allow_archive: true
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# after(:all) do
|
122
|
+
# Person.send(:undef_method, :appointments_attributes=)
|
123
|
+
# end
|
124
|
+
# context 'when the parent is persisted' do
|
125
|
+
# context 'when the child returns false in a before callback' do
|
126
|
+
# context 'when the child is archivable' do
|
127
|
+
#
|
128
|
+
# before(:all) do
|
129
|
+
# Person.accepts_nested_attributes_for :archivable_phones, allow_archive: true
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# after(:all) do
|
133
|
+
# Person.send(:undef_method, :archivable_phones=)
|
134
|
+
# Person.accepts_nested_attributes_for :archivable_phones
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# let!(:persisted) do
|
138
|
+
# Person.create(age: 42)
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# let!(:phone) do
|
142
|
+
# persisted.archivable_phones.create
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# before do
|
146
|
+
# persisted.archivable_phones_attributes =
|
147
|
+
# { 'foo' => { 'id' => phone.id, 'number' => 42, '_archive' => true }}
|
148
|
+
# end
|
149
|
+
#
|
150
|
+
# it 'does not archive the child' do
|
151
|
+
# expect(persisted.reload.archivable_phones).not_to be_empty
|
152
|
+
# end
|
153
|
+
# end
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
# end
|
157
|
+
# end
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|