mongoid 7.0.2 → 7.0.3

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/mongoid/association/referenced/eager.rb +4 -1
  4. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +3 -1
  5. data/lib/mongoid/association/referenced/has_many/proxy.rb +2 -2
  6. data/lib/mongoid/association/relatable.rb +90 -10
  7. data/lib/mongoid/clients/options.rb +6 -4
  8. data/lib/mongoid/copyable.rb +2 -2
  9. data/lib/mongoid/criteria/options.rb +2 -2
  10. data/lib/mongoid/criteria/queryable/selectable.rb +33 -6
  11. data/lib/mongoid/document.rb +11 -4
  12. data/lib/mongoid/extensions/big_decimal.rb +1 -1
  13. data/lib/mongoid/extensions/regexp.rb +1 -0
  14. data/lib/mongoid/matchable.rb +3 -1
  15. data/lib/mongoid/matchable/eq.rb +22 -0
  16. data/lib/mongoid/matchable/ne.rb +1 -1
  17. data/lib/mongoid/persistence_context.rb +20 -5
  18. data/lib/mongoid/query_cache.rb +8 -4
  19. data/lib/mongoid/railtie.rb +17 -0
  20. data/lib/mongoid/railties/controller_runtime.rb +86 -0
  21. data/lib/mongoid/scopable.rb +3 -3
  22. data/lib/mongoid/threaded.rb +36 -0
  23. data/lib/mongoid/version.rb +1 -1
  24. data/spec/app/models/minim.rb +7 -0
  25. data/spec/app/models/store_as_dup_test3.rb +7 -0
  26. data/spec/app/models/store_as_dup_test4.rb +7 -0
  27. data/spec/config/mongoid.yml +12 -3
  28. data/spec/integration/associations/belongs_to_spec.rb +13 -0
  29. data/spec/lite_spec_helper.rb +56 -0
  30. data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +24 -5
  31. data/spec/mongoid/association/referenced/belongs_to_spec.rb +46 -3
  32. data/spec/mongoid/association/referenced/has_and_belongs_to_many/eager_spec.rb +21 -2
  33. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_persistence_spec.rb +56 -0
  34. data/spec/mongoid/association/referenced/has_and_belongs_to_many_models.rb +26 -0
  35. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +3 -3
  36. data/spec/mongoid/association/referenced/has_many/proxy_query_spec.rb +23 -0
  37. data/spec/mongoid/association/referenced/has_many_models.rb +37 -0
  38. data/spec/mongoid/association/referenced/has_many_spec.rb +3 -3
  39. data/spec/mongoid/association/referenced/has_one_models.rb +48 -0
  40. data/spec/mongoid/association/referenced/has_one_spec.rb +51 -4
  41. data/spec/mongoid/clients/factory_spec.rb +24 -18
  42. data/spec/mongoid/clients/options_spec.rb +40 -37
  43. data/spec/mongoid/clients_spec.rb +68 -8
  44. data/spec/mongoid/config_spec.rb +3 -1
  45. data/spec/mongoid/contextual/mongo_spec.rb +5 -2
  46. data/spec/mongoid/copyable_spec.rb +40 -6
  47. data/spec/mongoid/copyable_spec_models.rb +17 -0
  48. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +3 -3
  49. data/spec/mongoid/criteria/queryable/selectable_spec.rb +42 -3
  50. data/spec/mongoid/criteria/queryable/selector_spec.rb +2 -2
  51. data/spec/mongoid/criteria/scopable_spec.rb +81 -0
  52. data/spec/mongoid/criteria_spec.rb +18 -3
  53. data/spec/mongoid/document_spec.rb +81 -2
  54. data/spec/mongoid/extensions/big_decimal_spec.rb +9 -9
  55. data/spec/mongoid/extensions/regexp_spec.rb +23 -0
  56. data/spec/mongoid/fields_spec.rb +1 -1
  57. data/spec/mongoid/findable_spec.rb +1 -1
  58. data/spec/mongoid/matchable/eq_spec.rb +48 -0
  59. data/spec/mongoid/persistable/incrementable_spec.rb +6 -6
  60. data/spec/mongoid/persistence_context_spec.rb +1 -1
  61. data/spec/mongoid/query_cache_spec.rb +59 -6
  62. data/spec/mongoid/scopable_spec.rb +13 -0
  63. data/spec/mongoid/threaded_spec.rb +68 -0
  64. data/spec/rails/controller_extension/controller_runtime_spec.rb +110 -0
  65. data/spec/spec_helper.rb +35 -25
  66. data/spec/support/constraints.rb +101 -0
  67. data/spec/support/macros.rb +20 -0
  68. data/spec/support/spec_config.rb +39 -0
  69. metadata +471 -460
  70. checksums.yaml.gz.sig +0 -0
  71. data.tar.gz.sig +0 -2
  72. metadata.gz.sig +0 -0
