mongoid 9.0.3 → 9.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 421d53636b0e90abcfa52bf637229588d434ff73f14574b1d316b72009f7236b
4
- data.tar.gz: fafa07a7cc9b17de0abbb140ca3d021e3041bfe00b67a424c81f6c15018e607d
3
+ metadata.gz: bb0499892233035ab6e0b07f3df4d08dfe7129c1b32830288e9453478b8aba9d
4
+ data.tar.gz: 5b02bd092e46dfb2aadfa580f3c8b3b4548b82d2e354eba4768ee6c1772eed98
5
5
  SHA512:
6
- metadata.gz: d4f2011d1f85178a8693d653518f06e7ad55c1363958e3577f80d040e3986b595bdcaade0e71696ff762e008c195ee23b4579458708bb9e916f7ddfa8e3b9352
7
- data.tar.gz: 8b981e1bc4e904085ecedfe459d31c91d06ed353ba0ceef3d67865ea195f31a36539284246b54537fc5af8b54eafcf1a60415eb88c4850d7cdf3af378d449206
6
+ metadata.gz: 96c1585ccba204b4c316920662aa5e750f3e0dd46a7849ca142d8b42d8b94ba8516c16ae9a194a30dd648937b94ec9247371035ddf951d93ec251eeb06fa15cd
7
+ data.tar.gz: 057f7cf03935fe694f385f186d74e3468d928f44dac47695ab58319e050ae86c25039083c1bf3debebdf8fdea9025347365b0e341040898cfdf2e09acbf72b31
@@ -42,6 +42,9 @@ module Mongoid
42
42
  docs_map = {}
43
43
  queue = [ klass.to_s ]
44
44
 
45
+ # account for single-collection inheritance
46
+ queue.push(klass.root_class.to_s) if klass != klass.root_class
47
+
45
48
  while klass = queue.shift
46
49
  if as = assoc_map.delete(klass)
47
50
  as.each do |assoc|
@@ -23,7 +23,7 @@ module Mongoid
23
23
  # @return [ true | false ] If the document is new, or if the field is not
24
24
  # readonly.
25
25
  def attribute_writable?(name)
26
- new_record? || (!readonly_attributes.include?(name) && _loaded?(name))
26
+ new_record? || (!self.class.readonly_attributes.include?(name) && _loaded?(name))
27
27
  end
28
28
 
29
29
  private
@@ -63,12 +63,17 @@ module Mongoid
63
63
  # end
64
64
  #
65
65
  # @param [ Symbol... ] *names The names of the fields.
66
+ # @note When a parent class contains readonly attributes and is then
67
+ # inherited by a child class, the child class will inherit the
68
+ # parent's readonly attributes at the time of its creation.
69
+ # Updating the parent does not propagate down to child classes after wards.
66
70
  def attr_readonly(*names)
71
+ self.readonly_attributes = self.readonly_attributes.dup
67
72
  names.each do |name|
68
- readonly_attributes << database_field_name(name)
73
+ self.readonly_attributes << database_field_name(name)
69
74
  end
70
75
  end
71
76
  end
72
77
  end
73
78
  end
74
- end
79
+ end
@@ -553,7 +553,7 @@ module Mongoid
553
553
  # @return [ Selectable ] The new selectable.
554
554
  def not(*criteria)
555
555
  if criteria.empty?
556
- dup.tap { |query| query.negating = true }
556
+ dup.tap { |query| query.negating = !query.negating }
557
557
  else
558
558
  criteria.compact.inject(self.clone) do |c, new_s|
559
559
  if new_s.is_a?(Selectable)
@@ -18,6 +18,7 @@ module Mongoid
18
18
  #
19
19
  # @return [ Integer ] -1, 0, 1.
20
20
  def <=>(other)
21
+ return super unless other.is_a?(Mongoid::Equality)
21
22
  attributes["_id"].to_s <=> other.attributes["_id"].to_s
22
23
  end
23
24
 
@@ -152,9 +152,13 @@ module Mongoid
152
152
  # @api private
153
153
  def _mongoid_run_child_callbacks(kind, children: nil, &block)
154
154
  if Mongoid::Config.around_callbacks_for_embeds
155
- _mongoid_run_child_callbacks_with_around(kind, children: children, &block)
155
+ _mongoid_run_child_callbacks_with_around(kind,
156
+ children: children,
157
+ &block)
156
158
  else
157
- _mongoid_run_child_callbacks_without_around(kind, children: children, &block)
159
+ _mongoid_run_child_callbacks_without_around(kind,
160
+ children: children,
161
+ &block)
158
162
  end
159
163
  end
160
164
 
@@ -235,9 +239,6 @@ module Mongoid
235
239
  return false if env.halted
