mongoid 9.1.0 → 9.1.1
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 +4 -4
- data/lib/config/locales/en.yml +49 -0
- data/lib/mongoid/association/accessors.rb +9 -1
- data/lib/mongoid/association/depending.rb +14 -10
- data/lib/mongoid/association/eager.rb +9 -0
- data/lib/mongoid/association/eager_load/discriminated_inclusion.rb +88 -0
- data/lib/mongoid/association/eager_load/embedded_distributor.rb +151 -0
- data/lib/mongoid/association/eager_load/inclusion.rb +173 -0
- data/lib/mongoid/association/eager_load/inclusion_tree.rb +96 -0
- data/lib/mongoid/association/eager_load/lookup_pipeline.rb +121 -0
- data/lib/mongoid/association/eager_load/polymorphic_preloader.rb +61 -0
- data/lib/mongoid/association/eager_load/polymorphic_targets.rb +151 -0
- data/lib/mongoid/association/eager_loadable.rb +57 -100
- data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -1
- data/lib/mongoid/association/embedded/embeds_one.rb +1 -0
- data/lib/mongoid/association/macros.rb +13 -1
- data/lib/mongoid/association/nested/many.rb +34 -5
- data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
- data/lib/mongoid/association/nested/one.rb +6 -1
- data/lib/mongoid/association/options.rb +17 -0
- data/lib/mongoid/association/referenced/belongs_to/eager.rb +6 -1
- data/lib/mongoid/association/referenced/belongs_to.rb +1 -0
- data/lib/mongoid/association/referenced/counter_cache.rb +3 -3
- data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +5 -0
- data/lib/mongoid/association/referenced/has_many/enumerable.rb +3 -1
- data/lib/mongoid/association/referenced/has_many/proxy.rb +100 -1
- data/lib/mongoid/association/referenced/has_many_through/eager.rb +99 -0
- data/lib/mongoid/association/referenced/has_many_through/proxy.rb +68 -0
- data/lib/mongoid/association/referenced/has_many_through.rb +201 -0
- data/lib/mongoid/association/referenced/has_one.rb +1 -0
- data/lib/mongoid/association/referenced/has_one_through/eager.rb +65 -0
- data/lib/mongoid/association/referenced/has_one_through/proxy.rb +30 -0
- data/lib/mongoid/association/referenced/has_one_through.rb +167 -0
- data/lib/mongoid/association/referenced.rb +2 -0
- data/lib/mongoid/association/relatable.rb +40 -0
- data/lib/mongoid/association.rb +7 -0
- data/lib/mongoid/clients/factory.rb +7 -13
- data/lib/mongoid/collection_configurable.rb +8 -0
- data/lib/mongoid/config/defaults.rb +0 -1
- data/lib/mongoid/config/encryption.rb +36 -19
- data/lib/mongoid/config.rb +56 -5
- data/lib/mongoid/contextual/aggregable/memory.rb +6 -2
- data/lib/mongoid/contextual/memory.rb +18 -7
- data/lib/mongoid/criteria/includable.rb +35 -11
- data/lib/mongoid/criteria/queryable/mergeable.rb +4 -0
- data/lib/mongoid/criteria/queryable/selectable.rb +123 -7
- data/lib/mongoid/deprecable.rb +45 -2
- data/lib/mongoid/encryptable.rb +46 -0
- data/lib/mongoid/errors/config_redactor.rb +43 -0
- data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
- data/lib/mongoid/errors/mixed_client_configuration.rb +1 -1
- data/lib/mongoid/errors/no_client_database.rb +1 -1
- data/lib/mongoid/errors/no_client_hosts.rb +1 -1
- data/lib/mongoid/errors/no_encryption_schema.rb +28 -0
- data/lib/mongoid/errors/readonly_association.rb +29 -0
- data/lib/mongoid/errors.rb +4 -0
- data/lib/mongoid/extensions/vector.rb +64 -0
- data/lib/mongoid/extensions.rb +1 -0
- data/lib/mongoid/field_readable.rb +69 -0
- data/lib/mongoid/indexable.rb +38 -26
- data/lib/mongoid/matchable.rb +6 -1
- data/lib/mongoid/matcher/eq_impl_with_regexp.rb +2 -4
- data/lib/mongoid/matcher/regex.rb +11 -12
- data/lib/mongoid/matcher/regexp_budget.rb +372 -0
- data/lib/mongoid/matcher.rb +1 -0
- data/lib/mongoid/persistable/creatable.rb +1 -1
- data/lib/mongoid/persistable/updatable.rb +9 -11
- data/lib/mongoid/persistence_context.rb +35 -0
- data/lib/mongoid/search_indexable.rb +64 -23
- data/lib/mongoid/serializable.rb +12 -4
- data/lib/mongoid/tasks/database.rb +23 -9
- data/lib/mongoid/threaded.rb +36 -0
- data/lib/mongoid/timestamps/timeless.rb +84 -10
- data/lib/mongoid/traversable.rb +2 -2
- data/lib/mongoid/validatable/associated.rb +18 -2
- data/lib/mongoid/version.rb +1 -1
- data/spec/integration/app_spec.rb +7 -1
- data/spec/integration/associations/belongs_to_spec.rb +13 -0
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +17 -2
- data/spec/integration/dots_and_dollars_spec.rb +12 -2
- data/spec/integration/encryption_spec.rb +151 -0
- data/spec/integration/matcher_operator_data/regex.yml +21 -0
- data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
- data/spec/integration/query_operator_guard_spec.rb +89 -0
- data/spec/mongoid/association/eager_loadable_spec.rb +47 -0
- data/spec/mongoid/association/eager_spec.rb +2 -2
- data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +33 -0
- data/spec/mongoid/association/fallback_spec.rb +425 -0
- data/spec/mongoid/association/macros_spec.rb +26 -0
- data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +17 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +48 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +50 -0
- data/spec/mongoid/association/referenced/has_many/eager_spec.rb +33 -0
- data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +145 -0
- data/spec/mongoid/association/referenced/has_many_through/eager_spec.rb +58 -0
- data/spec/mongoid/association/referenced/has_many_through/proxy_spec.rb +62 -0
- data/spec/mongoid/association/referenced/has_many_through_spec.rb +399 -0
- data/spec/mongoid/association/referenced/has_one_through/eager_spec.rb +58 -0
- data/spec/mongoid/association/referenced/has_one_through/proxy_spec.rb +49 -0
- data/spec/mongoid/association/referenced/has_one_through_spec.rb +211 -0
- data/spec/mongoid/attributes/nested_spec.rb +249 -8
- data/spec/mongoid/config/defaults_spec.rb +33 -0
- data/spec/mongoid/config/encryption_spec.rb +228 -0
- data/spec/mongoid/contextual/aggregable/memory_spec.rb +91 -0
- data/spec/mongoid/contextual/memory_spec.rb +177 -0
- data/spec/mongoid/criteria/includable_spec.rb +875 -0
- data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +15 -1
- data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +201 -0
- data/spec/mongoid/criteria_spec.rb +3 -0
- data/spec/mongoid/encryptable_spec.rb +61 -0
- data/spec/mongoid/errors/config_redactor_spec.rb +112 -0
- data/spec/mongoid/errors/mixed_client_configuration_spec.rb +31 -0
- data/spec/mongoid/errors/no_client_database_spec.rb +29 -0
- data/spec/mongoid/errors/no_client_hosts_spec.rb +26 -0
- data/spec/mongoid/errors/readonly_association_spec.rb +34 -0
- data/spec/mongoid/extensions/vector_spec.rb +175 -0
- data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
- data/spec/mongoid/search_indexable_spec.rb +295 -9
- data/spec/mongoid/tasks/database_spec.rb +18 -0
- data/spec/mongoid/timestamps/timeless_spec.rb +197 -5
- data/spec/mongoid/touchable_spec.rb +25 -0
- data/spec/mongoid/touchable_spec_models.rb +10 -0
- data/spec/mongoid/validatable/associated_spec.rb +19 -0
- data/spec/spec_helper.rb +5 -1
- data/spec/support/crypt/models.rb +157 -0
- metadata +52 -2
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'associations with the :fallback option' do
|
|
4
|
+
before(:all) do
|
|
5
|
+
class Anonymous
|
|
6
|
+
def attribution
|
|
7
|
+
'Composer unknown'
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
after(:all) do
|
|
13
|
+
Object.send(:remove_const, :Anonymous)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context 'belongs_to' do
|
|
17
|
+
before(:all) do
|
|
18
|
+
class Composer
|
|
19
|
+
include Mongoid::Document
|
|
20
|
+
|
|
21
|
+
field :name
|
|
22
|
+
|
|
23
|
+
def attribution
|
|
24
|
+
"Composed by #{name}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class Symphony
|
|
29
|
+
include Mongoid::Document
|
|
30
|
+
|
|
31
|
+
belongs_to :composer, fallback: -> { Anonymous.new }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
after(:all) do
|
|
36
|
+
Object.send(:remove_const, :Symphony)
|
|
37
|
+
Object.send(:remove_const, :Composer)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'returns a fresh null object instance on every access when the association is nil' do
|
|
41
|
+
symphony = Symphony.create!
|
|
42
|
+
|
|
43
|
+
expect(symphony.composer.attribution).to eq('Composer unknown')
|
|
44
|
+
expect(symphony.composer).not_to equal(symphony.composer)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'returns the real associated document when the FK is set' do
|
|
48
|
+
symphony = Symphony.create!(composer: Composer.create!(name: 'Mahler'))
|
|
49
|
+
|
|
50
|
+
expect(Symphony.find(symphony.id).composer.attribution).to eq('Composed by Mahler')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'treats a direct null object assignment as nil' do
|
|
54
|
+
symphony = Symphony.create!
|
|
55
|
+
|
|
56
|
+
symphony.composer = Anonymous.new
|
|
57
|
+
symphony.save!
|
|
58
|
+
|
|
59
|
+
expect(Symphony.find(symphony.id).composer_id).to be_nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'treats a null object passed via the constructor as nil' do
|
|
63
|
+
symphony = Symphony.create!(composer: Anonymous.new)
|
|
64
|
+
|
|
65
|
+
expect(Symphony.find(symphony.id).composer_id).to be_nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'has_one' do
|
|
70
|
+
before(:all) do
|
|
71
|
+
class Composer
|
|
72
|
+
include Mongoid::Document
|
|
73
|
+
|
|
74
|
+
field :name
|
|
75
|
+
|
|
76
|
+
belongs_to :symphony
|
|
77
|
+
|
|
78
|
+
def attribution
|
|
79
|
+
"Composed by #{name}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class Symphony
|
|
84
|
+
include Mongoid::Document
|
|
85
|
+
|
|
86
|
+
has_one :composer, fallback: -> { Anonymous.new }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
after(:all) do
|
|
91
|
+
Object.send(:remove_const, :Symphony)
|
|
92
|
+
Object.send(:remove_const, :Composer)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'returns a fresh null object instance on every access when the association is nil' do
|
|
96
|
+
symphony = Symphony.create!
|
|
97
|
+
|
|
98
|
+
expect(symphony.composer.attribution).to eq('Composer unknown')
|
|
99
|
+
expect(symphony.composer).not_to equal(symphony.composer)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'returns the real associated document when one exists' do
|
|
103
|
+
symphony = Symphony.create!
|
|
104
|
+
Composer.create!(symphony: symphony, name: 'Mahler')
|
|
105
|
+
|
|
106
|
+
expect(Symphony.find(symphony.id).composer.attribution).to eq('Composed by Mahler')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'treats a direct null object assignment as nil' do
|
|
110
|
+
symphony = Symphony.create!
|
|
111
|
+
|
|
112
|
+
symphony.composer = Anonymous.new
|
|
113
|
+
symphony.save!
|
|
114
|
+
|
|
115
|
+
expect(Composer.where(symphony_id: symphony.id).first).to be_nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'treats a null object passed via the constructor as nil' do
|
|
119
|
+
symphony = Symphony.create!(composer: Anonymous.new)
|
|
120
|
+
|
|
121
|
+
expect(Composer.where(symphony_id: symphony.id).first).to be_nil
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
context 'embeds_one' do
|
|
126
|
+
before(:all) do
|
|
127
|
+
class Composer
|
|
128
|
+
include Mongoid::Document
|
|
129
|
+
|
|
130
|
+
field :name
|
|
131
|
+
|
|
132
|
+
embedded_in :symphony
|
|
133
|
+
|
|
134
|
+
def attribution
|
|
135
|
+
"Composed by #{name}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class Symphony
|
|
140
|
+
include Mongoid::Document
|
|
141
|
+
|
|
142
|
+
embeds_one :composer, fallback: -> { Anonymous.new }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
after(:all) do
|
|
147
|
+
Object.send(:remove_const, :Symphony)
|
|
148
|
+
Object.send(:remove_const, :Composer)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'returns a fresh null object instance on every access when the association is nil' do
|
|
152
|
+
symphony = Symphony.create!
|
|
153
|
+
|
|
154
|
+
expect(symphony.composer.attribution).to eq('Composer unknown')
|
|
155
|
+
expect(symphony.composer).not_to equal(symphony.composer)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'returns the real embedded document when one exists' do
|
|
159
|
+
symphony = Symphony.create!(composer: Composer.new(name: 'Mahler'))
|
|
160
|
+
|
|
161
|
+
expect(Symphony.find(symphony.id).composer.attribution).to eq('Composed by Mahler')
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it 'treats a direct null object assignment as nil' do
|
|
165
|
+
symphony = Symphony.create!
|
|
166
|
+
|
|
167
|
+
symphony.composer = Anonymous.new
|
|
168
|
+
symphony.save!
|
|
169
|
+
|
|
170
|
+
expect(Symphony.collection.find(_id: symphony.id).first['composer']).to be_nil
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'treats a null object passed via the constructor as nil' do
|
|
174
|
+
symphony = Symphony.create!(composer: Anonymous.new)
|
|
175
|
+
|
|
176
|
+
expect(Symphony.collection.find(_id: symphony.id).first['composer']).to be_nil
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
context 'with a dependent strategy and no real associated document' do
|
|
181
|
+
before(:all) do
|
|
182
|
+
class Composer
|
|
183
|
+
include Mongoid::Document
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
after(:all) do
|
|
188
|
+
Object.send(:remove_const, :Composer)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
context 'when the strategy is :destroy' do
|
|
192
|
+
before(:all) do
|
|
193
|
+
class Symphony
|
|
194
|
+
include Mongoid::Document
|
|
195
|
+
|
|
196
|
+
has_one :composer, dependent: :destroy, fallback: -> { Anonymous.new }
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
after(:all) do
|
|
201
|
+
Object.send(:remove_const, :Symphony)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it 'does not apply the strategy to the fallback when destroying the owner' do
|
|
205
|
+
symphony = Symphony.create!
|
|
206
|
+
|
|
207
|
+
expect { symphony.destroy }.not_to raise_error
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
context 'when the strategy is :nullify' do
|
|
212
|
+
before(:all) do
|
|
213
|
+
class Symphony
|
|
214
|
+
include Mongoid::Document
|
|
215
|
+
|
|
216
|
+
has_one :composer, dependent: :nullify, fallback: -> { Anonymous.new }
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
after(:all) do
|
|
221
|
+
Object.send(:remove_const, :Symphony)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'does not apply the strategy to the fallback when destroying the owner' do
|
|
225
|
+
symphony = Symphony.create!
|
|
226
|
+
|
|
227
|
+
expect { symphony.destroy }.not_to raise_error
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
context 'when the strategy is :delete_all' do
|
|
232
|
+
before(:all) do
|
|
233
|
+
class Symphony
|
|
234
|
+
include Mongoid::Document
|
|
235
|
+
|
|
236
|
+
has_one :composer, dependent: :delete_all, fallback: -> { Anonymous.new }
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
after(:all) do
|
|
241
|
+
Object.send(:remove_const, :Symphony)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'does not apply the strategy to the fallback when destroying the owner' do
|
|
245
|
+
symphony = Symphony.create!
|
|
246
|
+
|
|
247
|
+
expect { symphony.destroy }.not_to raise_error
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
context 'when the strategy is :restrict_with_exception' do
|
|
252
|
+
before(:all) do
|
|
253
|
+
class Symphony
|
|
254
|
+
include Mongoid::Document
|
|
255
|
+
|
|
256
|
+
has_one :composer, dependent: :restrict_with_exception, fallback: -> { Anonymous.new }
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
after(:all) do
|
|
261
|
+
Object.send(:remove_const, :Symphony)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it 'does not apply the strategy to the fallback when destroying the owner' do
|
|
265
|
+
symphony = Symphony.create!
|
|
266
|
+
|
|
267
|
+
expect { symphony.destroy }.not_to raise_error
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
context 'when the strategy is :restrict_with_error' do
|
|
272
|
+
before(:all) do
|
|
273
|
+
class Symphony
|
|
274
|
+
include Mongoid::Document
|
|
275
|
+
|
|
276
|
+
has_one :composer, dependent: :restrict_with_error, fallback: -> { Anonymous.new }
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
after(:all) do
|
|
281
|
+
Object.send(:remove_const, :Symphony)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
it 'does not block destroying the owner because of the fallback' do
|
|
285
|
+
symphony = Symphony.create!
|
|
286
|
+
|
|
287
|
+
expect(symphony.destroy).to be_truthy
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
context 'on a polymorphic belongs_to' do
|
|
293
|
+
before(:all) do
|
|
294
|
+
class Composer
|
|
295
|
+
include Mongoid::Document
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
class Symphony
|
|
299
|
+
include Mongoid::Document
|
|
300
|
+
|
|
301
|
+
belongs_to :author, polymorphic: true, fallback: -> { Anonymous.new }
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
after(:all) do
|
|
306
|
+
Object.send(:remove_const, :Symphony)
|
|
307
|
+
Object.send(:remove_const, :Composer)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it 'does not raise when assigning a real document' do
|
|
311
|
+
symphony = Symphony.new
|
|
312
|
+
|
|
313
|
+
expect { symphony.author = Composer.new }.not_to raise_error
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it 'treats a null object assignment as nil' do
|
|
317
|
+
symphony = Symphony.new
|
|
318
|
+
|
|
319
|
+
expect { symphony.author = Anonymous.new }.not_to raise_error
|
|
320
|
+
expect(symphony.author_id).to be_nil
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
context 'with nested attributes' do
|
|
325
|
+
before(:all) do
|
|
326
|
+
class Composer
|
|
327
|
+
include Mongoid::Document
|
|
328
|
+
|
|
329
|
+
field :name
|
|
330
|
+
|
|
331
|
+
embedded_in :symphony
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
class Symphony
|
|
335
|
+
include Mongoid::Document
|
|
336
|
+
|
|
337
|
+
embeds_one :composer, fallback: -> { Anonymous.new }
|
|
338
|
+
accepts_nested_attributes_for :composer
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
after(:all) do
|
|
343
|
+
Object.send(:remove_const, :Symphony)
|
|
344
|
+
Object.send(:remove_const, :Composer)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
it 'builds the nested document instead of operating on the fallback' do
|
|
348
|
+
symphony = Symphony.new
|
|
349
|
+
|
|
350
|
+
expect { symphony.composer_attributes = { name: 'Mahler' } }.not_to raise_error
|
|
351
|
+
expect(symphony.composer.name).to eq('Mahler')
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
context 'when serialized with an included association' do
|
|
356
|
+
before(:all) do
|
|
357
|
+
class Composer
|
|
358
|
+
include Mongoid::Document
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
class Symphony
|
|
362
|
+
include Mongoid::Document
|
|
363
|
+
|
|
364
|
+
belongs_to :composer, fallback: -> { Anonymous.new }
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
after(:all) do
|
|
369
|
+
Object.send(:remove_const, :Symphony)
|
|
370
|
+
Object.send(:remove_const, :Composer)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
it 'omits the fallback when the real association is nil' do
|
|
374
|
+
symphony = Symphony.new
|
|
375
|
+
|
|
376
|
+
expect { symphony.serializable_hash(include: :composer) }.not_to raise_error
|
|
377
|
+
expect(symphony.serializable_hash(include: :composer)).not_to have_key('composer')
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
context 'with a counter cache' do
|
|
382
|
+
before(:all) do
|
|
383
|
+
class Composer
|
|
384
|
+
include Mongoid::Document
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
class Symphony
|
|
388
|
+
include Mongoid::Document
|
|
389
|
+
|
|
390
|
+
belongs_to :composer, counter_cache: true, fallback: -> { Anonymous.new }
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
after(:all) do
|
|
395
|
+
Object.send(:remove_const, :Symphony)
|
|
396
|
+
Object.send(:remove_const, :Composer)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
it 'does not update the counter on the fallback' do
|
|
400
|
+
expect { Symphony.create! }.not_to raise_error
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
context 'validation of the :fallback option' do
|
|
405
|
+
it 'rejects a declaration that combines :fallback with :autobuild' do
|
|
406
|
+
expect do
|
|
407
|
+
Class.new do
|
|
408
|
+
include Mongoid::Document
|
|
409
|
+
|
|
410
|
+
belongs_to :composer, fallback: -> { Object.new }, autobuild: true
|
|
411
|
+
end
|
|
412
|
+
end.to raise_error(ArgumentError, /cannot be combined with :autobuild/)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
it 'rejects a non-callable :fallback value' do
|
|
416
|
+
expect do
|
|
417
|
+
Class.new do
|
|
418
|
+
include Mongoid::Document
|
|
419
|
+
|
|
420
|
+
belongs_to :composer, fallback: Object.new
|
|
421
|
+
end
|
|
422
|
+
end.to raise_error(ArgumentError, /must be a Proc or lambda/)
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|
|
@@ -1027,4 +1027,30 @@ describe Mongoid::Association::Macros do
|
|
|
1027
1027
|
end
|
|
1028
1028
|
end
|
|
1029
1029
|
end
|
|
1030
|
+
|
|
1031
|
+
context 'with :through option' do
|
|
1032
|
+
let(:klass) do
|
|
1033
|
+
Class.new do
|
|
1034
|
+
include Mongoid::Document
|
|
1035
|
+
end
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1038
|
+
it 'creates a HasManyThrough for has_many with :through' do
|
|
1039
|
+
klass.has_many(:patients, through: :appointments)
|
|
1040
|
+
assoc = klass.relations['patients']
|
|
1041
|
+
expect(assoc).to be_a(Mongoid::Association::Referenced::HasManyThrough)
|
|
1042
|
+
end
|
|
1043
|
+
|
|
1044
|
+
it 'creates a HasOneThrough for has_one with :through' do
|
|
1045
|
+
klass.has_one(:store, through: :franchise)
|
|
1046
|
+
assoc = klass.relations['store']
|
|
1047
|
+
expect(assoc).to be_a(Mongoid::Association::Referenced::HasOneThrough)
|
|
1048
|
+
end
|
|
1049
|
+
|
|
1050
|
+
it 'raises when :through is used with belongs_to' do
|
|
1051
|
+
expect do
|
|
1052
|
+
klass.belongs_to(:something, through: :other)
|
|
1053
|
+
end.to raise_error(Mongoid::Errors::InvalidRelationOption)
|
|
1054
|
+
end
|
|
1055
|
+
end
|
|
1030
1056
|
end
|
|
@@ -325,6 +325,23 @@ describe Mongoid::Association::Referenced::BelongsTo::Eager do
|
|
|
325
325
|
eager.map(&:reviewer).should eq [ nil ]
|
|
326
326
|
end
|
|
327
327
|
end
|
|
328
|
+
|
|
329
|
+
context 'when the *_type field has not been registered with the resolver' do
|
|
330
|
+
before do
|
|
331
|
+
Publication::Review.collection.insert_one(
|
|
332
|
+
_id: BSON::ObjectId.new,
|
|
333
|
+
summary: 'tampered',
|
|
334
|
+
reviewer_id: BSON::ObjectId.new,
|
|
335
|
+
reviewer_type: 'Kernel'
|
|
336
|
+
)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
it 'raises UnrecognizedModelAlias' do
|
|
340
|
+
expect do
|
|
341
|
+
Publication::Review.includes(:reviewer).entries
|
|
342
|
+
end.to raise_error(Mongoid::Errors::UnrecognizedModelAlias)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
328
345
|
end
|
|
329
346
|
|
|
330
347
|
context 'when the association has scope' do
|
|
@@ -2161,6 +2161,13 @@ describe Mongoid::Association::Referenced::HasAndBelongsToMany::Proxy do
|
|
|
2161
2161
|
end
|
|
2162
2162
|
end
|
|
2163
2163
|
|
|
2164
|
+
# Whether the conditions carry a pattern decides which branch of remove_all
|
|
2165
|
+
# runs, so anything that has to behave the same on both is exercised twice.
|
|
2166
|
+
conditions_variants = [
|
|
2167
|
+
[ 'no regular expression', nil ],
|
|
2168
|
+
[ 'a regular expression', { name: /Test/ } ]
|
|
2169
|
+
].freeze
|
|
2170
|
+
|
|
2164
2171
|
%i[ delete_all destroy_all ].each do |method|
|
|
2165
2172
|
describe "##{method}" do
|
|
2166
2173
|
context 'when the relation is not polymorphic' do
|
|
@@ -2231,6 +2238,47 @@ describe Mongoid::Association::Referenced::HasAndBelongsToMany::Proxy do
|
|
|
2231
2238
|
expect(person.preferences).to eq([])
|
|
2232
2239
|
end
|
|
2233
2240
|
end
|
|
2241
|
+
|
|
2242
|
+
context 'when the base holds the foreign keys' do
|
|
2243
|
+
# unbind_one pulls ids out of the base's foreign key array and marks
|
|
2244
|
+
# the base dirty, so which documents get unbound is visible here.
|
|
2245
|
+
# Only what the association already held is unbound, and that has to
|
|
2246
|
+
# hold whether or not the conditions carry a regular expression:
|
|
2247
|
+
# scanning under a regexp budget loads the association first, and
|
|
2248
|
+
# that load must not turn into an unbind of its own. See the note in
|
|
2249
|
+
# HasMany::Proxy#remove_all_bounded.
|
|
2250
|
+
let!(:person) do
|
|
2251
|
+
Person.create!.tap do |base|
|
|
2252
|
+
base.preferences.create!(name: 'Testing')
|
|
2253
|
+
base.preferences.create!(name: 'Test')
|
|
2254
|
+
end
|
|
2255
|
+
end
|
|
2256
|
+
|
|
2257
|
+
let(:ids) { person.preference_ids.dup }
|
|
2258
|
+
|
|
2259
|
+
conditions_variants.each do |description, conditions|
|
|
2260
|
+
context "when the conditions carry #{description}" do
|
|
2261
|
+
context 'when the association is already in memory' do
|
|
2262
|
+
it 'unbinds the deleted documents' do
|
|
2263
|
+
person.preferences.send(method, conditions)
|
|
2264
|
+
expect(person.preference_ids).to eq([])
|
|
2265
|
+
expect(person).to be_changed
|
|
2266
|
+
end
|
|
2267
|
+
end
|
|
2268
|
+
|
|
2269
|
+
context 'when the association has not been loaded' do
|
|
2270
|
+
let(:cold) { Person.find(person._id) }
|
|
2271
|
+
|
|
2272
|
+
it 'leaves the foreign keys alone' do
|
|
2273
|
+
expect(ids.length).to eq(2)
|
|
2274
|
+
cold.preferences.send(method, conditions)
|
|
2275
|
+
expect(cold.preference_ids).to eq(ids)
|
|
2276
|
+
expect(cold).not_to be_changed
|
|
2277
|
+
end
|
|
2278
|
+
end
|
|
2279
|
+
end
|
|
2280
|
+
end
|
|
2281
|
+
end
|
|
2234
2282
|
end
|
|
2235
2283
|
end
|
|
2236
2284
|
end
|
|
@@ -984,4 +984,54 @@ describe Mongoid::Association::Referenced::HasAndBelongsToMany do
|
|
|
984
984
|
expect(school.updated_at).to eq(update_time)
|
|
985
985
|
end
|
|
986
986
|
end
|
|
987
|
+
|
|
988
|
+
context 'when the stored foreign key array contains nil', :integration do
|
|
989
|
+
# Associating a target whose custom primary key is unset stores a literal
|
|
990
|
+
# nil in the foreign key array. A nil in an $in matches every document
|
|
991
|
+
# whose primary key field is null or absent, returning documents that were
|
|
992
|
+
# never associated.
|
|
993
|
+
|
|
994
|
+
before(:all) do
|
|
995
|
+
Object.const_set(:NkPost, Class.new do
|
|
996
|
+
include Mongoid::Document
|
|
997
|
+
|
|
998
|
+
store_in collection: 'nk_posts'
|
|
999
|
+
has_and_belongs_to_many :nk_tags, class_name: 'NkTag',
|
|
1000
|
+
primary_key: :slug, inverse_of: nil
|
|
1001
|
+
end)
|
|
1002
|
+
Object.const_set(:NkTag, Class.new do
|
|
1003
|
+
include Mongoid::Document
|
|
1004
|
+
|
|
1005
|
+
store_in collection: 'nk_tags'
|
|
1006
|
+
field :slug, type: String
|
|
1007
|
+
field :name, type: String
|
|
1008
|
+
end)
|
|
1009
|
+
end
|
|
1010
|
+
|
|
1011
|
+
after(:all) do
|
|
1012
|
+
%w[NkPost NkTag].each { |c| Object.send(:remove_const, c) }
|
|
1013
|
+
end
|
|
1014
|
+
|
|
1015
|
+
before { [ NkPost, NkTag ].each(&:delete_all) }
|
|
1016
|
+
|
|
1017
|
+
let!(:tag) { NkTag.create!(slug: 'ruby', name: 'tagged') }
|
|
1018
|
+
let!(:keyless_tag) { NkTag.create!(name: 'never associated') }
|
|
1019
|
+
let!(:post) { NkPost.create!(nk_tags: [ tag, NkTag.create!(name: 'no slug') ]) }
|
|
1020
|
+
|
|
1021
|
+
it 'does not return tags that were never associated' do
|
|
1022
|
+
expect(post.reload.nk_tags.to_a).to eq([ tag ])
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
it 'does not put nil in the membership query' do
|
|
1026
|
+
expect(post.reload.nk_tags.criteria.selector['slug']['$in']).not_to include(nil)
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
context 'when every stored key is nil' do
|
|
1030
|
+
let!(:post) { NkPost.create!(nk_tags: [ NkTag.create!(name: 'no slug') ]) }
|
|
1031
|
+
|
|
1032
|
+
it 'returns nothing rather than everything' do
|
|
1033
|
+
expect(post.reload.nk_tags.to_a).to eq([])
|
|
1034
|
+
end
|
|
1035
|
+
end
|
|
1036
|
+
end
|
|
987
1037
|
end
|
|
@@ -323,6 +323,39 @@ describe Mongoid::Association::Referenced::HasMany::Eager do
|
|
|
323
323
|
expect(eager.ordered_posts.max(:rating)).to eq(30)
|
|
324
324
|
end
|
|
325
325
|
end
|
|
326
|
+
|
|
327
|
+
context 'when the field name is a method name' do
|
|
328
|
+
it 'does not call the method for sum' do
|
|
329
|
+
eager.ordered_posts.each { |post| expect(post).not_to receive(:destroy) }
|
|
330
|
+
expect(eager.ordered_posts.sum(:destroy)).to eq(0)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it 'returns zero from sum' do
|
|
334
|
+
expect(eager.ordered_posts.sum(:object_id)).to eq(0)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
it 'returns nil from avg' do
|
|
338
|
+
expect(eager.ordered_posts.avg(:object_id)).to be_nil
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
it 'returns nil from min' do
|
|
342
|
+
expect(eager.ordered_posts.min(:object_id)).to be_nil
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
it 'returns nil from max' do
|
|
346
|
+
expect(eager.ordered_posts.max(:object_id)).to be_nil
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
context 'when the field is not defined' do
|
|
351
|
+
it 'returns zero from sum' do
|
|
352
|
+
expect(eager.ordered_posts.sum(:not_a_field)).to eq(0)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
it 'returns nil from avg' do
|
|
356
|
+
expect(eager.ordered_posts.avg(:not_a_field)).to be_nil
|
|
357
|
+
end
|
|
358
|
+
end
|
|
326
359
|
end
|
|
327
360
|
end
|
|
328
361
|
end
|