@@ -138,7 +138,9 @@ module Mongoid
138
138
  # @since 5.0.0
139
139
  def each
140
140
  if @cached_documents
141
- @cached_documents.each{ |doc| yield doc }
141
+ @cached_documents.each do |doc|
142
+ yield doc
143
+ end
142
144
  else
143
145
  super
144
146
  end
@@ -163,7 +165,10 @@ module Mongoid
163
165
  @cursor_id = result.cursor_id
164
166
  @coll_name ||= result.namespace.sub("#{database.name}.", '') if result.namespace
165
167
  documents = result.documents
166
- (@cached_documents ||= []).concat(documents)
168
+ if @cursor_id.zero? && !@after_first_batch
169
+ (@cached_documents ||= []).concat(documents)
170
+ end
171
+ @after_first_batch = true
167
172
  documents
168
173
  end
169
174
  end
@@ -242,8 +247,7 @@ module Mongoid
242
247
  key = [ collection.namespace, selector, nil, skip, sort, projection, collation ]
243
248
  cursor = QueryCache.cache_table[key]
244
249
  if cursor
245
- limited_docs = cursor.to_a[0...limit.abs]
246
- cursor.instance_variable_set(:@cached_documents, limited_docs)
250
+ cursor.to_a[0...limit.abs]
247
251
  end
248
252
  end
249
253
  cursor || QueryCache.cache_table[cache_key]
@@ -102,6 +102,23 @@ module Rails
102
102
  puts "There is a configuration error with the current mongoid.yml."
103
103
  puts e.message
104
104
  end
105
+
106
+ # Include Controller extension that measures Mongoid runtime
107
+ # during request processing. The value then appears in Rails'
108
+ # instrumentation event `process_action.action_controller`.
109
+ #
110
+ # The measurement is made via internal Mongo monitoring subscription
111
+ initializer "mongoid.runtime-metric" do
112
+ require "mongoid/railties/controller_runtime"
113
+
114
+ ActiveSupport.on_load :action_controller do
115
+ include ::Mongoid::Railties::ControllerRuntime::ControllerExtension
116
+ end
117
+
118
+ Mongo::Monitoring::Global.subscribe Mongo::Monitoring::COMMAND,
119
+ ::Mongoid::Railties::ControllerRuntime::Collector.new
120
+ end
121
+
105
122
  end
106
123
  end
107
124
  end