236
240
  env.value = !env.halted
237
241
  callback_list << [next_sequence, env]
238
- if (grandchildren = child.send(:cascadable_children, kind))
239
- _mongoid_run_child_before_callbacks(kind, children: grandchildren, callback_list: callback_list)
240
- end
241
242
  end
242
243
  callback_list
243
244
  end
@@ -10,6 +10,13 @@ module Mongoid
10
10
  # (See #model_paths.)
11
11
  DEFAULT_MODEL_PATHS = %w( ./app/models ./lib/models ).freeze
12
12
 
13
+ # The default list of glob patterns that match paths to ignore when loading
14
+ # models. Defaults to '*/models/concerns/*', which Rails uses for extensions
15
+ # to models (and which cause errors when loaded out of order).
16
+ #
17
+ # See #ignore_patterns.
18
+ DEFAULT_IGNORE_PATTERNS = %w( */models/concerns/* ).freeze
19
+
13
20
  # Search a list of model paths to get every model and require it, so
14
21
  # that indexing and inheritance work in both development and production
15
22
  # with the same results.
@@ -24,17 +31,47 @@ module Mongoid
24
31
  # for model files. These must either be absolute paths, or relative to
25
32
  # the current working directory.
26
33
  def load_models(paths = model_paths)
27
- paths.each do |path|
28
- if preload_models.resizable?
29
- files = preload_models.map { |model| "#{path}/#{model.underscore}.rb" }
34
+ files = files_under_paths(paths)
35
+
36
+ files.sort.each do |file|
37
+ load_model(file)
38
+ end
39
+
40
+ nil
41
+ end
42
+
43
+ # Given a list of paths, return all ruby files under that path (or, if
44
+ # `preload_models` is a list of model names, returns only the files for
45
+ # those named models).
46
+ #
47
+ # @param [ Array<String> ] paths the list of paths to search
48
+ #
49
+ # @return [ Array<String> ] the normalized file names, suitable for loading
50
+ # via `require_dependency` or `require`.
51
+ def files_under_paths(paths)
52
+ paths.flat_map { |path| files_under_path(path) }
53
+ end
54
+
55
+ # Given a single path, returns all ruby files under that path (or, if
56
+ # `preload_models` is a list of model names, returns only the files for
57
+ # those named models).
58
+ #
59
+ # @param [ String ] path the path to search
60
+ #
61
+ # @return [ Array<String> ] the normalized file names, suitable for loading
62
+ # via `require_dependency` or `require`.
63
+ def files_under_path(path)
64
+ files = if preload_models.resizable?
65
+ preload_models.
66
+ map { |model| "#{path}/#{model.underscore}.rb" }.
67
+ select { |file_name| File.exists?(file_name) }
30
68
  else
31
- files = Dir.glob("#{path}/**/*.rb")
69
+ Dir.glob("#{path}/**/*.rb").
70
+ reject { |file_name| ignored?(file_name) }
32
71
  end
33
72
 
34
- files.sort.each do |file|
35
- load_model(file.gsub(/^#{path}\// , "").gsub(/\.rb$/, ""))
36
- end
37
- end
73
+ # strip the path and the suffix from each entry
74
+ files.map { |file| file.gsub(/^#{path}\// , "").gsub(/\.rb$/, "") }
38
75
  end
39
76
 
40
77
  # A convenience method for loading a model's file. If Rails'
@@ -71,6 +108,14 @@ module Mongoid
71
108
  DEFAULT_MODEL_PATHS
72
109
  end
73
110
 
111
+ # Returns the array of glob patterns that determine whether a given
112
+ # path should be ignored by the model loader.
113
+ #
114
+ # @return [ Array<String> ] the array of ignore patterns
115
+ def ignore_patterns
116
+ @ignore_patterns ||= DEFAULT_IGNORE_PATTERNS.dup
117
+ end
118
+
74
119
  # Sets the model paths to the given array of paths. These are the paths
75
120
  # where the application's model definitions are located.
76
121
  #
@@ -78,6 +123,25 @@ module Mongoid
78
123
  def model_paths=(paths)
79
124
  @model_paths = paths
80
125
  end
126
+
127
+ # Sets the ignore patterns to the given array of patterns. These are glob
128
+ # patterns that determine whether a given path should be ignored by the
129
+ # model loader or not.
130
+ #
131
+ # @param [ Array<String> ] patterns The list of glob patterns
132
+ def ignore_patterns=(patterns)
133
+ @ignore_patterns = patterns
134
+ end
135
+
136
+ # Returns true if the given file path matches any of the ignore patterns.
137
+ #
138
+ # @param [ String ] file_path The file path to consider
139
+ #
140
+ # @return [ true | false ] whether or not the given file path should be
141
+ # ignored.
142
+ def ignored?(file_path)
143
+ ignore_patterns.any? { |pattern| File.fnmatch?(pattern, file_path) }
144
+ end
81
145
  end
82
146
 
83
147
  end
@@ -138,7 +138,7 @@ module Mongoid
138
138
  # @return [ Symbol ] The client name for this persistence
139
139
  # context.
140
140
  def client_name
141
- @client_name ||= options[:client] ||
141
+ @client_name ||= __evaluate__(options[:client]) ||
142
142
  Threaded.client_override ||
143
143
  __evaluate__(storage_options[:client])
144
144
  end
@@ -23,13 +23,20 @@ module Mongoid
23
23
  # @example Set the created at time.
24
24
  # person.set_created_at
25
25
  def set_created_at
26
- if !timeless? && !created_at
26
+ if able_to_set_created_at?
27
27
  now = Time.current
28
28
  self.updated_at = now if is_a?(Updated) && !updated_at_changed?
29
29
  self.created_at = now
30
30
  end
31
31
  clear_timeless_option
32
32
  end
33
+
34
+ # Is the created timestamp able to be set?
35
+ #
36
+ # @return [ true, false ] If the timestamp can be set.
37
+ def able_to_set_created_at?
38
+ !frozen? && !timeless? && !created_at
39
+ end
33
40
  end
34
41
  end
35
42
  end
@@ -8,6 +8,29 @@ module Mongoid
8
8
  # around traversing the document graph.
9
9
  module Traversable
10
10
  extend ActiveSupport::Concern
11
+ # This code is extracted from ActiveSupport so that we do not depend on
12
+ # their private API that may change at any time.
13
+ # This code should be reviewed and maybe removed when implementing
14
+ # https://jira.mongodb.org/browse/MONGOID-5832
15
+ class << self
16
+ # @api private
17
+ def __redefine(owner, name, value)
18
+ if owner.singleton_class?
19
+ owner.redefine_method(name) { value }
20
+ owner.send(:public, name)
21
+ end
22
+ owner.redefine_singleton_method(name) { value }
23
+ owner.singleton_class.send(:public, name)
24
+ owner.redefine_singleton_method("#{name}=") do |new_value|
25
+ if owner.equal?(self)
26
+ value = new_value
27
+ else
28
+ ::Mongoid::Traversable.redefine(self, name, new_value)
29
+ end
30
+ end
31
+ owner.singleton_class.send(:public, "#{name}=")
32
+ end
33
+ end
11
34
 
12
35
  # Class-level methods for the Traversable behavior.
13
36
  module ClassMethods
@@ -21,6 +44,18 @@ module Mongoid
21
44
  !!(superclass < Mongoid::Document)
22
45
  end
23
46
 
47
+ # Returns the root class of the STI tree that the current
48
+ # class participates in. If the class is not an STI subclass, this
49
+ # returns the class itself.
50
+ #
51
+ # @return [ Mongoid::Document ] the root of the STI tree
52
+ def root_class
53
+ root = self
54
+ root = root.superclass while root.hereditary?
55
+
56
+ root
57
+ end
58
+
24
59
  # When inheriting, we want to copy the fields from the parent class and
25
60
  # set the on the child to start, mimicking the behavior of the old
26
61
  # class_inheritable_accessor that was deprecated in Rails edge.
@@ -105,11 +140,7 @@ module Mongoid
105
140
  if value
106
141
  Mongoid::Fields::Validators::Macro.validate_field_name(self, value)
107
142
  value = value.to_s
108
- if defined?(::ActiveSupport::ClassAttribute)
109
- ::ActiveSupport::ClassAttribute.redefine(self, 'discriminator_key', value)
110
- else
111
- super
112
- end
143
+ ::Mongoid::Traversable.__redefine(self, 'discriminator_key', value)
113
144
  else
114
145
  # When discriminator key is set to nil, replace the class's definition
115
146
  # of the discriminator key reader (provided by class_attribute earlier)
@@ -74,7 +74,7 @@ module Mongoid
74
74
  # use map.all? instead of just all?, because all? will do short-circuit
75
75
  # evaluation and terminate on the first failed validation.
76
76
  list.map do |value|
77
- if value && !value.flagged_for_destroy? && (!value.persisted? || value.changed?)
77
+ if value && !value.flagged_for_destroy?
78
78
  value.validated? ? true : value.valid?
79
79
  else
80
80
  true
@@ -2,5 +2,5 @@
2
2
  # rubocop:todo all
3
3
 
4
4
  module Mongoid
5
- VERSION = "9.0.3"
5
+ VERSION = "9.0.5"
6
6
  end
@@ -15,14 +15,36 @@ describe Mongoid::Association::EagerLoadable do
15
15
  Mongoid::Contextual::Mongo.new(criteria)
16
16
  end
17
17
 
18
+ let(:association_host) { Account }
19
+
18
20
  let(:inclusions) do
19
21
  includes.map do |key|
20
- Account.reflect_on_association(key)
22
+ association_host.reflect_on_association(key)
21
23
  end
22
24
  end
23
25
 
24
26
  let(:doc) { criteria.first }
25
27
 
28
+ context 'when root is an STI subclass' do
29
+ # Driver has_one Vehicle
30
+ # Vehicle belongs_to Driver
31
+ # Truck is a Vehicle
32
+
33
+ before do
34
+ Driver.create!(vehicle: Truck.new)
35
+ end
36
+
37
+ let(:criteria) { Truck.all }
38
+ let(:includes) { %i[ driver ] }
39
+ let(:association_host) { Truck }
40
+
41
+ it 'preloads the driver' do
42
+ expect(doc.ivar(:driver)).to be false
43
+ context.preload(inclusions, [ doc ])
44
+ expect(doc.ivar(:driver)).to be == Driver.first
45
+ end
46
+ end
47
+
26
48
  context "when belongs_to" do
27
49
 
28
50
  let!(:account) do
@@ -43,7 +65,7 @@ describe Mongoid::Association::EagerLoadable do
43
65
  it "preloads the parent" do
44
66
  expect(doc.ivar(:person)).to be false
45
67
  context.preload(inclusions, [doc])
46
- expect(doc.ivar(:person)).to eq(doc.person)
68
+ expect(doc.ivar(:person)).to be == person
47
69
  end
48
70
  end
49
71
 
@@ -115,6 +115,66 @@ describe Mongoid::Association do
115
115
  expect(name).to_not be_an_embedded_many
116
116
  end
117
117
  end
118
+
119
+ context "when validation depends on association" do
120
+ before(:all) do
121
+ class Author
122
+ include Mongoid::Document
123
+ embeds_many :books, cascade_callbacks: true
124
+ field :condition, type: Boolean
125
+ end
126
+
127
+ class Book
128
+ include Mongoid::Document
129
+ embedded_in :author
130
+ validate :parent_condition_is_not_true
131
+
132
+ def parent_condition_is_not_true
133
+ return unless author&.condition
134
+ errors.add :base, "Author condition is true."
135
+ end
136
+ end
137
+
138
+ Author.delete_all
139
+ Book.delete_all
140
+ end
141
+
142
+ let(:author) { Author.new }
143
+ let(:book) { Book.new }
144
+
145
+ context "when author is not persisted" do
146
+ it "is valid without books" do
147
+ expect(author.valid?).to be true
148
+ end
149
+
150
+ it "is valid with a book" do
151
+ author.books << book
152
+ expect(author.valid?).to be true
153
+ end
154
+
155
+ it "is not valid when condition is true with a book" do
156
+ author.condition = true
157
+ author.books << book
158
+ expect(author.valid?).to be false
159
+ end
160
+ end
161
+
162
+ context "when author is persisted" do
163
+ before do
164
+ author.books << book
165
+ author.save
166
+ end
167
+
168
+ it "remains valid initially" do
169
+ expect(author.valid?).to be true
170
+ end
171
+
172
+ it "becomes invalid when condition is set to true" do
173
+ author.update_attributes(condition: true)
174
+ expect(author.valid?).to be false
175
+ end
176
+ end
177
+ end
118
178
  end
119
179
 
120
180
  describe "#embedded_one?" do
@@ -266,7 +266,26 @@ describe Mongoid::Attributes::Readonly do
266
266
  expect(child.mother).to be_nil
267
267
  end
268
268
  end
269
+ end
270
+
271
+ context "when a subclass inherits readonly fields" do
272
+ let(:attributes) do
273
+ [:title, :terms]
274
+ end
275
+
276
+ before do
277
+ class OldPerson < Person
278
+ attr_readonly :age
279
+ end
280
+ end
269
281
 
282
+ it "ensures subclass inherits the readonly attributes from parent" do
283
+ expect(OldPerson.readonly_attributes.to_a).to include("title","terms")
284
+ end
285
+
286
+ it "ensures subclass does not modify parent's readonly attributes" do
287
+ expect(Person.readonly_attributes.to_a).not_to include("age")
288
+ end
270
289
  end
271
290
  end
272
291
  end
@@ -1939,6 +1939,35 @@ describe Mongoid::Criteria::Queryable::Selectable do
1939
1939
  end
1940
1940
  end
1941
1941
 
1942
+ describe "#not" do
1943
+ context "when negating a criterion" do
1944
+ let(:selection) do
1945
+ query.not(field: /value/)
1946
+ end
1947
+
1948
+ it "adds the $not selector" do
1949
+ expect(selection.selector).to eq({
1950
+ "field" => { "$not" => /value/ }
1951
+ })
1952
+ end
1953
+
1954
+ it "returns a cloned query" do
1955
+ expect(selection).to_not equal(query)
1956
+ end
1957
+
1958
+ context "when toggling negation state" do
1959
+ it "negates the negating value" do
1960
+ expect(query.negating).to be_nil
1961
+ negated_query = query.not
1962
+ expect(negated_query.negating).to be true
1963
+ double_negated_query = negated_query.not
1964
+ expect(double_negated_query.negating).to be false
1965
+ end
1966
+ end
1967
+ end
1968
+ end
1969
+
1970
+
1942
1971
  describe Symbol do
1943
1972
 
1944
1973
  describe "#all" do
@@ -189,6 +189,12 @@ describe Mongoid::Equality do
189
189
  it "compares based on the document id" do
190
190
  expect(first <=> second).to eq(-1)
191
191
  end
192
+
193
+ it "doesn't break if one isn't a document" do
194
+ expect do
195
+ first <=> "Foo"
196
+ end.to_not raise_error
197
+ end
192
198
  end
193
199
 
194
200
  describe "#eql?" do
@@ -389,6 +389,84 @@ describe Mongoid::Interceptable do
389
389
  end
390
390
  end
391
391
  end
392
+
393
+ context 'with embedded grandchildren' do
394
+ IS = InterceptableSpec
395
+
396
+ context 'when creating' do
397
+ let(:registry) { IS::CallbackRegistry.new(only: %i[ before_save ]) }
398
+
399
+ let(:expected_calls) do
400
+ [
401
+ # the parent
402
+ [ IS::CbParent, :before_save ],
403
+
404
+ # the immediate child of the parent
405
+ [ IS::CbCascadedNode, :before_save ],
406
+
407
+ # the grandchild of the parent
408
+ [ IS::CbCascadedNode, :before_save ],
409
+ ]
410
+ end
411
+
412
+ let!(:parent) do
413
+ parent = IS::CbParent.new(registry)
414
+ child = IS::CbCascadedNode.new(registry)
415
+ grandchild = IS::CbCascadedNode.new(registry)
416
+
417
+ child.cb_cascaded_nodes = [ grandchild ]
418
+ parent.cb_cascaded_nodes = [ child ]
419
+
420
+ parent.tap(&:save)
421
+ end
422
+
423
+ it 'should cascade callbacks to grandchildren' do
424
+ expect(registry.calls).to be == expected_calls
425
+ end
426
+ end
427
+
428
+ context 'when updating' do
429
+ let(:registry) { IS::CallbackRegistry.new(only: %i[ before_update ]) }
430
+
431
+ let(:expected_calls) do
432
+ [
433
+ # the parent
434
+ [ IS::CbParent, :before_update ],
435
+
436
+ # the immediate child of the parent
437
+ [ IS::CbCascadedNode, :before_update ],
438
+
439
+ # the grandchild of the parent
440
+ [ IS::CbCascadedNode, :before_update ],
441
+ ]
442
+ end
443
+
444
+ let!(:parent) do
445
+ parent = IS::CbParent.new(nil)
446
+ child = IS::CbCascadedNode.new(nil)
447
+ grandchild = IS::CbCascadedNode.new(nil)
448
+
449
+ child.cb_cascaded_nodes = [ grandchild ]
450
+ parent.cb_cascaded_nodes = [ child ]
451
+
452
+ parent.save
453
+
454
+ parent.callback_registry = registry
455
+ child.callback_registry = registry
456
+ grandchild.callback_registry = registry
457
+
458
+ parent.name = 'updated'
459
+ child.name = 'updated'
460
+ grandchild.name = 'updated'
461
+
462
+ parent.tap(&:save)
463
+ end
464
+
465
+ it 'should cascade callbacks to grandchildren' do
466
+ expect(registry.calls).to be == expected_calls
467
+ end
468
+ end
469
+ end
392
470
  end
393
471
 
394
472
  describe ".before_destroy" do
@@ -1,11 +1,13 @@
1
1
  # rubocop:todo all
2
2
  module InterceptableSpec
3
3
  class CallbackRegistry
4
- def initialize
4
+ def initialize(only: [])
5
5
  @calls = []
6
+ @only = only
6
7
  end
7
8
 
8
9
  def record_call(cls, cb)
10
+ return unless @only.empty? || @only.any? { |pat| pat == cb }
9
11
  @calls << [cls, cb]
10
12
  end
11
13
 
@@ -16,6 +18,8 @@ module InterceptableSpec
16
18
  extend ActiveSupport::Concern
17
19
 
18
20
  included do
21
+ field :name, type: String
22
+
19
23
  %i(
20
24
  validation save create update
21
25
  ).each do |what|
@@ -35,199 +39,110 @@ module InterceptableSpec
35
39
  end
36
40
  end
37
41
  end
38
- end
39
-
40
- class CbHasOneParent
41
- include Mongoid::Document
42
42
 
43
- has_one :child, autosave: true, class_name: "CbHasOneChild", inverse_of: :parent
43
+ attr_accessor :callback_registry
44
44
 
45
- def initialize(callback_registry)
45
+ def initialize(callback_registry, *args, **kwargs)
46
46
  @callback_registry = callback_registry
47
- super()
47
+ super(*args, **kwargs)
48
48
  end
49
+ end
49
50
 
50
- attr_accessor :callback_registry
51
-
51
+ module RootInsertable
52
52
  def insert_as_root
53
53
  @callback_registry&.record_call(self.class, :insert_into_database)
54
54
  super
55
55
  end
56
+ end
56
57
 
58
+ class CbHasOneParent
59
+ include Mongoid::Document
57
60
  include CallbackTracking
61
+ include RootInsertable
62
+
63
+ has_one :child, autosave: true, class_name: "CbHasOneChild", inverse_of: :parent
58
64
  end
59
65
 
60
66
  class CbHasOneChild
61
67
  include Mongoid::Document
68
+ include CallbackTracking
62
69
 
63
70
  belongs_to :parent, class_name: "CbHasOneParent", inverse_of: :child
64
-
65
- def initialize(callback_registry)
66
- @callback_registry = callback_registry
67
- super()
68
- end
69
-
70
- attr_accessor :callback_registry
71
-
72
- include CallbackTracking
73
71
  end
74
72
 
75
73
  class CbHasManyParent
76
74
  include Mongoid::Document
75
+ include CallbackTracking
76
+ include RootInsertable
77
77
 
78
78
  has_many :children, autosave: true, class_name: "CbHasManyChild", inverse_of: :parent
79
-
80
- def initialize(callback_registry)
81
- @callback_registry = callback_registry
82
- super()
83
- end
84
-
85
- attr_accessor :callback_registry
86
-
87
- def insert_as_root
88
- @callback_registry&.record_call(self.class, :insert_into_database)
89
- super
90
- end
91
-
92
- include CallbackTracking
93
79
  end
94
80
 
95
81
  class CbHasManyChild
96
82
  include Mongoid::Document
83
+ include CallbackTracking
97
84
 
98
85
  belongs_to :parent, class_name: "CbHasManyParent", inverse_of: :children
99
-
100
- def initialize(callback_registry)
101
- @callback_registry = callback_registry
102
- super()
103
- end
104
-
105
- attr_accessor :callback_registry
106
-
107
- include CallbackTracking
108
86
  end
109
87
 
110
88
  class CbEmbedsOneParent
111
89
  include Mongoid::Document
90
+ include CallbackTracking
91
+ include RootInsertable
112
92
 
113
93
  field :name
114
94
 
115
95
  embeds_one :child, cascade_callbacks: true, class_name: "CbEmbedsOneChild", inverse_of: :parent
116
-
117
- def initialize(callback_registry)
118
- @callback_registry = callback_registry
119
- super()
120
- end
121
-
122
- attr_accessor :callback_registry
123
-
124
- def insert_as_root
125
- @callback_registry&.record_call(self.class, :insert_into_database)
126
- super
127
- end
128
-
129
- include CallbackTracking
130
96
  end
131
97
 
132
98
  class CbEmbedsOneChild
133
99
  include Mongoid::Document
100
+ include CallbackTracking
134
101
 
135
102
  field :age
136
103
 
137
104
  embedded_in :parent, class_name: "CbEmbedsOneParent", inverse_of: :child
138
-
139
- def initialize(callback_registry)
140
- @callback_registry = callback_registry
141
- super()
142
- end
143
-
144
- attr_accessor :callback_registry
145
-
146
- include CallbackTracking
147
105
  end
148
106
 
149
107
  class CbEmbedsManyParent
150
108
  include Mongoid::Document
109
+ include CallbackTracking
110
+ include RootInsertable
151
111
 
152
112
  embeds_many :children, cascade_callbacks: true, class_name: "CbEmbedsManyChild", inverse_of: :parent
153
-
154
- def initialize(callback_registry)
155
- @callback_registry = callback_registry
156
- super()
157
- end
158
-
159
- attr_accessor :callback_registry
160
-
161
- def insert_as_root
162
- @callback_registry&.record_call(self.class, :insert_into_database)
163
- super
164
- end
165
-
166
- include CallbackTracking
167
113
  end
168
114
 
169
115
  class CbEmbedsManyChild
170
116
  include Mongoid::Document
117
+ include CallbackTracking
171
118
 
172
119
  embedded_in :parent, class_name: "CbEmbedsManyParent", inverse_of: :children
173
-
174
- def initialize(callback_registry)
175
- @callback_registry = callback_registry
176
- super()
177
- end
178
-
179
- attr_accessor :callback_registry
180
-
181
- include CallbackTracking
182
120
  end
183
121
 
184
122
  class CbParent
185
123
  include Mongoid::Document
186
-
187
- def initialize(callback_registry)
188
- @callback_registry = callback_registry
189
- super()
190
- end
191
-
192
- attr_accessor :callback_registry
124
+ include CallbackTracking
193
125
 
194
126
  embeds_many :cb_children
195
127
  embeds_many :cb_cascaded_children, cascade_callbacks: true
196
-
197
- include CallbackTracking
128
+ embeds_many :cb_cascaded_nodes, cascade_callbacks: true, as: :parent
198
129
  end
199
130
 
200
131
  class CbChild
201
132
  include Mongoid::Document
133
+ include CallbackTracking
202
134
 
203
135
  embedded_in :cb_parent
204
-
205
- def initialize(callback_registry, options)
206
- @callback_registry = callback_registry
207
- super(options)
208
- end
209
-
210
- attr_accessor :callback_registry
211
-
212
- include CallbackTracking
213
136
  end
214
137
 
215
138
  class CbCascadedChild
216
139
  include Mongoid::Document
140
+ include CallbackTracking
217
141
 
218
142
  embedded_in :cb_parent
219
143
 
220
- def initialize(callback_registry, options)
221
- @callback_registry = callback_registry
222
- super(options)
223
- end
224
-
225
- attr_accessor :callback_registry
226
-
227
144
  before_save :test_mongoid_state
228
145
 
229
- include CallbackTracking
230
-
231
146
  private
232
147
 
233
148
  # Helps test that cascading child callbacks have access to the Mongoid
@@ -238,6 +153,15 @@ module InterceptableSpec
238
153
  Mongoid::Threaded.stack('interceptable').push(self)
239
154
  end
240
155
  end
156
+
157
+ class CbCascadedNode
158
+ include Mongoid::Document
159
+ include CallbackTracking
160
+
161
+ embedded_in :parent, polymorphic: true
162
+
163
+ embeds_many :cb_cascaded_nodes, cascade_callbacks: true, as: :parent
164
+ end
241
165
  end
242
166
 
243
167
  class InterceptableBand
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mongoid::Loadable do
6
+ let(:lib_dir) { Pathname.new('../../lib').realpath(__dir__) }
7
+
8
+ shared_context 'with ignore_patterns' do
9
+ around do |example|
10
+ saved = Mongoid.ignore_patterns
11
+ Mongoid.ignore_patterns = ignore_patterns
12
+ example.run
13
+ ensure
14
+ Mongoid.ignore_patterns = saved
15
+ end
16
+ end
17
+
18
+ describe '#ignore_patterns' do
19
+ context 'when not explicitly set' do
20
+ it 'equals the default list of ignore patterns' do
21
+ expect(Mongoid.ignore_patterns).to eq Mongoid::Loadable::DEFAULT_IGNORE_PATTERNS
22
+ end
23
+ end
24
+
25
+ context 'when explicitly set' do
26
+ include_context 'with ignore_patterns'
27
+
28
+ let(:ignore_patterns) { %w[ pattern1 pattern2 ] }
29
+
30
+ it 'equals the list of specified patterns' do
31
+ expect(Mongoid.ignore_patterns).to eq ignore_patterns
32
+ end
33
+ end
34
+ end
35
+
36
+ describe '#files_under_path' do
37
+ let(:results) { Mongoid.files_under_path(lib_dir) }
38
+
39
+ include_context 'with ignore_patterns'
40
+
41
+ context 'when ignore_patterns is empty' do
42
+ let(:ignore_patterns) { [] }
43
+
44
+ it 'returns all ruby files' do
45
+ expect(results.length).to be > 10 # should be a bunch of them
46
+ expect(results).to include('rails/mongoid')
47
+ end
48
+ end
49
+
50
+ context 'when ignore_patterns is not empty' do
51
+ let(:ignore_patterns) { %w[ */rails/* ] }
52
+
53
+ it 'omits the ignored paths' do
54
+ expect(results.length).to be > 10 # should be a bunch of them
55
+ expect(results).not_to include('rails/mongoid')
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#files_under_paths' do
61
+ let(:paths) { [ lib_dir.join('mongoid'), lib_dir.join('rails') ] }
62
+ let(:results) { Mongoid.files_under_paths(paths) }
63
+
64
+ include_context 'with ignore_patterns'
65
+
66
+ context 'when ignore_patterns is empty' do
67
+ let(:ignore_patterns) { [] }
68
+
69
+ it 'returns all ruby files' do
70
+ expect(results.length).to be > 10 # should be a bunch
71
+ expect(results).to include('generators/mongoid/model/model_generator')
72
+ expect(results).to include('fields/encrypted')
73
+ end
74
+ end
75
+
76
+ context 'when ignore_patterns is not empty' do
77
+ let(:ignore_patterns) { %w[ */model/* */fields/* ] }
78
+
79
+ it 'returns all ruby files' do
80
+ expect(results.length).to be > 10 # should be a bunch
81
+ expect(results).not_to include('generators/mongoid/model/model_generator')
82
+ expect(results).not_to include('fields/encrypted')
83
+ end
84
+ end
85
+ end
86
+ end
@@ -584,6 +584,14 @@ describe Mongoid::PersistenceContext do
584
584
  expect(persistence_context.client).to eq(Mongoid::Clients.with_name(:alternative))
585
585
  end
586
586
 
587
+ context 'when the client option is a proc' do
588
+ let(:options) { { client: -> { :alternative } } }
589
+
590
+ it 'evaluates the proc' do
591
+ expect(persistence_context.client).to eq(Mongoid::Clients.with_name(:alternative))
592
+ end
593
+ end
594
+
587
595
  context 'when there is a client override' do
588
596
  persistence_context_override :client, :other
589
597
 
@@ -44,4 +44,27 @@ describe Mongoid::Timestamps::Created do
44
44
  expect(quiz.created_at).to be_within(10).of(Time.now.utc)
45
45
  end
46
46
  end
47
+
48
+ context "when the document is destroyed" do
49
+ let(:book) do
50
+ Book.create!
51
+ end
52
+
53
+ before do
54
+ Cover.before_save do
55
+ destroy if title == "delete me"
56
+ end
57
+ end
58
+
59
+ after do
60
+ Cover.reset_callbacks(:save)
61
+ end
62
+
63
+ it "does not set the created_at timestamp" do
64
+ book.covers << Cover.new(title: "delete me")
65
+ expect {
66
+ book.save
67
+ }.not_to raise_error
68
+ end
69
+ end
47
70
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.0.3
4
+ version: 9.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - The MongoDB Ruby Team
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-11-12 00:00:00.000000000 Z
10
+ date: 2025-01-30 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activemodel
@@ -830,6 +829,7 @@ files:
830
829
  - spec/mongoid/inspectable_spec.rb
831
830
  - spec/mongoid/interceptable_spec.rb
832
831
  - spec/mongoid/interceptable_spec_models.rb
832
+ - spec/mongoid/loadable_spec.rb
833
833
  - spec/mongoid/loading_spec.rb
834
834
  - spec/mongoid/loggable_spec.rb
835
835
  - spec/mongoid/matcher/extract_attribute_data/numeric_keys.yml
@@ -1188,7 +1188,6 @@ metadata:
1188
1188
  documentation_uri: https://www.mongodb.com/docs/mongoid/
1189
1189
  homepage_uri: https://mongoid.org/
1190
1190
  source_code_uri: https://github.com/mongodb/mongoid
1191
- post_install_message:
1192
1191
  rdoc_options: []
1193
1192
  require_paths:
1194
1193
  - lib
@@ -1203,8 +1202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1203
1202
  - !ruby/object:Gem::Version
1204
1203
  version: 1.3.6
1205
1204
  requirements: []
1206
- rubygems_version: 3.4.19
1207
- signing_key:
1205
+ rubygems_version: 3.6.3
1208
1206
  specification_version: 4
1209
1207
  summary: Elegant Persistence in Ruby for MongoDB.
1210
1208
  test_files:
@@ -1552,6 +1550,7 @@ test_files:
1552
1550
  - spec/mongoid/inspectable_spec.rb
1553
1551
  - spec/mongoid/interceptable_spec.rb
1554
1552
  - spec/mongoid/interceptable_spec_models.rb
1553
+ - spec/mongoid/loadable_spec.rb
1555
1554
  - spec/mongoid/loading_spec.rb
1556
1555
  - spec/mongoid/loggable_spec.rb
1557
1556
  - spec/mongoid/matcher/extract_attribute_data/numeric_keys.yml