mongoid 7.0.3 → 7.0.8

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 (102) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -0
  3. data.tar.gz.sig +1 -0
  4. data/LICENSE +1 -0
  5. data/README.md +3 -2
  6. data/Rakefile +12 -0
  7. data/lib/mongoid.rb +2 -1
  8. data/lib/mongoid/association/embedded/embeds_many.rb +2 -1
  9. data/lib/mongoid/association/embedded/embeds_one.rb +2 -1
  10. data/lib/mongoid/association/proxy.rb +1 -1
  11. data/lib/mongoid/association/relatable.rb +23 -21
  12. data/lib/mongoid/atomic.rb +13 -3
  13. data/lib/mongoid/atomic/paths/embedded.rb +1 -1
  14. data/lib/mongoid/attributes.rb +28 -20
  15. data/lib/mongoid/attributes/dynamic.rb +15 -14
  16. data/lib/mongoid/config/environment.rb +21 -8
  17. data/lib/mongoid/copyable.rb +5 -1
  18. data/lib/mongoid/criteria.rb +7 -1
  19. data/lib/mongoid/criteria/modifiable.rb +13 -2
  20. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -1
  21. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +3 -3
  22. data/lib/mongoid/criteria/queryable/key.rb +67 -8
  23. data/lib/mongoid/criteria/queryable/mergeable.rb +5 -4
  24. data/lib/mongoid/criteria/queryable/selectable.rb +3 -4
  25. data/lib/mongoid/criteria/queryable/selector.rb +9 -31
  26. data/lib/mongoid/extensions/hash.rb +4 -2
  27. data/lib/mongoid/extensions/regexp.rb +1 -1
  28. data/lib/mongoid/extensions/string.rb +5 -3
  29. data/lib/mongoid/fields.rb +2 -1
  30. data/lib/mongoid/matchable.rb +14 -15
  31. data/lib/mongoid/matchable/all.rb +4 -3
  32. data/lib/mongoid/matchable/default.rb +71 -24
  33. data/lib/mongoid/matchable/regexp.rb +2 -2
  34. data/lib/mongoid/persistable/pushable.rb +11 -2
  35. data/lib/mongoid/persistence_context.rb +6 -6
  36. data/lib/mongoid/positional.rb +1 -1
  37. data/lib/mongoid/query_cache.rb +3 -2
  38. data/lib/mongoid/validatable/macros.rb +1 -1
  39. data/lib/mongoid/validatable/uniqueness.rb +1 -1
  40. data/lib/mongoid/version.rb +2 -1
  41. data/lib/rails/generators/mongoid/model/templates/model.rb.tt +1 -1
  42. data/spec/README.md +18 -0
  43. data/spec/app/models/delegating_patient.rb +16 -0
  44. data/spec/app/models/other_owner_object.rb +2 -0
  45. data/spec/integration/app_spec.rb +192 -0
  46. data/spec/integration/associations/embedded_spec.rb +62 -0
  47. data/spec/integration/criteria/time_with_zone_spec.rb +32 -0
  48. data/spec/integration/document_spec.rb +22 -0
  49. data/spec/integration/matchable_spec.rb +680 -0
  50. data/spec/lite_spec_helper.rb +15 -5
  51. data/spec/mongoid/association/embedded/embedded_in_spec.rb +58 -0
  52. data/spec/mongoid/association/embedded/embeds_many_models.rb +53 -0
  53. data/spec/mongoid/association/embedded/embeds_many_spec.rb +10 -0
  54. data/spec/mongoid/association/embedded/embeds_one_dnl_models.rb +6 -0
  55. data/spec/mongoid/association/embedded/embeds_one_models.rb +51 -0
  56. data/spec/mongoid/association/embedded/embeds_one_spec.rb +46 -0
  57. data/spec/mongoid/association/referenced/belongs_to_spec.rb +23 -6
  58. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +2 -1
  59. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +2 -1
  60. data/spec/mongoid/association/referenced/has_one_spec.rb +12 -2
  61. data/spec/mongoid/attributes/dynamic_spec.rb +153 -0
  62. data/spec/mongoid/attributes_spec.rb +19 -7
  63. data/spec/mongoid/clients/factory_spec.rb +2 -2
  64. data/spec/mongoid/clients/options_spec.rb +4 -4
  65. data/spec/mongoid/clients/sessions_spec.rb +20 -7
  66. data/spec/mongoid/clients/transactions_spec.rb +36 -15
  67. data/spec/mongoid/clients_spec.rb +2 -2
  68. data/spec/mongoid/contextual/atomic_spec.rb +20 -10
  69. data/spec/mongoid/contextual/geo_near_spec.rb +1 -0
  70. data/spec/mongoid/contextual/map_reduce_spec.rb +20 -5
  71. data/spec/mongoid/contextual/mongo_spec.rb +76 -53
  72. data/spec/mongoid/criteria/modifiable_spec.rb +59 -10
  73. data/spec/mongoid/criteria/queryable/extensions/numeric_spec.rb +54 -0
  74. data/spec/mongoid/criteria/queryable/extensions/regexp_spec.rb +7 -7
  75. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +1 -1
  76. data/spec/mongoid/criteria/queryable/key_spec.rb +48 -6
  77. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +762 -0
  78. data/spec/mongoid/criteria/queryable/selectable_spec.rb +5 -224
  79. data/spec/mongoid/criteria/queryable/selector_spec.rb +37 -0
  80. data/spec/mongoid/criteria_spec.rb +7 -2
  81. data/spec/mongoid/document_fields_spec.rb +88 -0
  82. data/spec/mongoid/document_persistence_context_spec.rb +33 -0
  83. data/spec/mongoid/extensions/string_spec.rb +35 -7
  84. data/spec/mongoid/indexable_spec.rb +6 -4
  85. data/spec/mongoid/matchable/default_spec.rb +10 -3
  86. data/spec/mongoid/matchable/regexp_spec.rb +2 -2
  87. data/spec/mongoid/matchable_spec.rb +2 -2
  88. data/spec/mongoid/persistable/pushable_spec.rb +55 -1
  89. data/spec/mongoid/query_cache_spec.rb +2 -1
  90. data/spec/mongoid/relations/proxy_spec.rb +1 -1
  91. data/spec/mongoid/scopable_spec.rb +2 -1
  92. data/spec/mongoid/tasks/database_rake_spec.rb +13 -13
  93. data/spec/mongoid/tasks/database_spec.rb +1 -1
  94. data/spec/mongoid/validatable/uniqueness_spec.rb +33 -6
  95. data/spec/spec_helper.rb +4 -37
  96. data/spec/support/child_process_helper.rb +76 -0
  97. data/spec/support/cluster_config.rb +158 -0
  98. data/spec/support/constraints.rb +29 -19
  99. data/spec/support/expectations.rb +17 -3
  100. data/spec/support/spec_config.rb +12 -4
  101. metadata +525 -464
  102. metadata.gz.sig +2 -0