@@ -0,0 +1,86 @@
1
+ module Mongoid
2
+ module Railties
3
+ module ControllerRuntime
4
+
5
+ # This extension mimics the Rails' internal method to
6
+ # measure ActiveRecord runtime during request processing.
7
+ # It appends MongoDB runtime value (`mongoid_runtime`) into payload
8
+ # of instrumentation event `process_action.action_controller`.
9
+ module ControllerExtension
10
+ extend ActiveSupport::Concern
11
+
12
+ protected
13
+
14
+ attr_internal :mongoid_runtime
15
+
16
+ # Reset the runtime before each action.
17
+ def process_action(action, *args)
18
+ Collector.reset_runtime
19
+ super
20
+ end
21
+
22
+ # Override to collect the measurements.
23
+ def cleanup_view_runtime
24
+ mongo_rt_before_render = Collector.reset_runtime
25
+ runtime = super
26
+ mongo_rt_after_render = Collector.reset_runtime
27
+ self.mongoid_runtime = mongo_rt_before_render + mongo_rt_after_render
28
+ runtime - mongo_rt_after_render
29
+ end
30
+
31
+ # Add the measurement to the instrumentation event payload.
32
+ def append_info_to_payload(payload)
33
+ super
34
+ payload[:mongoid_runtime] = (mongoid_runtime || 0) + Collector.reset_runtime
35
+ end
36
+
37
+ module ClassMethods
38
+
39
+ # Append MongoDB runtime information to ActionController runtime
40
+ # log message.
41
+ def log_process_action(payload)
42
+ messages = super
43
+ mongoid_runtime = payload[:mongoid_runtime]
44
+ messages << ("MongoDB: %.1fms" % mongoid_runtime.to_f) if mongoid_runtime
45
+ messages
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ # The Collector of MongoDB runtime metric, that subscribes to Mongo
53
+ # driver command monitoring. Stores the value within a thread-local
54
+ # variable to provide correct accounting when an application issues
55
+ # MongoDB operations from background threads.
56
+ class Collector
57
+
58
+ VARIABLE_NAME = "Mongoid.controller_runtime".freeze
59
+
60
+ def started _; end
61
+
62
+ def _completed e
63
+ Collector.runtime += e.duration
64
+ end
65
+ alias :succeeded :_completed
66
+ alias :failed :_completed
67
+
68
+ def self.runtime
69
+ Thread.current[VARIABLE_NAME] ||= 0
70
+ end
71
+
72
+ def self.runtime= value
73
+ Thread.current[VARIABLE_NAME] = value
74
+ end
75
+
76
+ def self.reset_runtime
77
+ to_now = runtime
78
+ self.runtime = 0
79
+ to_now
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -102,7 +102,7 @@ module Mongoid
102
102
  #
103
103
  # @since 3.0.0
104
104
  def default_scopable?
105
- default_scoping? && !Threaded.executing?(:without_default_scope)
105
+ default_scoping? && !Threaded.without_default_scope?(self)
106
106
  end
107
107
 
108
108
  # Get a queryable, either the last one on the scope stack or a fresh one.
@@ -244,10 +244,10 @@ module Mongoid
244
244
  #
245
245
  # @since 3.0.0
246
246
  def without_default_scope
247
- Threaded.begin_execution("without_default_scope")
247
+ Threaded.begin_without_default_scope(self)
248
248
  yield
249
249
  ensure
250
- Threaded.exit_execution("without_default_scope")
250
+ Threaded.exit_without_default_scope(self)
251
251
  end
252
252
 
253
253
  private
@@ -163,6 +163,30 @@ module Mongoid
163
163
  validations_for(document.class).delete_one(document._id)
164
164
  end
165
165
 
166
+ # Begin suppressing default scopes for given model on the current thread.
167
+ #
168
+ # @example Begin without default scope stack.
169
+ # Threaded.begin_without_default_scope(klass)
170
+ #
171
+ # @param [ Class ] klass The model to suppress default scoping on.
172
+ #
173
+ # @api private
174
+ def begin_without_default_scope(klass)
175
+ stack(:without_default_scope).push(klass)
176
+ end
177
+
178
+ # Exit suppressing default scopes for given model on the current thread.
179
+ #
180
+ # @example Exit without default scope stack.
181
+ # Threaded.exit_without_default_scope(klass)
182
+ #
183
+ # @param [ Class ] klass The model to unsuppress default scoping on.
184
+ #
185
+ # @api private
186
+ def exit_without_default_scope(klass)
187
+ stack(:without_default_scope).delete(klass)
188
+ end
189
+
166
190
  # Get the global client override.
