mongoid_archival 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Association
5
+ module Depending
6
+
7
+ STRATEGIES = STRATEGIES.dup + %i[archive archive_all]
8
+
9
+ def apply_archive_dependencies!
10
+ self.class._all_dependents.each do |association|
11
+ dependent = association.try(:dependent)
12
+ next if !dependent || dependent.in?(%i[delete_all destroy])
13
+ send(:"_dependent_#{dependent}!", association)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def _dependent_archive!(association)
20
+ return unless _warn_association_archivable?(association)
21
+ relation = send(association.name)
22
+ return unless relation
23
+ if relation.is_a?(Enumerable)
24
+ relation.entries
25
+ relation.each(&:archive)
26
+ elsif relation.try(:archivable?)
27
+ relation.archive
28
+ end
29
+ end
30
+
31
+ def _dependent_archive_all!(association)
32
+ return unless _warn_association_archivable?(association)
33
+ relation = send(association.name)
34
+ return unless relation
35
+ relation.set(archive_at: Time.zone.now)
36
+
37
+ # TODO: this code enables dependency recursion. Untested.
38
+ # dependents = relation.respond_to?(:dependents) && relation.dependents
39
+ # if dependents && dependents.reject {|dep| dep.try(:dependent).in?(%i[delete_all destroy]) }.blank?
40
+ # relation.set(archive_at: Time.zone.now)
41
+ # else
42
+ # ::Array.wrap(send(association.name)).each { |rel| rel.archive }
43
+ # end
44
+ end
45
+
46
+ def _warn_association_archivable?(association)
47
+ result = _association_archivable?(association)
48
+ Mongoid.logger.warn "Non-archivable association: #{association.name}" unless result
49
+ result
50
+ end
51
+
52
+ def _association_archivable?(association)
53
+ relations[association.name].class_name.constantize.try(:archivable?)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Archivable
5
+ module Protected
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Mongoid::Persistable::Deletable
10
+
11
+ alias :delete! :delete
12
+
13
+ def delete
14
+ raise '#delete not permitted. Use #archive_without_callbacks or #delete! instead.'
15
+ end
16
+ alias :remove :delete
17
+
18
+ def destroy
19
+ raise '#destroy not permitted. Use #archive or #destroy! instead.'
20
+ end
21
+
22
+ def destroy!(options = {})
23
+ raise Errors::ReadonlyDocument.new(self.class) if readonly?
24
+ self.flagged_for_destroy = true
25
+ result = run_callbacks(:destroy) do
26
+ if catch(:abort) { apply_destroy_dependencies! }
27
+ delete!(options || {})
28
+ else
29
+ false
30
+ end
31
+ end
32
+ self.flagged_for_destroy = false
33
+ result
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Archivable
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongoid/archivable'
data/perf/scope.rb ADDED
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'mongoid'
5
+ require 'mongoid/archivable'
6
+ require 'benchmark'
7
+
8
+ Mongoid.configure do |config|
9
+ config.connect_to('my_little_test')
10
+ end
11
+
12
+ class Model
13
+ include Mongoid::Document
14
+ field :text, type: String
15
+
16
+ index({ text: 'text' })
17
+ end
18
+
19
+ class ArchivableModel
20
+ include Mongoid::Document
21
+ include Mongoid::Archivable
22
+ field :text, type: String
23
+
24
+ index({ text: 'text' })
25
+ end
26
+
27
+ class MetaArchivableModel
28
+ include Mongoid::Document
29
+ field :text, type: String
30
+ field :archived_at, type: Time
31
+ default_scope -> { where(archived_at: nil) }
32
+
33
+ index({ text: 'text' })
34
+ end
35
+
36
+ if ENV['FORCE']
37
+ Mongoid.purge!
38
+ ::Mongoid::Tasks::Database.create_indexes
39
+
40
+ n = 50_000
41
+ n.times {|_i| Model.create(text: "text #{i}") }
42
+ n.times {|_i| ArchivableModel.create(text: "text #{i}") }
43
+ n.times {|_i| MetaArchivableModel.create(text: "text #{i}") }
44
+ end
45
+
46
+ n = 100
47
+
48
+ puts 'text_search benchmark ***'
49
+ Benchmark.bm(20) do |x|
50
+ x.report('without') { n.times { Model.text_search('text').execute } }
51
+ x.report('with') { n.times { ArchivableModel.text_search('text').execute } }
52
+ x.report('meta') { n.times { MetaArchivableModel.text_search('text').execute } }
53
+ x.report('unscoped meta') { n.times { MetaArchivableModel.unscoped.text_search('text').execute } }
54
+ x.report('unscoped archivable') { n.times { ArchivableModel.unscoped.text_search('text').execute } }
55
+ end
56
+
57
+ puts ''
58
+ puts 'Pluck all ids benchmark ***'
59
+ Benchmark.bm(20) do |x|
60
+ x.report('without') { n.times { Model.all.pluck(:id) } }
61
+ x.report('with') { n.times { ArchivableModel.all.pluck(:id) } }
62
+ x.report('meta') { n.times { MetaArchivableModel.all.pluck(:id) } }
63
+ x.report('unscoped meta') { n.times { MetaArchivableModel.unscoped.all.pluck(:id) } }
64
+ x.report('unscoped archivable') { n.times { ArchivableModel.unscoped.all.pluck(:id) } }
65
+ end
@@ -0,0 +1,71 @@
1
+ class Address
2
+ include Mongoid::Document
3
+
4
+ field :_id, type: String, default: ->{ street.try(:parameterize) }
5
+
6
+ attr_accessor :mode
7
+
8
+ field :address_type
9
+ field :number, type: Integer
10
+ field :street
11
+ field :city
12
+ field :state
13
+ field :post_code
14
+ field :parent_title
15
+ field :services, type: Array
16
+ field :latlng, type: Array
17
+ field :map, type: Hash
18
+ field :move_in, type: DateTime
19
+ field :s, type: String, as: :suite
20
+ field :name, localize: true
21
+
22
+ embeds_one :code, validate: false
23
+ embeds_one :target, as: :targetable, validate: false
24
+
25
+ embedded_in :addressable, polymorphic: true do
26
+ def extension
27
+ 'Testing'
28
+ end
29
+ def doctor?
30
+ title == 'Dr'
31
+ end
32
+ end
33
+
34
+ accepts_nested_attributes_for :code, :target
35
+
36
+ belongs_to :account
37
+
38
+ scope :without_postcode, -> {where(postcode: nil)}
39
+ scope :rodeo, -> {
40
+ where(street: 'Rodeo Dr') do
41
+ def mansion?
42
+ all? { |address| address.street == 'Rodeo Dr' }
43
+ end
44
+ end
45
+ }
46
+
47
+ validates_presence_of :street, on: :update
48
+ validates_format_of :street, with: /\D/, allow_nil: true
49
+
50
+ def set_parent=(set = false)
51
+ self.parent_title = addressable.title if set
52
+ end
53
+
54
+ def <=>(other)
55
+ street <=> other.street
56
+ end
57
+
58
+ class << self
59
+ def california
60
+ where(state: 'CA')
61
+ end
62
+
63
+ def homes
64
+ where(address_type: 'Home')
65
+ end
66
+
67
+ def streets
68
+ all.map(&:street)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,7 @@
1
+ class Appointment
2
+ include Mongoid::Document
3
+ field :active, type: Boolean, default: true
4
+ field :timed, type: Boolean, default: true
5
+ embedded_in :person
6
+ default_scope ->{where(active: true)}
7
+ end
@@ -0,0 +1,25 @@
1
+ class ArchivablePhone
2
+ include Mongoid::Document
3
+ include Mongoid::Archivable
4
+
5
+ attr_accessor :after_archive_called, :before_archive_called
6
+
7
+ field :number, type: String
8
+
9
+ embedded_in :person
10
+
11
+ before_archive :before_archive_stub, :halt_me
12
+ after_archive :after_archive_stub
13
+
14
+ def before_archive_stub
15
+ self.before_archive_called = true
16
+ end
17
+
18
+ def after_archive_stub
19
+ self.after_archive_called = true
20
+ end
21
+
22
+ def halt_me
23
+ throw :abort if person.age == 42
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ class ArchivablePost
2
+ include Mongoid::Document
3
+ include Mongoid::Archivable
4
+
5
+ field :title, type: String
6
+
7
+ attr_accessor :after_archive_called, :before_archive_called,
8
+ :after_restore_called, :before_restore_called,
9
+ :after_archive_called, :before_archive_called,
10
+ :around_before_restore_called, :around_after_restore_called
11
+
12
+ belongs_to :person
13
+
14
+ has_and_belongs_to_many :tags
15
+ has_many :authors, dependent: :delete_all, inverse_of: :post
16
+ has_many :titles, dependent: :restrict_with_error
17
+ has_one :fish, dependent: :archive
18
+
19
+ scope :recent, -> { where(created_at: { '$lt' => Time.now, '$gt' => 30.days.ago }) }
20
+
21
+ before_archive :before_archive_stub
22
+ after_archive :after_archive_stub
23
+
24
+ before_archive :before_archive_stub
25
+ after_archive :after_archive_stub
26
+
27
+ before_restore :before_restore_stub
28
+ after_restore :after_restore_stub
29
+ around_restore :around_restore_stub
30
+
31
+ def before_archive_stub
32
+ self.before_archive_called = true
33
+ end
34
+
35
+ def after_archive_stub
36
+ self.after_archive_called = true
37
+ end
38
+
39
+ def before_archive_stub
40
+ self.before_archive_called = true
41
+ end
42
+
43
+ def after_archive_stub
44
+ self.after_archive_called = true
45
+ end
46
+
47
+ def before_restore_stub
48
+ self.before_restore_called = true
49
+ end
50
+
51
+ def after_restore_stub
52
+ self.after_restore_called = true
53
+ end
54
+
55
+ def around_restore_stub
56
+ self.around_before_restore_called = true
57
+ yield
58
+ self.around_after_restore_called = true
59
+ end
60
+
61
+ class << self
62
+ def old
63
+ where(created_at: { '$lt' => 30.days.ago })
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,7 @@
1
+ class Author
2
+ include Mongoid::Document
3
+
4
+ field :name, type: String
5
+
6
+ belongs_to :post, class_name: 'ArchivablePost'
7
+ end
@@ -0,0 +1,10 @@
1
+ class Fish
2
+ include Mongoid::Document
3
+ include Mongoid::Archivable
4
+
5
+ def self.fresh
6
+ where(fresh: true)
7
+ end
8
+
9
+ belongs_to :post, class_name: 'ArchivablePost'
10
+ end
@@ -0,0 +1,21 @@
1
+ class Person
2
+ include Mongoid::Document
3
+
4
+ field :age, type: Integer, default: '100'
5
+ field :score, type: Integer
6
+
7
+ attr_reader :rescored
8
+
9
+ embeds_many :phone_numbers, class_name: 'Phone', validate: false
10
+ embeds_many :phones, store_as: :mobile_phones, validate: false
11
+ embeds_many :addresses, as: :addressable, validate: false
12
+
13
+ embeds_many :appointments, validate: false
14
+ embeds_many :archivable_phones, validate: false
15
+
16
+ has_many :archivable_posts, validate: false
17
+ belongs_to :archivable_post
18
+
19
+ accepts_nested_attributes_for :addresses
20
+ accepts_nested_attributes_for :archivable_phones
21
+ end
@@ -0,0 +1,11 @@
1
+ class Phone
2
+ include Mongoid::Document
3
+
4
+ attr_accessor :number_in_observer
5
+
6
+ field :_id, type: String, default: ->{ number }
7
+
8
+ field :number
9
+ embeds_one :country_code
10
+ embedded_in :person
11
+ end
@@ -0,0 +1,246 @@
1
+ class NormBase
2
+ include Mongoid::Document
3
+
4
+ has_one :norm_has_one, dependent: :destroy
5
+ has_one :arch_has_one, dependent: :archive
6
+
7
+ has_many :norm_has_many, dependent: :destroy
8
+ has_many :arch_has_many, dependent: :archive
9
+
10
+ has_many :norm_has_many_poly, dependent: :destroy
11
+ has_many :arch_has_many_poly, dependent: :archive
12
+
13
+ belongs_to :norm_belongs_to_one, dependent: :destroy
14
+ belongs_to :arch_belongs_to_one, dependent: :archive
15
+
16
+ belongs_to :norm_belongs_to, dependent: :destroy
17
+ belongs_to :arch_belongs_to, dependent: :archive
18
+
19
+ has_and_belongs_to_many :norm_habtm, dependent: :destroy
20
+ has_and_belongs_to_many :arch_habtm, dependent: :archive
21
+
22
+ embeds_one :norm_embeds_one
23
+ embeds_one :arch_embeds_one
24
+
25
+ embeds_many :norm_embeds_many
26
+ embeds_many :arch_embeds_many
27
+
28
+ embeds_many :norm_embeds_many_poly
29
+ embeds_many :arch_embeds_many_poly
30
+ end
31
+
32
+ class ArchBase
33
+ include Mongoid::Document
34
+ include Mongoid::Archivable
35
+
36
+ has_one :norm_has_one, dependent: :destroy
37
+ has_one :arch_has_one, dependent: :archive
38
+
39
+ has_many :norm_has_many, dependent: :destroy
40
+ has_many :arch_has_many, dependent: :archive
41
+
42
+ has_many :norm_has_many_poly, dependent: :destroy
43
+ has_many :arch_has_many_poly, dependent: :archive
44
+
45
+ belongs_to :norm_belongs_to_one, dependent: :destroy
46
+ belongs_to :arch_belongs_to_one, dependent: :archive
47
+
48
+ belongs_to :norm_belongs_to, dependent: :destroy
49
+ belongs_to :arch_belongs_to, dependent: :archive
50
+
51
+ has_and_belongs_to_many :norm_habtm, dependent: :destroy
52
+ has_and_belongs_to_many :arch_habtm, dependent: :archive
53
+
54
+ embeds_one :norm_embeds_one
55
+ embeds_one :arch_embeds_one
56
+
57
+ embeds_many :norm_embeds_many
58
+ embeds_many :arch_embeds_many
59
+
60
+ embeds_many :norm_embeds_many_poly
61
+ embeds_many :arch_embeds_many_poly
62
+ end
63
+
64
+ class NormHasOne
65
+ include Mongoid::Document
66
+
67
+ belongs_to :norm_base
68
+ belongs_to :arch_base
69
+
70
+ has_one :norm_belongs_to, dependent: :destroy
71
+ has_one :arch_belongs_to, dependent: :archive
72
+
73
+ has_one :norm_habtm, dependent: :destroy
74
+ has_one :norm_habtm, dependent: :destroy
75
+ end
76
+
77
+ class NormHasMany
78
+ include Mongoid::Document
79
+
80
+ belongs_to :norm_base
81
+ belongs_to :arch_base
82
+
83
+ has_many :norm_belongs_to, dependent: :destroy
84
+ has_many :arch_belongs_to, dependent: :archive
85
+
86
+ has_many :norm_habtm, dependent: :destroy
87
+ has_many :norm_habtm, dependent: :destroy
88
+ end
89
+
90
+ class NormHasManyPoly
91
+ include Mongoid::Document
92
+
93
+ belongs_to :base, polymorphic: true
94
+ end
95
+
96
+ class NormBelongsToOne
97
+ include Mongoid::Document
98
+
99
+ has_one :norm_base
100
+ has_one :arch_base
101
+ end
102
+
103
+ class NormBelongsTo
104
+ include Mongoid::Document
105
+
106
+ has_many :norm_base
107
+ has_many :arch_base
108
+
109
+ belongs_to :norm_has_one, dependent: :destroy
110
+ belongs_to :arch_has_one, dependent: :archive
111
+
112
+ belongs_to :norm_has_many, dependent: :destroy
113
+ belongs_to :arch_has_many, dependent: :archive
114
+ end
115
+
116
+ class NormHabtm
117
+ include Mongoid::Document
118
+
119
+ has_and_belongs_to_many :norm_base
120
+ has_and_belongs_to_many :arch_base
121
+
122
+ belongs_to :norm_has_one, dependent: :destroy
123
+ belongs_to :arch_has_one, dependent: :archive
124
+
125
+ belongs_to :norm_has_many, dependent: :destroy
126
+ belongs_to :arch_has_many, dependent: :archive
127
+
128
+ has_and_belongs_to_many :recursive, class_name: 'NormHabtm', inverse_of: :recursive, dependent: :archive
129
+ has_and_belongs_to_many :arch_habtm, dependent: :archive
130
+ end
131
+
132
+ class NormEmbedsOne
133
+ include Mongoid::Document
134
+
135
+ embedded_in :norm_base
136
+ embedded_in :arch_base
137
+ end
138
+
139
+ class NormEmbedsMany
140
+ include Mongoid::Document
141
+
142
+ embedded_in :norm_base
143
+ embedded_in :arch_base
144
+ end
145
+
146
+ class NormEmbedsManyPoly
147
+ include Mongoid::Document
148
+
149
+ embedded_in :base, polymorphic: true
150
+ end
151
+
152
+ class ArchHasOne
153
+ include Mongoid::Document
154
+ include Mongoid::Archivable
155
+
156
+ belongs_to :norm_base
157
+ belongs_to :arch_base
158
+
159
+ has_one :norm_belongs_to, dependent: :destroy
160
+ has_one :arch_belongs_to, dependent: :archive
161
+
162
+ has_one :norm_habtm, dependent: :destroy
163
+ has_one :norm_habtm, dependent: :destroy
164
+ end
165
+
166
+ class ArchHasMany
167
+ include Mongoid::Document
168
+ include Mongoid::Archivable
169
+
170
+ belongs_to :norm_base
171
+ belongs_to :arch_base
172
+
173
+ has_many :norm_belongs_to, dependent: :destroy
174
+ has_many :arch_belongs_to, dependent: :archive
175
+
176
+ has_many :norm_habtm, dependent: :destroy
177
+ has_many :norm_habtm, dependent: :destroy
178
+ end
179
+
180
+ class ArchHasManyPoly
181
+ include Mongoid::Document
182
+ include Mongoid::Archivable
183
+
184
+ belongs_to :base, polymorphic: true
185
+ end
186
+
187
+ class ArchBelongsToOne
188
+ include Mongoid::Document
189
+ include Mongoid::Archivable
190
+
191
+ has_one :norm_base
192
+ has_one :arch_base
193
+ end
194
+
195
+ class ArchBelongsTo
196
+ include Mongoid::Document
197
+ include Mongoid::Archivable
198
+
199
+ has_many :norm_base
200
+ has_many :arch_base
201
+
202
+ belongs_to :norm_has_one, dependent: :destroy
203
+ belongs_to :arch_has_one, dependent: :archive
204
+
205
+ belongs_to :norm_has_many, dependent: :destroy
206
+ belongs_to :arch_has_many, dependent: :archive
207
+ end
208
+
209
+ class ArchHabtm
210
+ include Mongoid::Document
211
+ include Mongoid::Archivable
212
+
213
+ has_and_belongs_to_many :norm_base
214
+ has_and_belongs_to_many :arch_base
215
+
216
+ belongs_to :norm_has_one, dependent: :destroy
217
+ belongs_to :arch_has_one, dependent: :archive
218
+
219
+ belongs_to :norm_has_many, dependent: :destroy
220
+ belongs_to :arch_has_many, dependent: :archive
221
+
222
+ has_and_belongs_to_many :norm_habtm, dependent: :destroy
223
+ end
224
+
225
+ class ArchEmbedsOne
226
+ include Mongoid::Document
227
+ include Mongoid::Archivable
228
+
229
+ embedded_in :norm_base
230
+ embedded_in :arch_base
231
+ end
232
+
233
+ class ArchEmbedsMany
234
+ include Mongoid::Document
235
+ include Mongoid::Archivable
236
+
237
+ embedded_in :norm_base
238
+ embedded_in :arch_base
239
+ end
240
+
241
+ class ArchEmbedsManyPoly
242
+ include Mongoid::Document
243
+ include Mongoid::Archivable
244
+
245
+ embedded_in :base, polymorphic: true
246
+ end