mongoid 7.0.2 → 7.0.3

Sign up to get free protection for your applications and to get access to all the features.
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