167
191
  #
168
192
  # @example Get the global client override.
@@ -247,6 +271,18 @@ module Mongoid
247
271
  end
248
272
  end
249
273
 
274
+ # Is the given klass' default scope suppressed on the current thread?
275
+ #
276
+ # @example Is the given klass' default scope suppressed?
277
+ # Threaded.without_default_scope?(klass)
278
+ #
279
+ # @param [ Class ] klass The model to check for default scope suppression.
280
+ #
281
+ # @api private
282
+ def without_default_scope?(klass)
283
+ stack(:without_default_scope).include?(klass)
284
+ end
285
+
250
286
  # Is the document autosaved on the current thread?
251
287
  #
252
288
  # @example Is the document autosaved?
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid
3
- VERSION = "7.0.2"
3
+ VERSION = "7.0.3"
4
4
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Minimal model, do not add any fields
4
+ class Minim
5
+ include Mongoid::Document
6
+ field :name
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StoreAsDupTest3
4
+ include Mongoid::Document
5
+ embeds_many :store_as_dup_test4s, :store_as => :t
6
+ field :name
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StoreAsDupTest4
4
+ include Mongoid::Document
5
+ embedded_in :store_as_dup_test3
6
+ field :name
7
+ end
@@ -3,7 +3,9 @@ test:
3
3
  default:
4
4
  database: mongoid_test
5
5
  hosts:
6
- - <%=ENV["MONGOID_SPEC_HOST"]%>:<%=ENV["MONGOID_SPEC_PORT"]%>
6
+ <% SpecConfig.instance.addresses.each do |address| %>
7
+ - <%= address %>
8
+ <% end %>
7
9
  options:
8
10
  user: "mongoid-user"
9
11
  password: "password"
@@ -13,14 +15,18 @@ test:
13
15
  tag_sets:
14
16
  - use: web
15
17
  max_pool_size: 1
18
+ server_selection_timeout: 3.14
16
19
  reports:
17
20
  database: reports
18
21
  hosts:
19
- - <%=ENV["MONGOID_SPEC_HOST"]%>:<%=ENV["MONGOID_SPEC_PORT"]%>
22
+ <% SpecConfig.instance.addresses.each do |address| %>
23
+ - <%= address %>
24
+ <% end %>
20
25
  options:
21
26
  user: "mongoid-user"
22
27
  password: "password"
23
28
  auth_source: "admin"
29
+ server_selection_timeout: 3.14
24
30
  options:
25
31
  include_root_in_json: false
26
32
  include_type_for_serialization: false
@@ -38,8 +44,11 @@ test_with_max_staleness:
38
44
  default:
39
45
  database: mongoid_test
40
46
  hosts:
41
- - <%=ENV["MONGOID_SPEC_HOST"]%>:<%=ENV["MONGOID_SPEC_PORT"]%>
47
+ <% SpecConfig.instance.addresses.each do |address| %>
48
+ - <%= address %>
49
+ <% end %>
42
50
  options:
43
51
  read:
44
52
  mode: :primary_preferred
45
53
  max_staleness: 100