@@ -31,6 +31,7 @@ end
31
31
  require 'support/authorization'
32
32
  require 'support/expectations'
33
33
  require 'support/macros'
34
+ require 'support/cluster_config'
34
35
  require 'support/constraints'
35
36
 
36
37
  # Give MongoDB time to start up on the travis ci environment.
@@ -74,42 +75,6 @@ CONFIG = {
74
75
  }
75
76
  }
76
77
 
77
- def non_legacy_server?
78
- Mongoid::Clients.default.cluster.servers.first.features.write_command_enabled?
79
- end
80
-
81
- def testing_replica_set?
82
- Mongoid::Clients.default.cluster.replica_set?
83
- end
84
-
85
- def collation_supported?
86
- Mongoid::Clients.default.cluster.next_primary.features.collation_enabled?
87
- end
88
- alias :decimal128_supported? :collation_supported?
89
-
90
- def array_filters_supported?
91
- Mongoid::Clients.default.cluster.next_primary.features.array_filters_enabled?
92
- end
93
- alias :sessions_supported? :array_filters_supported?
94
-
95
- def testing_geo_near?
96
- $geo_near_enabled ||= (Mongoid::Clients.default
97
- .command(serverStatus: 1)
98
- .first['version'] < '4.1')
99
- end
100
-
101
- def transactions_supported?
102
- Mongoid::Clients.default.cluster.next_primary.features.transactions_enabled?
103
- end
104
-
105
- def testing_transactions?
106
- transactions_supported? && testing_replica_set?
107
- end
108
-
109
- def testing_locally?
110
- !(ENV['CI'] == 'travis')
111
- end
112
-
113
78
  # Set the database that the spec suite connects to.
