mongoid 9.0.3 → 9.0.5

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 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