54
+ server_selection_timeout: 3.14
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+ require_relative '../../mongoid/association/referenced/has_one_models'
3
+
4
+ describe 'belongs_to associations' do
5
+ context 'referencing top level classes when source class is namespaced' do
6
+ let(:college) { HomCollege.create! }
7
+ let(:child) { HomAccreditation::Child.new(hom_college: college) }
8
+
9
+ it 'works' do
10
+ expect(child).to be_valid
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
5
+
6
+ require "mongoid"
7
+ require "rspec"
8
+
9
+ # MRI 2.5 and JRuby 9.2 change visibility of Object#pp when 'pp' is required,
10
+ # which happens when RSpec reports anything. This creates an issue for tests
11
+ # that verify method forwarding. Work around by proactively loading 'pp'.
12
+ # https://github.com/jruby/jruby/issues/5599
13
+ require 'pp'
14
+
15
+ require 'support/spec_config'
16
+
17
+ unless SpecConfig.instance.ci?
18
+ begin
19
+ require 'byebug'
20
+ rescue LoadError
21
+ # jruby - try pry
22
+ begin
23
+ require 'pry'
24
+ # jruby likes to raise random error classes, in this case
25
+ # NameError in addition to LoadError
26
+ rescue Exception
27
+ end
28
+ end
29
+ end
30
+
31
+ if SpecConfig.instance.mri?
32
+ require 'timeout_interrupt'
33
+ else
34
+ require 'timeout'
35
+ TimeoutInterrupt = Timeout
36
+ end
37
+
38
+ RSpec.configure do |config|
39
+ if SpecConfig.instance.ci?
40
+ config.add_formatter(RSpec::Core::Formatters::JsonFormatter, File.join(File.dirname(__FILE__), '../tmp/rspec.json'))
41
+ end
42
+
43
+ if SpecConfig.instance.ci?
44
+ # Allow a max of 30 seconds per test.
45
+ # Tests should take under 10 seconds ideally but it seems
46
+ # we have some that run for more than 10 seconds in CI.
47
+ config.around(:each) do |example|
48
+ TimeoutInterrupt.timeout(30) do
49
+ example.run
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # require all shared examples
56
+ Dir['./spec/support/shared/*.rb'].sort.each { |file| require file }
@@ -1,4 +1,5 @@
1
1
  require "spec_helper"
2
+ require_relative '../has_many_models'
2
3
 
3
4
  describe Mongoid::Association::Referenced::BelongsTo::Eager do
4
5
 
@@ -55,7 +56,7 @@ describe Mongoid::Association::Referenced::BelongsTo::Eager do
55
56
  Post.create!(person: person)
56
57
  end
57
58
 
58
- it "sets the relation into the parent" do
59
+ it "sets the association into the parent" do
59
60
  docs.each do |doc|
60
61
  expect(doc).to receive(:set_relation).with(:person, :foo)
61
62
  end
@@ -73,7 +74,7 @@ describe Mongoid::Association::Referenced::BelongsTo::Eager do
73
74
  3.times { |i| Account.create!(person: person, name: "savings#{i}") }
74
75
  end
75
76
 
76
- context "when including the belongs_to relation" do
77
+ context "when including the belongs_to association" do
77
78
 
78
79
  it "queries twice" do
79
80
 
@@ -85,7 +86,7 @@ describe Mongoid::Association::Referenced::BelongsTo::Eager do
85
86
  end
86
87
  end
87
88
 
88
- context "when the relation is not polymorphic" do
89
+ context "when the association is not polymorphic" do
89
90
 
90
91
  let(:eager) do
91
92
  Post.includes(:person).last
@@ -128,13 +129,13 @@ describe Mongoid::Association::Referenced::BelongsTo::Eager do
128
129
  expect(eager.ivar(:person)).to be nil
129
130
  end
130
131
 
131
- it "has a nil relation" do
132
+ it "has a nil association" do
132
133
  expect(eager.person).to be nil
133
134
  end
134
135
  end
135
136
  end
136
137
 
137
- context "when the relation is polymorphic" do
138
+ context "when the association is polymorphic" do
138
139
 
139
140
  let!(:movie) do
140
141
  Movie.create(name: "Bladerunner")
@@ -159,5 +160,23 @@ describe Mongoid::Association::Referenced::BelongsTo::Eager do
159
160
  expect(game.person_id).to eql(id)
160
161
  end
161
162
  end
