mongoid 7.1.0 → 7.1.6

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 (131) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +6 -6
  5. data/README.md +1 -1
  6. data/Rakefile +14 -5
  7. data/lib/config/locales/en.yml +5 -5
  8. data/lib/mongoid/association/accessors.rb +37 -2
  9. data/lib/mongoid/association/embedded/embeds_many.rb +2 -1
  10. data/lib/mongoid/association/embedded/embeds_one.rb +2 -1
  11. data/lib/mongoid/association/proxy.rb +1 -1
  12. data/lib/mongoid/association/referenced/belongs_to/binding.rb +1 -1
  13. data/lib/mongoid/association/referenced/belongs_to/eager.rb +38 -2
  14. data/lib/mongoid/association/referenced/eager.rb +29 -9
  15. data/lib/mongoid/association/referenced/has_one/proxy.rb +6 -1
  16. data/lib/mongoid/atomic.rb +13 -3
  17. data/lib/mongoid/clients/factory.rb +2 -2
  18. data/lib/mongoid/clients/options.rb +8 -8
  19. data/lib/mongoid/clients/sessions.rb +20 -4
  20. data/lib/mongoid/clients/storage_options.rb +5 -5
  21. data/lib/mongoid/config.rb +39 -9
  22. data/lib/mongoid/criteria.rb +23 -4
  23. data/lib/mongoid/criteria/modifiable.rb +2 -1
  24. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -1
  25. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +6 -6
  26. data/lib/mongoid/criteria/queryable/extensions/time_with_zone.rb +12 -0
  27. data/lib/mongoid/criteria/queryable/mergeable.rb +75 -8
  28. data/lib/mongoid/criteria/queryable/pipeline.rb +3 -2
  29. data/lib/mongoid/criteria/queryable/selectable.rb +120 -13
  30. data/lib/mongoid/criteria/queryable/storable.rb +104 -99
  31. data/lib/mongoid/errors/eager_load.rb +2 -0
  32. data/lib/mongoid/errors/no_client_config.rb +2 -2
  33. data/lib/mongoid/errors/no_default_client.rb +1 -1
  34. data/lib/mongoid/extensions/hash.rb +4 -2
  35. data/lib/mongoid/extensions/regexp.rb +1 -1
  36. data/lib/mongoid/fields.rb +2 -1
  37. data/lib/mongoid/fields/validators/macro.rb +4 -1
  38. data/lib/mongoid/matchable/regexp.rb +2 -2
  39. data/lib/mongoid/persistable/pushable.rb +11 -2
  40. data/lib/mongoid/persistence_context.rb +6 -6
  41. data/lib/mongoid/query_cache.rb +61 -18
  42. data/lib/mongoid/serializable.rb +9 -3
  43. data/lib/mongoid/tasks/database.rb +38 -3
  44. data/lib/mongoid/validatable/uniqueness.rb +1 -1
  45. data/lib/mongoid/version.rb +1 -1
  46. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +32 -23
  47. data/lib/rails/generators/mongoid/model/templates/model.rb.tt +1 -1
  48. data/spec/app/models/coding.rb +4 -0
  49. data/spec/app/models/coding/pull_request.rb +12 -0
  50. data/spec/app/models/delegating_patient.rb +16 -0
  51. data/spec/app/models/passport.rb +1 -0
  52. data/spec/app/models/person.rb +2 -0
  53. data/spec/app/models/phone.rb +1 -0
  54. data/spec/app/models/publication.rb +5 -0
  55. data/spec/app/models/publication/encyclopedia.rb +12 -0
  56. data/spec/app/models/publication/review.rb +14 -0
  57. data/spec/app/models/series.rb +1 -0
  58. data/spec/app/models/wiki_page.rb +1 -0
  59. data/spec/integration/app_spec.rb +254 -0
  60. data/spec/integration/associations/embedded_spec.rb +54 -0
  61. data/spec/integration/associations/embeds_many_spec.rb +24 -0
  62. data/spec/integration/associations/embeds_one_spec.rb +24 -0
  63. data/spec/integration/associations/has_many_spec.rb +76 -0
  64. data/spec/integration/associations/has_one_spec.rb +76 -0
  65. data/spec/integration/bson_regexp_raw_spec.rb +20 -0
  66. data/spec/integration/criteria/date_field_spec.rb +41 -0
  67. data/spec/integration/criteria/logical_spec.rb +13 -0
  68. data/spec/integration/document_spec.rb +22 -0
  69. data/spec/integration/shardable_spec.rb +20 -4
  70. data/spec/lite_spec_helper.rb +12 -4
  71. data/spec/mongoid/association/accessors_spec.rb +238 -63
  72. data/spec/mongoid/association/embedded/embeds_many_models.rb +19 -0
  73. data/spec/mongoid/association/embedded/embeds_many_spec.rb +10 -0
  74. data/spec/mongoid/association/embedded/embeds_one_spec.rb +0 -2
  75. data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +193 -10
  76. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +140 -1
  77. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +105 -0
  78. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +2 -1
  79. data/spec/mongoid/clients/factory_spec.rb +8 -8
  80. data/spec/mongoid/clients/options_spec.rb +11 -11
  81. data/spec/mongoid/clients/sessions_spec.rb +8 -4
  82. data/spec/mongoid/clients/transactions_spec.rb +20 -8
  83. data/spec/mongoid/clients_spec.rb +2 -2
  84. data/spec/mongoid/contextual/atomic_spec.rb +22 -11
  85. data/spec/mongoid/contextual/geo_near_spec.rb +11 -2
  86. data/spec/mongoid/contextual/map_reduce_spec.rb +20 -5
  87. data/spec/mongoid/contextual/mongo_spec.rb +76 -53
  88. data/spec/mongoid/criteria/queryable/extensions/regexp_raw_spec.rb +1 -1
  89. data/spec/mongoid/criteria/queryable/extensions/regexp_spec.rb +7 -7
  90. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +1 -1
  91. data/spec/mongoid/criteria/queryable/extensions/time_spec.rb +19 -7
  92. data/spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb +28 -1
  93. data/spec/mongoid/criteria/queryable/mergeable_spec.rb +45 -12
  94. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +1051 -392
  95. data/spec/mongoid/criteria/queryable/selectable_spec.rb +52 -0
  96. data/spec/mongoid/criteria/queryable/storable_spec.rb +80 -2
  97. data/spec/mongoid/criteria_spec.rb +36 -2
  98. data/spec/mongoid/document_persistence_context_spec.rb +33 -0
  99. data/spec/mongoid/errors/no_client_config_spec.rb +2 -2
  100. data/spec/mongoid/errors/no_client_database_spec.rb +3 -3
  101. data/spec/mongoid/errors/no_client_hosts_spec.rb +3 -3
  102. data/spec/mongoid/fields_spec.rb +24 -1
  103. data/spec/mongoid/indexable_spec.rb +6 -4
  104. data/spec/mongoid/matchable/default_spec.rb +1 -1
  105. data/spec/mongoid/matchable/regexp_spec.rb +2 -2
  106. data/spec/mongoid/matchable_spec.rb +2 -2
  107. data/spec/mongoid/persistable/pushable_spec.rb +55 -1
  108. data/spec/mongoid/query_cache_spec.rb +77 -9
  109. data/spec/mongoid/relations/proxy_spec.rb +1 -1
  110. data/spec/mongoid/scopable_spec.rb +2 -1
  111. data/spec/mongoid/serializable_spec.rb +129 -18
  112. data/spec/mongoid/shardable_models.rb +1 -1
  113. data/spec/mongoid/shardable_spec.rb +2 -2
  114. data/spec/mongoid/tasks/database_rake_spec.rb +13 -13
  115. data/spec/mongoid/tasks/database_spec.rb +1 -1
  116. data/spec/shared/LICENSE +20 -0
  117. data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
  118. data/spec/shared/lib/mrss/cluster_config.rb +211 -0
  119. data/spec/shared/lib/mrss/constraints.rb +312 -0
  120. data/spec/shared/lib/mrss/lite_constraints.rb +175 -0
  121. data/spec/shared/lib/mrss/spec_organizer.rb +149 -0
  122. data/spec/spec_helper.rb +2 -31
  123. data/spec/support/child_process_helper.rb +76 -0
  124. data/spec/support/cluster_config.rb +3 -3
  125. data/spec/support/constraints.rb +26 -10
  126. data/spec/support/expectations.rb +3 -1
  127. data/spec/support/helpers.rb +11 -0
  128. data/spec/support/session_registry.rb +50 -0
  129. data/spec/support/spec_config.rb +12 -4
  130. metadata +520 -473
  131. metadata.gz.sig +0 -0