114
79
  Mongoid.configure do |config|
115
80
  config.load_configuration(CONFIG)
@@ -164,7 +129,9 @@ RSpec.configure do |config|
164
129
 
165
130
  # Drop all collections and clear the identity map before each spec.
166
131
  config.before(:each) do
167
- unless Mongoid.default_client.cluster.connected?
132
+ cluster = Mongoid.default_client.cluster
133
+ # Older drivers do not have a #connected? method
134
+ if cluster.respond_to?(:connected?) && !cluster.connected?
168
135
  Mongoid.default_client.reconnect
169
136
  end
170
137
  Mongoid.default_client.collections.each do |coll|
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ autoload :ChildProcess, 'childprocess'
5
+ autoload :Tempfile, 'tempfile'
6
+
7
+ module ChildProcessHelper
8
+ module_function def call(cmd, env: nil, cwd: nil)
9
+ process = ChildProcess.new(*cmd)
10
+ process.io.inherit!
11
+ if cwd
12
+ process.cwd = cwd
13
+ end
14
+ if env
15
+ env.each do |k, v|
16
+ process.environment[k.to_s] = v
17
+ end
18
+ end
19
+ process.start
20
+ process.wait
21
+ process
22
+ end
23
+
24
+ module_function def check_call(cmd, env: nil, cwd: nil)
25
+ process = call(cmd, env: env, cwd: cwd)
26
+ unless process.exit_code == 0
27
+ raise "Failed to execute: #{cmd}"
28
+ end
29
+ end
30
+
31
+ module_function def get_output(cmd, env: nil, cwd: nil)
32
+ process = ChildProcess.new(*cmd)
33
+ process.io.inherit!
34
+ if cwd
35
+ process.cwd = cwd
36
+ end
37
+ if env
38
+ env.each do |k, v|
39
+ process.environment[k.to_s] = v
40
+ end
41
+ end
42
+
43
+ output = ''
44
+ r, w = IO.pipe
45
+
46
+ begin
47
+ process.io.stdout = w
48
+ process.start
49
+ w.close
50
+
51
+ thread = Thread.new do
52
+ begin
53
+ loop do
54
+ output << r.readpartial(16384)
55
+ end
56
+ rescue EOFError
57
+ end
58
+ end
59
+
60
+ process.wait
61
+ thread.join
62
+ ensure
63
+ r.close
64
+ end
65
+
66
+ [process, output]
67
+ end
68
+
69
+ module_function def check_output(*args)
70
+ process, output = get_output(*args)
71
+ unless process.exit_code == 0
72
+ raise "Failed to execute: #{args}"
73
+ end
74
+ output
75
+ end
76
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'singleton'
5
+
6
+ class ClusterConfig
7
+ include Singleton
8
+
9
+ def single_server?
10
+ determine_cluster_config
11
+ @single_server
12
+ end
13
+
14
+ def replica_set_name
15
+ determine_cluster_config
16
+ @replica_set_name
17
+ end
18
+
19
+ def server_version
20
+ determine_cluster_config
21
+ @server_version
22
+ end
23
+
24
+ def short_server_version
25
+ server_version.split('.')[0..1].join('.')
26
+ end
27
+
28
+ def fcv
29
+ determine_cluster_config
30
+ @fcv
31
+ end
32
+
33
+ # Per https://jira.mongodb.org/browse/SERVER-39052, working with FCV
34
+ # in sharded topologies is annoying. Also, FCV doesn't exist in servers
35
+ # less than 3.4. This method returns FCV on 3.4+ servers when in single
36
+ # or RS topologies, and otherwise returns the major.minor server version.
37
+ def fcv_ish
38
+ if server_version >= '3.4' && topology != :sharded
39
+ fcv
40
+ else
41
+ if short_server_version == '4.1'
42
+ '4.2'
43
+ else
44
+ short_server_version
45
+ end
46
+ end
47
+ end
48
+
49
+ def primary_address
50
+ determine_cluster_config
51
+ @primary_address
52
+ end
53
+
54
+ def primary_address_str
55
+ determine_cluster_config
56
+ @primary_address.seed
57
+ end
58
+
59
+ def primary_address_host
60
+ both = primary_address_str
61
+ both.split(':').first
62
+ end
63
+
64
+ def primary_address_port
65
+ both = primary_address_str
66
+ both.split(':')[1] || 27017
67
+ end
68
+
69
+ def primary_description
70
+ determine_cluster_config
71
+ @primary_description
72
+ end
73
+
74
+ # Try running a command on the admin database to see if the mongod was
75
+ # started with auth.
76
+ def auth_enabled?
77
+ if @auth_enabled.nil?
78
+ @auth_enabled = begin
79
+ basic_client.use(:admin).command(getCmdLineOpts: 1).first["argv"].include?("--auth")
80
+ rescue => e
81
+ e.message =~ /(not authorized)|(unauthorized)|(no users authenticated)|(requires authentication)/
82
+ end
83
+ end
84
+ @auth_enabled
85
+ end
86
+
87
+ def topology
88
+ determine_cluster_config
89
+ @topology
90
+ end
91
+
92
+ def storage_engine
93
+ @storage_engine ||= begin
94
+ # 2.6 does not have wired tiger
95
+ if short_server_version == '2.6'
96
+ :mmapv1
97
+ else
98
+ client = ClientRegistry.instance.global_client('root_authorized')
99
+ if topology == :sharded
100
+ shards = client.use(:admin).command(listShards: 1).first
101
+ shard = shards['shards'].first
102
+ address_str = shard['host'].sub(/\A.*\//, '').sub(/,.*/, '')
103
+ client = ClusterTools.instance.direct_client(address_str,
104
+ SpecConfig.instance.test_options.merge(SpecConfig.instance.auth_options).merge(connect: :direct))
105
+ end
106
+ rv = client.use(:admin).command(serverStatus: 1).first
107
+ rv = rv['storageEngine']['name']
108
+ rv_map = {
109
+ 'wiredTiger' => :wired_tiger,
110
+ 'mmapv1' => :mmapv1,
111
+ }
112
+ rv_map[rv] || rv
113
+ end
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def determine_cluster_config
120
+ return if @primary_address
121
+
122
+ # Run all commands to figure out the cluster configuration from the same
123
+ # client. This is somewhat wasteful when running a single test, but reduces
124
+ # test runtime for the suite overall because all commands are sent on the
125
+ # same connection rather than each command connecting to the cluster by
126
+ # itself.
127
+ client = Mongoid::Clients.default
128
+
129
+ primary = client.cluster.next_primary
130
+ @primary_address = primary.address
131
+ @primary_description = primary.description
132
+ @replica_set_name = client.cluster.topology.replica_set_name
133
+
134
+ @topology ||= begin
135
+ topology = client.cluster.topology.class.name.sub(/.*::/, '')
136
+ topology = topology.gsub(/([A-Z])/) { |match| '_' + match.downcase }.sub(/\A_/, '')
137
+ if topology =~ /\Areplica_set/
138
+ topology = 'replica_set'
139
+ end
140
+ topology.to_sym
141
+ end
142
+
143
+ @single_server = client.cluster.send(:servers_list).length == 1
144
+
145
+ @server_version = client.database.command(buildInfo: 1).first['version']
146
+
147
+ if @topology != :sharded && short_server_version >= '3.4'
148
+ rv = client.use(:admin).command(getParameter: 1, featureCompatibilityVersion: 1).first['featureCompatibilityVersion']
149
+ @fcv = rv['version'] || rv
150
+ end
151
+ end
152
+
153
+ def basic_client
154
+ # Do not cache the result here so that if the client gets closed,
155
+ # client registry reconnects it in subsequent tests
156
+ ClientRegistry.instance.global_client('basic')
157
+ end
158
+ end
@@ -2,11 +2,11 @@ module Constraints
2
2
  RAILS_VERSION = ActiveSupport.version.to_s.split('.')[0..1].join('.').freeze
3
3
 
4
4
  def min_rails_version(version)
5
- unless version =~ /^\d+\.\d+$/
5
+ unless version =~ /\A\d+\.\d+\z/
6
6
  raise ArgumentError, "Version can only be major.minor: #{version}"
7
7
  end
8
8
 
9
- before do
9
+ before(:all) do
10
10
  if version > RAILS_VERSION
11
11
  skip "Rails version #{version} or higher required, we have #{RAILS_VERSION}"
12
12
  end
@@ -14,11 +14,11 @@ module Constraints
14
14
  end
15
15
 
16
16
  def max_rails_version(version)
17
- unless version =~ /^\d+\.\d+$/
17
+ unless version =~ /\A\d+\.\d+\z/
18
18
  raise ArgumentError, "Version can only be major.minor: #{version}"
19
19
  end
20
20
 
21
- before do
21
+ before(:all) do
22
22
  if version < RAILS_VERSION
23
23
  skip "Rails version #{version} or lower required, we have #{RAILS_VERSION}"
24
24
  end
@@ -26,11 +26,11 @@ module Constraints
26
26
  end
27
27
 
28
28
  def min_server_version(version)
29
- unless version =~ /^\d+\.\d+$/
29
+ unless version =~ /\A\d+\.\d+\z/
30
30
  raise ArgumentError, "Version can only be major.minor: #{version}"
31
31
  end
32
32
 
33
- before do
33
+ before(:all) do
34
34
  if version > ClusterConfig.instance.server_version
35
35
  skip "Server version #{version} or higher required, we have #{ClusterConfig.instance.server_version}"
36
36
  end
@@ -38,11 +38,11 @@ module Constraints
38
38
  end
39
39
 
40
40
  def max_server_version(version)
41
- unless version =~ /^\d+\.\d+$/
41
+ unless version =~ /\A\d+\.\d+\z/
42
42
  raise ArgumentError, "Version can only be major.minor: #{version}"
43
43
  end
44
44
 
45
- before do
45
+ before(:all) do
46
46
  if version < ClusterConfig.instance.short_server_version
47
47
  skip "Server version #{version} or lower required, we have #{ClusterConfig.instance.server_version}"
48
48
  end
@@ -50,18 +50,14 @@ module Constraints
50
50
  end
51
51
 
52
52
  def require_topology(*topologies)
53
- topologies = topologies.map { |t| t.to_s }
54
- invalid_topologies = topologies - %w(single replica_set sharded)
53
+ invalid_topologies = topologies - [:single, :replica_set, :sharded]
54
+
55
55
  unless invalid_topologies.empty?
56
56
  raise ArgumentError, "Invalid topologies requested: #{invalid_topologies.join(', ')}"
57
57
  end
58
- before do
59
- topology = Mongoid.default_client.cluster.topology.class.name.sub(/.*::/, '')
60
- topology = topology.gsub(/([A-Z])/) { |match| '_' + match.downcase }.sub(/^_/, '')
61
- if topology =~ /^replica_set/
62
- topology = 'replica_set'
63
- end
64
- unless topologies.include?(topology)
58
+
59
+ before(:all) do
60
+ unless topologies.include?(topology = ClusterConfig.instance.topology)
65
61
  skip "Topology #{topologies.join(' or ')} required, we have #{topology}"
66
62
  end
67
63
  end
@@ -76,8 +72,22 @@ module Constraints
76
72
  end
77
73
 
78
74
  def require_transaction_support
79
- min_server_version '4.0'
80
- require_topology :replica_set
75
+ before(:all) do
76
+ case ClusterConfig.instance.topology
77
+ when :single
78
+ skip 'Transactions tests require a replica set (4.0+) or a sharded cluster (4.2+)'
79
+ when :replica_set
80
+ unless ClusterConfig.instance.server_version >= '4.0'
81
+ skip 'Transactions tests in a replica set topology require server 4.0+'
82
+ end
83
+ when :sharded
84
+ unless ClusterConfig.instance.server_version >= '4.2'
85
+ skip 'Transactions tests in a sharded cluster topology require server 4.2+'
86
+ end
87
+ else
88
+ raise NotImplementedError
89
+ end
90
+ end
81
91
  end
82
92
 
83
93
  def require_scram_sha_256_support
@@ -1,10 +1,24 @@
1
1
  module Mongoid
2
2
  module Expectations
3
3
 
4
+ def connection_class
5
+ if defined?(Mongo::Server::ConnectionBase)
6
+ Mongo::Server::ConnectionBase
7
+ else
8
+ # Pre-2.8 drivers
9
+ Mongo::Server::Connection
10
+ end
11
+ end
12
+
4
13
  def expect_query(number)
5
- # There are both start and complete events for each query.
6
- expect(Mongo::Logger.logger).to receive(:debug?).exactly(number * 2).times.and_call_original
7
- yield
14
+ RSpec::Mocks.with_temporary_scope do
15
+ if number > 0
16
+ expect_any_instance_of(connection_class).to receive(:command_started).exactly(number).times.and_call_original
17
+ else
18
+ expect_any_instance_of(connection_class).not_to receive(:command_started)
19
+ end
20
+ yield
21
+ end
8
22
  end
9
23
 
10
24
  def expect_no_queries(&block)
@@ -5,13 +5,17 @@ class SpecConfig
5
5
 
6
6
  def initialize
7
7
  if ENV['MONGODB_URI']
8
- @mongodb_uri = Mongo::URI.new(ENV['MONGODB_URI'])
8
+ @uri_str = ENV['MONGODB_URI']
9
+ @uri = Mongo::URI.new(@uri_str)
9
10
  end
10
11
  end
11
12
 
13
+ attr_reader :uri_str
14
+ attr_reader :uri
15
+
12
16
  def addresses
13
- if @mongodb_uri
14
- @mongodb_uri.servers
17
+ if @uri
18
+ @uri.servers
15
19
  else
16
20
  ['127.0.0.1']
17
21
  end
@@ -30,7 +34,11 @@ class SpecConfig
30
34
  end
31
35
 
32
36
  def client_debug?
33
- %w(1 true yes).include?((ENV['CLIENT_DEBUG'] || '').downcase)
37
+ %w(1 true yes).include?(ENV['CLIENT_DEBUG'] && ENV['CLIENT_DEBUG'].downcase)
38
+ end
39
+
40
+ def app_tests?
41
+ %w(1 true yes).include?(ENV['APP_TESTS'] && ENV['APP_TESTS'].downcase)
34
42
  end
35
43
 
36
44
  def ci?