163
+
164
+ context "when all the values for the belongs_to association are nil" do
165
+
166
+ before do
167
+ 2.times { |i| HmmTicket.create!(person: nil) }
168
+ end
169
+
170
+ it "only queries once for the parent documents" do
171
+ found_ticket = false
172
+ expect_query(1) do
173
+ HmmTicket.all.includes(:person).each do |ticket|
174
+ expect(ticket.person).to eq nil
175
+ found_ticket = true
176
+ end
177
+ end
178
+ expect(found_ticket).to be true
179
+ end
180
+ end
162
181
  end
163
182
  end
@@ -1,4 +1,5 @@
1
1
  require "spec_helper"
2
+ require_relative './has_one_models'
2
3
 
3
4
  describe Mongoid::Association::Referenced::BelongsTo do
4
5
 
@@ -1627,9 +1628,19 @@ describe Mongoid::Association::Referenced::BelongsTo do
1627
1628
  expect(association.relation_class_name).to eq('OwnerObject')
1628
1629
  end
1629
1630
  end
1631
+
1632
+ context 'when the association is polymorphic' do
1633
+ let(:association) do
1634
+ HomPolymorphicChild.relations['p_parent']
1635
+ end
1636
+
1637
+ it 'is the computed class name that does not match any existing class' do
1638
+ expect(association.relation_class_name).to eq('PParent')
1639
+ end
1640
+ end
1630
1641
  end
1631
1642
 
1632
- describe '#klass' do
1643
+ describe '#relation_class' do
1633
1644
 
1634
1645
  context 'when the :class_name option is specified' do
1635
1646
 
@@ -1643,14 +1654,26 @@ describe Mongoid::Association::Referenced::BelongsTo do
1643
1654
  end
1644
1655
 
1645
1656
  it 'returns the class name option' do
1646
- expect(association.klass).to eq(_class)
1657
+ expect(association.relation_class).to eq(_class)
1647
1658
  end
1648
1659
  end
1649
1660
 
1650
1661
  context 'when the class_name option is not specified' do
1651
1662
 
1652
1663
  it 'uses the name of the relation to deduce the class name' do
1653
- expect(association.klass).to eq(OwnerObject)
1664
+ expect(association.relation_class).to eq(OwnerObject)
1665
+ end
1666
+ end
1667
+
1668
+ context 'when the association is polymorphic' do
1669
+ let(:association) do
1670
+ HomPolymorphicChild.relations['p_parent']
1671
+ end
1672
+
1673
+ it 'raises NameError' do
1674
+ expect do
1675
+ association.relation_class
1676
+ end.to raise_error(NameError, /uninitialized constant .*PParent/)
1654
1677
  end
1655
1678
  end
1656
1679
  end
@@ -1660,6 +1683,16 @@ describe Mongoid::Association::Referenced::BelongsTo do
1660
1683
  it 'returns the name of the owner class' do
1661
1684
  expect(association.inverse_class_name).to eq(belonging_class.name)
1662
1685
  end
1686
+
1687
+ context 'polymorphic association' do
1688
+ let(:association) do
1689
+ belonging_class.belongs_to :poly_owner, polymorphic: true
1690
+ end
1691
+
1692
+ it 'returns the name of the owner class' do
1693
+ expect(association.inverse_class_name).to eq(belonging_class.name)
1694
+ end
1695
+ end
1663
1696
  end
1664
1697
 
1665
1698
  describe '#inverse_class' do
@@ -1667,6 +1700,16 @@ describe Mongoid::Association::Referenced::BelongsTo do
1667
1700
  it 'returns the owner class' do
1668
1701
  expect(association.inverse_class).to be(belonging_class)
1669
1702
  end
1703
+
1704
+ context 'polymorphic association' do
1705
+ let(:association) do
1706
+ belonging_class.belongs_to :poly_owner, polymorphic: true
1707
+ end
1708
+
1709
+ it 'returns the owner class' do
1710
+ expect(association.inverse_class).to be(belonging_class)
1711
+ end
1712
+ end
1670
1713
  end
1671
1714
 
1672
1715
  describe '#inverse_of' do