@@ -0,0 +1,149 @@
1
+ autoload :JSON, 'json'
2
+ autoload :FileUtils, 'fileutils'
3
+ autoload :Find, 'find'
4
+
5
+ module Mrss
6
+
7
+ autoload :ChildProcessHelper, 'mrss/child_process_helper'
8
+
9
+ # Organizes and runs all of the tests in the test suite in batches.
10
+ #
11
+ # Organizing the tests in batches serves two purposes:
12
+ #
13
+ # 1. This allows running unit tests before integration tests, therefore
14
+ # in theory revealing failures quicker on average.
15
+ # 2. This allows running some tests that have high intermittent failure rate
16
+ # in their own test process.
17
+ #
18
+ # This class aggregates RSpec results after the test runs.
19
+ class SpecOrganizer
20
+
21
+ class BucketsNotPrioritized < StandardError
22
+ end
23
+
24
+ def initialize(root: nil, classifiers:, priority_order:,
25
+ spec_root: nil, rspec_json_path: nil, rspec_all_json_path: nil
26
+ )
27
+ @spec_root = spec_root || File.join(root, 'spec')
28
+ @classifiers = classifiers
29
+ @priority_order = priority_order
30
+ @rspec_json_path = rspec_json_path || File.join(root, 'tmp/rspec.json')
31
+ @rspec_all_json_path = rspec_all_json_path || File.join(root, 'tmp/rspec-all.json')
32
+ end
33
+
34
+ attr_reader :spec_root, :classifiers, :priority_order
35
+ attr_reader :rspec_json_path, :rspec_all_json_path
36
+
37
+ def buckets
38
+ @buckets ||= {}.tap do |buckets|
39
+ Find.find(spec_root) do |path|
40
+ next unless File.file?(path)
41
+ next unless path =~ /_spec\.rb\z/
42
+ rel_path = path[(spec_root.length + 1)..path.length]
43
+
44
+ found = false
45
+ classifiers.each do |(regexp, category)|
46
+ if regexp =~ rel_path
47
+ buckets[category] ||= []
48
+ buckets[category] << File.join('spec', rel_path)
49
+ found = true
50
+ break
51
+ end
52
+ end
53
+
54
+ unless found
55
+ buckets[nil] ||= []
56
+ buckets[nil] << File.join('spec', rel_path)
57
+ end
58
+ end
59
+ end.freeze
60
+ end
61
+
62
+ def ordered_buckets
63
+ @ordered_buckets ||= {}.tap do |ordered_buckets|
64
+ buckets = self.buckets.dup
65
+ priority_order.each do |category|
66
+ files = buckets.delete(category)
67
+ ordered_buckets[category] = files
68
+ end
69
+
70
+ if files = buckets.delete(nil)
71
+ ordered_buckets[nil] = files
72
+ end
73
+
74
+ unless buckets.empty?
75
+ raise BucketsNotPrioritized, "Some buckets were not prioritized: #{buckets.keys.map(&:to_s).join(', ')}"
76
+ end
77
+ end.freeze
78
+ end
79
+
80
+ def run
81
+ FileUtils.rm_f(rspec_all_json_path)
82
+
83
+ failed = []
84
+ buckets = self.buckets.dup
85
+
86
+ priority_order.each do |category|
87
+ if files = buckets.delete(category)
88
+ unless run_files(category, files)
89
+ failed << category
90
+ end
91
+ end
92
+ end
93
+ if files = buckets.delete(nil)
94
+ unless run_files('remaining', files)
95
+ failed << 'remaining'
96
+ end
97
+ end
98
+
99
+ unless buckets.empty?
100
+ raise "Some buckets were not executed: #{buckets.keys.map(&:to_s).join(', ')}"
101
+ end
102
+
103
+ if failed.any?
104
+ raise "The following buckets failed: #{failed.map(&:to_s).join(', ')}"
105
+ end
106
+ end
107
+
108
+ def run_files(category, paths)
109
+ puts "Running #{category.to_s.gsub('_', ' ')} tests"
110
+ FileUtils.rm_f(rspec_json_path)
111
+ cmd = %w(rspec) + paths
112
+
113
+ begin
114
+ ChildProcessHelper.check_call(cmd)
115
+ ensure
116
+ if File.exist?(rspec_json_path)
117
+ if File.exist?(rspec_all_json_path)
118
+ merge_rspec_results
119
+ else
120
+ FileUtils.cp(rspec_json_path, rspec_all_json_path)
121
+ end
122
+ end
123
+ end
124
+
125
+ true
126
+ rescue ChildProcessHelper::SpawnError
127
+ false
128
+ end
129
+
130
+ def merge_rspec_results
131
+ all = JSON.parse(File.read(rspec_all_json_path))
132
+ new = JSON.parse(File.read(rspec_json_path))
133
+ all['examples'] += new.delete('examples')
134
+ new.delete('summary').each do |k, v|
135
+ all['summary'][k] += v
136
+ end
137
+ new.delete('version')
138
+ new.delete('summary_line')
139
+ unless new.empty?
140
+ raise "Unhandled rspec results keys: #{new.keys.join(', ')}"
141
+ end
142
+ # We do not merge summary lines, delete them from aggregated results
143
+ all.delete('summary_line')
144
+ File.open(rspec_all_json_path, 'w') do |f|
145
+ f << JSON.dump(all)
146
+ end
147
+ end
148
+ end
149
+ end
@@ -31,6 +31,7 @@ end
31
31
 
32
32
  require 'support/authorization'
33
33
  require 'support/expectations'
34
+ require 'support/helpers'
34
35
  require 'support/macros'
35
36
  require 'support/cluster_config'
36
37
  require 'support/constraints'
@@ -76,37 +77,6 @@ CONFIG = {
76
77
  }
77
78
  }
78
79
 
79
- def non_legacy_server?
80
- Mongoid::Clients.default.cluster.servers.first.features.write_command_enabled?
81
- end
82
-
83
- def testing_replica_set?
84
- Mongoid::Clients.default.cluster.replica_set?
85
- end
86
-
87
- def collation_supported?
88
- Mongoid::Clients.default.cluster.next_primary.features.collation_enabled?
89
- end
90
- alias :decimal128_supported? :collation_supported?
91
-
92
- def array_filters_supported?
93
- Mongoid::Clients.default.cluster.next_primary.features.array_filters_enabled?
94
- end
95
- alias :sessions_supported? :array_filters_supported?
96
-
97
- def transactions_supported?
98
- features = Mongoid::Clients.default.cluster.next_primary.features
99
- features.respond_to?(:transactions_enabled?) && features.transactions_enabled?
100
- end
101
-
102
- def testing_transactions?
103
- transactions_supported? && if Gem::Version.new(ClusterConfig.instance.fcv_ish) >= Gem::Version.new('4.2')
104
- %i(replica_set sharded).include?(ClusterConfig.instance.topology)
105
- else
106
- ClusterConfig.instance.topology == :replica_set
107
- end
108
- end
109
-
110
80
  # Set the database that the spec suite connects to.
111
81
  Mongoid.configure do |config|
112
82
  config.load_configuration(CONFIG)
@@ -156,6 +126,7 @@ end
156
126
 
157
127
  RSpec.configure do |config|
158
128
  config.raise_errors_for_deprecations!
129
+ config.include(Helpers)
159
130
  config.include(Mongoid::Expectations)
160
131
  config.extend(Constraints)
161
132
  config.extend(Mongoid::Macros)
@@ -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
@@ -99,7 +99,7 @@ class ClusterConfig
99
99
  if topology == :sharded
100
100
  shards = client.use(:admin).command(listShards: 1).first
101
101
  shard = shards['shards'].first
102
- address_str = shard['host'].sub(/^.*\//, '').sub(/,.*/, '')
102
+ address_str = shard['host'].sub(/\A.*\//, '').sub(/,.*/, '')
103
103
  client = ClusterTools.instance.direct_client(address_str,
104
104
  SpecConfig.instance.test_options.merge(SpecConfig.instance.auth_options).merge(connect: :direct))
105
105
  end
@@ -133,8 +133,8 @@ class ClusterConfig
133
133
 
134
134
  @topology ||= begin
135
135
  topology = client.cluster.topology.class.name.sub(/.*::/, '')
136
- topology = topology.gsub(/([A-Z])/) { |match| '_' + match.downcase }.sub(/^_/, '')
137
- if topology =~ /^replica_set/
136
+ topology = topology.gsub(/([A-Z])/) { |match| '_' + match.downcase }.sub(/\A_/, '')
137
+ if topology =~ /\Areplica_set/
138
138
  topology = 'replica_set'
139
139
  end
140
140
  topology.to_sym
@@ -5,11 +5,11 @@ module Constraints
5
5
  RAILS_VERSION = ActiveSupport.version.to_s.split('.')[0..1].join('.').freeze
6
6
 
7
7
  def min_rails_version(version)
8
- unless version =~ /^\d+\.\d+$/
8
+ unless version =~ /\A\d+\.\d+\z/
9
9
  raise ArgumentError, "Version can only be major.minor: #{version}"
10
10
  end
11
11
 
12
- before do
12
+ before(:all) do
13
13
  if version > RAILS_VERSION
14
14
  skip "Rails version #{version} or higher required, we have #{RAILS_VERSION}"
15
15
  end
@@ -17,11 +17,11 @@ module Constraints
17
17
  end
18
18
 
19
19
  def max_rails_version(version)
20
- unless version =~ /^\d+\.\d+$/
20
+ unless version =~ /\A\d+\.\d+\z/
21
21
  raise ArgumentError, "Version can only be major.minor: #{version}"
22
22
  end
23
23
 
24
- before do
24
+ before(:all) do
25
25
  if version < RAILS_VERSION
26
26
  skip "Rails version #{version} or lower required, we have #{RAILS_VERSION}"
27
27
  end
@@ -29,11 +29,11 @@ module Constraints
29
29
  end
30
30
 
31
31
  def min_server_version(version)
32
- unless version =~ /^\d+\.\d+$/
32
+ unless version =~ /\A\d+\.\d+\z/
33
33
  raise ArgumentError, "Version can only be major.minor: #{version}"
34
34
  end
35
35
 
36
- before do
36
+ before(:all) do
37
37
  if version > ClusterConfig.instance.server_version
38
38
  skip "Server version #{version} or higher required, we have #{ClusterConfig.instance.server_version}"
39
39
  end
@@ -41,11 +41,11 @@ module Constraints
41
41
  end
42
42
 
43
43
  def max_server_version(version)
44
- unless version =~ /^\d+\.\d+$/
44
+ unless version =~ /\A\d+\.\d+\z/
45
45
  raise ArgumentError, "Version can only be major.minor: #{version}"
46
46
  end
47
47
 
48
- before do
48
+ before(:all) do
49
49
  if version < ClusterConfig.instance.short_server_version
50
50
  skip "Server version #{version} or lower required, we have #{ClusterConfig.instance.server_version}"
51
51
  end
@@ -54,9 +54,11 @@ module Constraints
54
54
 
55
55
  def require_topology(*topologies)
56
56
  invalid_topologies = topologies - [:single, :replica_set, :sharded]
57
+
57
58
  unless invalid_topologies.empty?
58
59
  raise ArgumentError, "Invalid topologies requested: #{invalid_topologies.join(', ')}"
59
60
  end
61
+
60
62
  before(:all) do
61
63
  unless topologies.include?(topology = ClusterConfig.instance.topology)
62
64
  skip "Topology #{topologies.join(' or ')} required, we have #{topology}"
@@ -73,8 +75,22 @@ module Constraints
73
75
  end
74
76
 
75
77
  def require_transaction_support
76
- min_server_fcv '4.0'
77
- require_topology :replica_set
78
+ before(:all) do
79
+ case ClusterConfig.instance.topology
80
+ when :single
81
+ skip 'Transactions tests require a replica set (4.0+) or a sharded cluster (4.2+)'
82
+ when :replica_set
83
+ unless ClusterConfig.instance.server_version >= '4.0'
84
+ skip 'Transactions tests in a replica set topology require server 4.0+'
85
+ end
86
+ when :sharded
87
+ unless ClusterConfig.instance.server_version >= '4.2'
88
+ skip 'Transactions tests in a sharded cluster topology require server 4.2+'
89
+ end
90
+ else
91
+ raise NotImplementedError
92
+ end
93
+ end
78
94
  end
79
95
 
80
96
  def require_tls
@@ -14,14 +14,16 @@ module Mongoid
14
14
  end
15
15
 
16
16
  def expect_query(number)
17
+ rv = nil
17
18
  RSpec::Mocks.with_temporary_scope do
18
19
  if number > 0
19
20
  expect_any_instance_of(connection_class).to receive(:command_started).exactly(number).times.and_call_original
20
21
  else
21
22
  expect_any_instance_of(connection_class).not_to receive(:command_started)
22
23
  end
23
- yield
24
+ rv = yield
24
25
  end
26
+ rv
25
27
  end
26
28
 
27
29
  def expect_no_queries(&block)
@@ -0,0 +1,11 @@
1
+ module Helpers
2
+ # Reloads the specified model class.
3
+ #
4
+ # @param [ String | Symbol ] name Class name to reload.
5
+ def reload_model(name)
6
+ Object.class_eval do
7
+ remove_const(name)
8
+ end
9
+ load "spec/app/models/#{name.to_s.underscore}.rb"
10
+ end
11
+ end
@@ -0,0 +1,50 @@
1
+ require 'singleton'
2
+
3
+ module Mongo
4
+ class Client
5
+ alias :get_session_without_tracking :get_session
6
+
7
+ def get_session(options = {})
8
+ get_session_without_tracking(options).tap do |session|
9
+ SessionRegistry.instance.register(session)
10
+ end
11
+ end
12
+ end
13
+
14
+ class Session
15
+ alias :end_session_without_tracking :end_session
16
+
17
+ def end_session
18
+ SessionRegistry.instance.unregister(self)
19
+ end_session_without_tracking
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ class SessionRegistry
26
+ include Singleton
27
+
28
+ def initialize
29
+ @registry = {}
30
+ end
31
+
32
+ def register(session)
33
+ @registry[session.session_id] = session if session
34
+ end
35
+
36
+ def unregister(session)
37
+ @registry.delete(session.session_id)
38
+ end
39
+
40
+ def verify_sessions_ended!
41
+ unless @registry.empty?
42
+ sessions = @registry.map { |_, session| session }
43
+ raise "Session registry contains live sessions: #{sessions.join(', ')}"
44
+ end
45
+ end
46
+
47
+ def clear_registry
48
+ @registry = {}
49
+ end
50
+ end