diff --git a/.evergreen/config.yml b/.evergreen/config.yml index a505b0755f..c170cb3da4 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -88,7 +88,6 @@ functions: export RVM_RUBY="${RVM_RUBY}" export RAILS="${RAILS}" export DRIVER="${DRIVER}" - export I18N="${I18N}" export TEST_I18N_FALLBACKS="${TEST_I18N_FALLBACKS}" export FLE="${FLE}" EOT @@ -276,7 +275,6 @@ functions: RVM_RUBY="${RVM_RUBY}" \ RAILS="${RAILS}" \ DRIVER="${DRIVER}" \ - I18N="${I18N}" \ TEST_I18N_FALLBACKS="${TEST_I18N_FALLBACKS}" \ FLE="${FLE}" \ .evergreen/run-tests-docker.sh @@ -458,10 +456,6 @@ axes: - id: "ruby" display_name: Ruby Version values: - - id: "ruby-2.6" - display_name: ruby-2.6 - variables: - RVM_RUBY: "ruby-2.6" - id: "ruby-2.7" display_name: ruby-2.7 variables: @@ -486,10 +480,10 @@ axes: - id: "jruby" display_name: JRuby Version values: - - id: "jruby-9.3" - display_name: jruby-9.3 + - id: "jruby-9.4" + display_name: jruby-9.4 variables: - RVM_RUBY: "jruby-9.3" + RVM_RUBY: "jruby-9.4" - id: "os" display_name: OS @@ -497,11 +491,11 @@ axes: - id: actual-ubuntu-22.04 display_name: "Ubuntu 22.04" run_on: ubuntu2204-small - - id: ubuntu-18.04 - display_name: "Ubuntu 18.04" + - id: ubuntu-20.04 + display_name: "Ubuntu 20.04" run_on: ubuntu2004-small variables: - DOCKER_DISTRO: ubuntu1804 + DOCKER_DISTRO: ubuntu2004 - id: ubuntu-22.04 display_name: "Ubuntu 22.04" run_on: ubuntu2004-small @@ -573,10 +567,6 @@ axes: display_name: "Rails master" variables: RAILS: "master" - - id: "5.2" - display_name: "Rails 5.2" - variables: - RAILS: "5.2" - id: "6.0" display_name: "Rails 6.0" variables: @@ -594,16 +584,6 @@ axes: variables: RAILS: "7.1" - - id: "i18n" - display_name: I18n version - values: - - id: '1.0' - display_name: "i18n-1.0" - variables: - I18N: "1.0" - - id: current - display_name: "i18n-current" - - id: "test-i18n-fallbacks" display_name: Test i18n fallbacks values: @@ -678,7 +658,7 @@ buildvariants: - matrix_name: "jruby" matrix_spec: - jruby: ["jruby-9.3"] + jruby: ["jruby-9.4"] driver: ["current"] topology: ['replica-set', 'sharded-cluster'] mongodb-version: '5.0' @@ -698,17 +678,6 @@ buildvariants: tasks: - name: "test" -- matrix_name: "ruby-2.6" - matrix_spec: - ruby: ["ruby-2.6"] - driver: ["current"] - topology: ['replica-set'] - mongodb-version: ['4.0'] - os: rhel80 - display_name: "${ruby}, ${driver}, ${mongodb-version}, ${topology}" - tasks: - - name: "test" - - matrix_name: "driver-upcoming" matrix_spec: driver: [master, stable] @@ -723,7 +692,7 @@ buildvariants: - matrix_name: "driver-oldstable" matrix_spec: driver: [oldstable, min] - ruby: ["ruby-2.6"] + ruby: ["ruby-2.7"] mongodb-version: "4.0" topology: ['replica-set', 'sharded-cluster'] os: rhel80 @@ -734,7 +703,7 @@ buildvariants: - matrix_name: "driver-min" matrix_spec: driver: [min] - ruby: ["ruby-2.6"] + ruby: ["ruby-2.7"] mongodb-version: "3.6" topology: "standalone" os: rhel80 @@ -790,81 +759,57 @@ buildvariants: tasks: - name: "test" -- matrix_name: "rails-5" - matrix_spec: - ruby: ["ruby-2.7"] - driver: ["current"] - mongodb-version: "4.0" - topology: "standalone" - rails: ['5.2'] - os: rhel80 - display_name: "${rails}, ${driver}, ${mongodb-version}" - tasks: - - name: "test" - -- matrix_name: "i18n-1.0" - matrix_spec: - ruby: "ruby-2.6" - driver: ["current"] - mongodb-version: "4.4" - topology: "standalone" - i18n: '1.0' - os: rhel80 - display_name: "i18n-1.0 ${rails}, ${driver}, ${mongodb-version}" - tasks: - - name: "test" - - matrix_name: "i18n-fallbacks" matrix_spec: - ruby: "ruby-2.6" + ruby: "ruby-2.7" driver: ["current"] mongodb-version: "4.2" topology: "standalone" - i18n: '*' test-i18n-fallbacks: yes os: rhel80 - display_name: "i18n fallbacks ${rails}, ${driver}, ${mongodb-version}, ${i18n}" + display_name: "i18n fallbacks ${rails}, ${driver}, ${mongodb-version}" tasks: - name: "test" -- matrix_name: app-tests-ruby-3 +- matrix_name: app-tests-rails-7 matrix_spec: - ruby: ["ruby-3.0", "ruby-3.1", "ruby-3.2"] + ruby: ["ruby-3.1", "ruby-3.2"] driver: ["current"] mongodb-version: '6.0' topology: standalone app-tests: yes - rails: ['6.0', '6.1', '7.0', '7.1'] - os: rhel80 + rails: ['6.1', '7.0', '7.1'] + os: ubuntu-20.04 display_name: "app tests ${driver}, ${ruby}, ${rails}" tasks: - name: "test" -- matrix_name: app-tests-ruby-2.7 +- matrix_name: app-tests-rails-6-0 matrix_spec: - ruby: ruby-2.7 + ruby: ["ruby-2.7"] driver: ["current"] mongodb-version: '5.0' topology: standalone app-tests: yes - rails: ['5.2'] + rails: ['6.0'] os: rhel80 display_name: "app tests ${driver}, ${ruby}, ${rails}" tasks: - name: "test" -- matrix_name: app-tests-jruby - matrix_spec: - jruby: ["jruby-9.3"] - driver: ["current"] - mongodb-version: '5.0' - topology: standalone - app-tests: yes - rails: ['6.0'] - os: ubuntu-18.04 - display_name: "app tests ${driver}, ${jruby}" - tasks: - - name: "test" +# https://github.com/rails/rails/issues/49737 +#- matrix_name: app-tests-jruby-9-4 +# matrix_spec: +# jruby: ["jruby-9.4"] +# driver: ["current"] +# mongodb-version: '5.0' +# topology: standalone +# app-tests: yes +# rails: ['7.1'] +# os: ubuntu-20.04 +# display_name: "app tests ${driver}, ${jruby}" +# tasks: +# - name: "test" - matrix_name: "auto-encryption" matrix_spec: diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index e7a5a2aba1..f14d932374 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -83,10 +83,6 @@ axes: - id: "ruby" display_name: Ruby Version values: - - id: "ruby-2.6" - display_name: ruby-2.6 - variables: - RVM_RUBY: "ruby-2.6" - id: "ruby-2.7" display_name: ruby-2.7 variables: @@ -111,10 +107,10 @@ axes: - id: "jruby" display_name: JRuby Version values: - - id: "jruby-9.3" - display_name: jruby-9.3 + - id: "jruby-9.4" + display_name: jruby-9.4 variables: - RVM_RUBY: "jruby-9.3" + RVM_RUBY: "jruby-9.4" - id: "os" display_name: OS @@ -122,11 +118,11 @@ axes: - id: actual-ubuntu-22.04 display_name: "Ubuntu 22.04" run_on: ubuntu2204-small - - id: ubuntu-18.04 - display_name: "Ubuntu 18.04" + - id: ubuntu-20.04 + display_name: "Ubuntu 20.04" run_on: ubuntu2004-small variables: - DOCKER_DISTRO: ubuntu1804 + DOCKER_DISTRO: ubuntu2004 - id: ubuntu-22.04 display_name: "Ubuntu 22.04" run_on: ubuntu2004-small @@ -198,10 +194,6 @@ axes: display_name: "Rails master" variables: RAILS: "master" - - id: "5.2" - display_name: "Rails 5.2" - variables: - RAILS: "5.2" - id: "6.0" display_name: "Rails 6.0" variables: @@ -219,16 +211,6 @@ axes: variables: RAILS: "7.1" - - id: "i18n" - display_name: I18n version - values: - - id: '1.0' - display_name: "i18n-1.0" - variables: - I18N: "1.0" - - id: current - display_name: "i18n-current" - - id: "test-i18n-fallbacks" display_name: Test i18n fallbacks values: diff --git a/.evergreen/config/commands.yml.erb b/.evergreen/config/commands.yml.erb index 396ab4531b..1410abdd8f 100644 --- a/.evergreen/config/commands.yml.erb +++ b/.evergreen/config/commands.yml.erb @@ -62,7 +62,6 @@ functions: export RVM_RUBY="${RVM_RUBY}" export RAILS="${RAILS}" export DRIVER="${DRIVER}" - export I18N="${I18N}" export TEST_I18N_FALLBACKS="${TEST_I18N_FALLBACKS}" export FLE="${FLE}" EOT @@ -250,7 +249,6 @@ functions: RVM_RUBY="${RVM_RUBY}" \ RAILS="${RAILS}" \ DRIVER="${DRIVER}" \ - I18N="${I18N}" \ TEST_I18N_FALLBACKS="${TEST_I18N_FALLBACKS}" \ FLE="${FLE}" \ .evergreen/run-tests-docker.sh diff --git a/.evergreen/config/variants.yml.erb b/.evergreen/config/variants.yml.erb index 78cfa7a66e..a4e514e57d 100644 --- a/.evergreen/config/variants.yml.erb +++ b/.evergreen/config/variants.yml.erb @@ -45,7 +45,7 @@ buildvariants: - matrix_name: "jruby" matrix_spec: - jruby: ["jruby-9.3"] + jruby: ["jruby-9.4"] driver: ["current"] topology: ['replica-set', 'sharded-cluster'] mongodb-version: '5.0' @@ -65,17 +65,6 @@ buildvariants: tasks: - name: "test" -- matrix_name: "ruby-2.6" - matrix_spec: - ruby: ["ruby-2.6"] - driver: ["current"] - topology: ['replica-set'] - mongodb-version: ['4.0'] - os: rhel80 - display_name: "${ruby}, ${driver}, ${mongodb-version}, ${topology}" - tasks: - - name: "test" - - matrix_name: "driver-upcoming" matrix_spec: driver: [master, stable] @@ -90,7 +79,7 @@ buildvariants: - matrix_name: "driver-oldstable" matrix_spec: driver: [oldstable, min] - ruby: ["ruby-2.6"] + ruby: ["ruby-2.7"] mongodb-version: "4.0" topology: ['replica-set', 'sharded-cluster'] os: rhel80 @@ -101,7 +90,7 @@ buildvariants: - matrix_name: "driver-min" matrix_spec: driver: [min] - ruby: ["ruby-2.6"] + ruby: ["ruby-2.7"] mongodb-version: "3.6" topology: "standalone" os: rhel80 @@ -157,81 +146,57 @@ buildvariants: tasks: - name: "test" -- matrix_name: "rails-5" - matrix_spec: - ruby: ["ruby-2.7"] - driver: ["current"] - mongodb-version: "4.0" - topology: "standalone" - rails: ['5.2'] - os: rhel80 - display_name: "${rails}, ${driver}, ${mongodb-version}" - tasks: - - name: "test" - -- matrix_name: "i18n-1.0" - matrix_spec: - ruby: "ruby-2.6" - driver: ["current"] - mongodb-version: "4.4" - topology: "standalone" - i18n: '1.0' - os: rhel80 - display_name: "i18n-1.0 ${rails}, ${driver}, ${mongodb-version}" - tasks: - - name: "test" - - matrix_name: "i18n-fallbacks" matrix_spec: - ruby: "ruby-2.6" + ruby: "ruby-2.7" driver: ["current"] mongodb-version: "4.2" topology: "standalone" - i18n: '*' test-i18n-fallbacks: yes os: rhel80 - display_name: "i18n fallbacks ${rails}, ${driver}, ${mongodb-version}, ${i18n}" + display_name: "i18n fallbacks ${rails}, ${driver}, ${mongodb-version}" tasks: - name: "test" -- matrix_name: app-tests-ruby-3 +- matrix_name: app-tests-rails-7 matrix_spec: - ruby: ["ruby-3.0", "ruby-3.1", "ruby-3.2"] + ruby: ["ruby-3.1", "ruby-3.2"] driver: ["current"] mongodb-version: '6.0' topology: standalone app-tests: yes - rails: ['6.0', '6.1', '7.0', '7.1'] - os: rhel80 + rails: ['6.1', '7.0', '7.1'] + os: ubuntu-20.04 display_name: "app tests ${driver}, ${ruby}, ${rails}" tasks: - name: "test" -- matrix_name: app-tests-ruby-2.7 +- matrix_name: app-tests-rails-6-0 matrix_spec: - ruby: ruby-2.7 + ruby: ["ruby-2.7"] driver: ["current"] mongodb-version: '5.0' topology: standalone app-tests: yes - rails: ['5.2'] + rails: ['6.0'] os: rhel80 display_name: "app tests ${driver}, ${ruby}, ${rails}" tasks: - name: "test" -- matrix_name: app-tests-jruby - matrix_spec: - jruby: ["jruby-9.3"] - driver: ["current"] - mongodb-version: '5.0' - topology: standalone - app-tests: yes - rails: ['6.0'] - os: ubuntu-18.04 - display_name: "app tests ${driver}, ${jruby}" - tasks: - - name: "test" +# https://github.com/rails/rails/issues/49737 +#- matrix_name: app-tests-jruby-9-4 +# matrix_spec: +# jruby: ["jruby-9.4"] +# driver: ["current"] +# mongodb-version: '5.0' +# topology: standalone +# app-tests: yes +# rails: ['7.1'] +# os: ubuntu-20.04 +# display_name: "app tests ${driver}, ${jruby}" +# tasks: +# - name: "test" - matrix_name: "auto-encryption" matrix_spec: diff --git a/.evergreen/make-github-actions b/.evergreen/make-github-actions index 4512295b35..e0df19682a 100755 --- a/.evergreen/make-github-actions +++ b/.evergreen/make-github-actions @@ -55,11 +55,10 @@ class EvergreenConfig < YamlConfig # these will be later mapped to gemfile driver: spec[:driver], rails: spec[:rails], - i18n: spec[:i18n], } missing = node.map {|k, v| k if v.blank? }.compact - missing -= %i[driver rails i18n] + missing -= %i[driver rails] if missing.present? puts "Skipping invalid Evergreen buildvariant '#{name}'. Keys missing: #{missing}" @@ -101,7 +100,7 @@ class GithubConfig < YamlConfig end class Transmogrifier - SPLATTABLE_FIELDS = %i[mongodb topology ruby rails driver i18n] + SPLATTABLE_FIELDS = %i[mongodb topology ruby rails driver] COMPACTABLE_FIELDS = %i[mongodb topology ruby gemfile] attr_reader :eg_config, @@ -205,18 +204,16 @@ class Transmogrifier end def extract_gemfile!(node) - node[:gemfile] = get_gemfile(*node.values_at(:driver, :rails, :i18n)) + node[:gemfile] = get_gemfile(*node.values_at(:driver, :rails)) end # Ported from run-tests.sh - def get_gemfile(driver, rails, i18n) - driver, rails, i18n = [driver, rails, i18n].map {|v| v&.to_s } + def get_gemfile(driver, rails) + driver, rails = [driver, rails].map {|v| v&.to_s } if driver && driver != 'current' "gemfiles/driver_#{driver.underscore}.gemfile" elsif rails && rails != '6.1' # TODO: "6.1" should be renamed to "current" in Evergreen "gemfiles/rails-#{rails}.gemfile" - elsif i18n && i18n == '1.0' - 'gemfiles/i18n-1.0.gemfile' else 'Gemfile' end diff --git a/.evergreen/run-tests-docker.sh b/.evergreen/run-tests-docker.sh index 444305e8f7..038be89d08 100755 --- a/.evergreen/run-tests-docker.sh +++ b/.evergreen/run-tests-docker.sh @@ -10,7 +10,7 @@ fi params= for var in MONGODB_VERSION TOPOLOGY RVM_RUBY \ - SINGLE_MONGOS AUTH SSL APP_TESTS FLE + SINGLE_MONGOS AUTH SSL APP_TESTS FLE RAILS DRIVER TEST_I18N_FALLBACKS do value="${!var}" if test -n "$value"; then diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 8c12efe810..b48d881f40 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -52,6 +52,8 @@ if echo $RVM_RUBY |grep -q jruby && test "$DRIVER" = master-jruby; then gem install *.gem) fi +git config --global --add safe.directory "*" + if test "$DRIVER" = "master"; then bundle install --gemfile=gemfiles/driver_master.gemfile BUNDLE_GEMFILE=gemfiles/driver_master.gemfile @@ -85,9 +87,6 @@ elif test "$RAILS" = "master-jruby"; then elif test -n "$RAILS" && test "$RAILS" != 6.1; then bundle install --gemfile=gemfiles/rails-"$RAILS".gemfile BUNDLE_GEMFILE=gemfiles/rails-"$RAILS".gemfile -elif test "$I18N" = "1.0"; then - bundle install --gemfile=gemfiles/i18n-1.0.gemfile - BUNDLE_GEMFILE=gemfiles/i18n-1.0.gemfile else bundle install fi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dc8961f5cf..4137145092 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,14 +42,6 @@ jobs: driver: current gemfile: Gemfile experimental: false - - mongodb: '6.0' - ruby: ruby-3.1 - topology: replica_set - os: ubuntu-20.04 - task: test - driver: master - gemfile: gemfiles/driver_master.gemfile - experimental: true - mongodb: '6.0' ruby: ruby-3.0 topology: replica_set @@ -109,17 +101,7 @@ jobs: gemfile: gemfiles/rails-6.0.gemfile experimental: false - mongodb: '6.0' - ruby: ruby-2.7 - topology: server - os: ubuntu-20.04 - task: test - driver: current - rails: '5.2' - fle: helper - gemfile: gemfiles/rails-5.2.gemfile - experimental: false - - mongodb: '6.0' - ruby: jruby-9.3 + ruby: jruby-9.4 topology: server os: ubuntu-20.04 task: test diff --git a/.rubocop.yml b/.rubocop.yml index 59a04444b2..bdb558ac7c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,7 +4,7 @@ require: - rubocop-rspec AllCops: - TargetRubyVersion: 2.6 + TargetRubyVersion: 2.7 NewCops: enable Exclude: - 'spec/shared/**/*' diff --git a/README.md b/README.md index 8ebdee4eab..f2394f4e02 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ Compatibility Mongoid supports and is tested against: -- MRI 2.6 - 3.1 -- JRuby 9.3 +- MRI 2.7 - 3.1 +- JRuby 9.4 - MongoDB server 3.6 - 6.0 Issues @@ -39,7 +39,7 @@ Support License ------- -Copyright (c) 2015-Present MongoDB Inc. +Copyright (c) 2015-Present MongoDB Inc. Copyright (c) 2009-2016 Durran Jordan Permission is hereby granted, free of charge, to any person obtaining diff --git a/Rakefile b/Rakefile index 0a02a96754..eb8db1a69a 100644 --- a/Rakefile +++ b/Rakefile @@ -67,6 +67,20 @@ namespace :eg do end end +namespace :generate do + desc 'Generates a mongoid.yml from the template' + task :config do + require 'mongoid' + require 'erb' + + template_path = 'lib/rails/generators/mongoid/config/templates/mongoid.yml' + database_name = ENV['DATABASE_NAME'] || 'my_db' + + config = ERB.new(File.read(template_path), trim_mode: '-').result(binding) + File.write('mongoid.yml', config) + end +end + CLASSIFIERS = [ [%r,^mongoid/attribute,, :attributes], [%r,^mongoid/association/[or],, :associations_referenced], diff --git a/docs/includes/unicode-ballot-x.rst b/docs/includes/unicode-ballot-x.rst new file mode 100644 index 0000000000..50c3667ae6 --- /dev/null +++ b/docs/includes/unicode-ballot-x.rst @@ -0,0 +1 @@ +.. |x| unicode:: U+2717 diff --git a/docs/installation.txt b/docs/installation.txt index 0ce9cbb726..7247273d61 100644 --- a/docs/installation.txt +++ b/docs/installation.txt @@ -27,7 +27,7 @@ To install the gem with bundler, include the following in your ``Gemfile``: .. code-block:: ruby - gem 'mongoid', '~> 9.0.0' + gem 'mongoid' Using Mongoid with a New Rails Application ========================================== diff --git a/docs/reference/associations.txt b/docs/reference/associations.txt index 011549710c..520ca44e5e 100644 --- a/docs/reference/associations.txt +++ b/docs/reference/associations.txt @@ -693,30 +693,22 @@ Querying Loaded Associations ```````````````````````````` Mongoid query methods can be used on embedded associations of documents which -are already loaded in the application. This mechanism is sometimes called -"embedded matching" or "embedded document matching" and it is implemented -entirely in Mongoid - the queries are NOT sent to the server. - -Embedded matching is supported for most general-purpose query operators. It -is not implemented for :ref:`text search `, :manual:`geospatial query -operators `, -operators that execute JavaScript code (:manual:`$where `) -and operators that are implemented via other server functionality such as -:manual:`$expr ` -and :manual:`$jsonSchema `. +are already loaded in the application. This mechanism is called +"embedded matching" and it is implemented entirely in Mongoid--the queries +are NOT sent to the server. The following operators are supported: -- :manual:`Bitwise operators ` - :manual:`Comparison operators ` - :manual:`Logical operators ` -- :manual:`$comment ` +- :manual:`Array query operators ` - :manual:`$exists ` - :manual:`$mod ` - :manual:`$type ` - :manual:`$regex ` (``$options`` field is only supported when the ``$regex`` argument is a string) -- :manual:`Array query operators ` +- :manual:`Bitwise operators ` +- :manual:`$comment ` For example, using the model definitions just given, we could query tours on a loaded band: @@ -726,33 +718,19 @@ tours on a loaded band: band = Band.where(name: 'Astral Projection').first tours = band.tours.where(year: {'$gte' => 2000}) -Embedded Matching Vs Server Behavior +Embedded Matching vs Server Behavior ```````````````````````````````````` -Mongoid aims to provide the same semantics when performing embedded matching -as those of MongoDB server. This means, for example, when the server only -accepts arguments of particular types for a particular operator, Mongoid -would also only accept arguments of the corresponding types. - -The following deviations are known: - -- Mongoid embedded matchers, because they are implemented on the client side, - behave the same regardless of the server version that backs the application. - As such, it is possible for Mongoid to deviate from server behavior if - the server itself behaves differently in different versions. All operators are implemented in - Mongoid regardless of backing deployment's server version. - - As of this writing, the known cases of such deviation are: - - - 3.2 and earlier servers not validating ``$size`` arguments as strictly as newer versions do. - - 4.0 and earlier servers not validating ``$type`` arguments as strictly as newer versions - do (allowing invalid arguments like 0, for example). - - 3.2 and earlier servers not supporting ``Decimal128`` for ``$type``, as well as allowing invalid - arguments such as negative numbers (smaller than -1) and numbers that are greater than 19 - (not including 127, the argument for the ``MaxKey`` type). - - 3.4 and earlier servers not supporting arrays for ``$type``. - - 3.0 and earlier servers not supporting bitwise operators. +Mongoid's embedded matching aims to support the same functionality and +semantics as native queries on the latest MongoDB server version. +Note the following known limitations: +- Embedded matching is not implemented for :ref:`text search `, + :manual:`geospatial query operators `, + operators that execute JavaScript code (:manual:`$where `) + and operators that are implemented via other server functionality such as + :manual:`$expr ` + and :manual:`$jsonSchema `. - Mongoid DSL expands ``Range`` arguments to hashes with ``$gte`` and ``$lte`` conditions. `In some cases `_ @@ -764,7 +742,9 @@ The following deviations are known: possible `_ to specify a regular expression object as the pattern and also provide options. -In general, Mongoid adopts the behavior of current server versions and validates more strictly. +- MongoDB Server 4.0 and earlier servers do not validate ``$type`` arguments strictly + (for example, allowing invalid arguments like 0). This is validated more strictly on + the client side. .. _omit-id: diff --git a/docs/reference/compatibility.txt b/docs/reference/compatibility.txt index 4c7751a67a..8b5a023402 100644 --- a/docs/reference/compatibility.txt +++ b/docs/reference/compatibility.txt @@ -34,47 +34,17 @@ specified Mongoid versions. - Driver 2.17-2.10 - Driver 2.9-2.7 - * - 8.1 + * - 8.0 thru 9.0 - |checkmark| - - - * - 8.0 - - |checkmark| - - - - - - * - 7.5 + * - 7.2 thru 7.5 - |checkmark| - |checkmark| - - * - 7.4 - - |checkmark| - - |checkmark| - - - - * - 7.3 - - |checkmark| - - |checkmark| - - - - * - 7.2 - - |checkmark| - - |checkmark| - - - - * - 7.1 - - |checkmark| - - |checkmark| - - |checkmark| - - * - 7.0 - - |checkmark| - - |checkmark| - - |checkmark| - - * - 6.4 + * - 7.0 thru 7.1 - |checkmark| - |checkmark| - |checkmark| @@ -102,8 +72,23 @@ is deprecated. - Ruby 2.4 - Ruby 2.3 - Ruby 2.2 - - JRuby 9.2 + - JRuby 9.4 - JRuby 9.3 + - JRuby 9.2 + + * - 9.0 + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - + - + - + - + - + - |checkmark| + - + - * - 8.1 - |checkmark| @@ -117,7 +102,7 @@ is deprecated. - - - |checkmark| - + - * - 8.0 - @@ -131,6 +116,7 @@ is deprecated. - - - |checkmark| + - * - 7.5 - @@ -142,8 +128,9 @@ is deprecated. - - - - - D + - - |checkmark| + - D * - 7.4 - @@ -155,8 +142,9 @@ is deprecated. - - - - - |checkmark| - + - + - |checkmark| * - 7.3 - @@ -168,8 +156,9 @@ is deprecated. - D - D - - - |checkmark| - + - + - |checkmark| * - 7.2 - @@ -181,8 +170,9 @@ is deprecated. - D - D - - - |checkmark| - + - + - |checkmark| * - 7.1 - @@ -194,8 +184,9 @@ is deprecated. - |checkmark| [#ruby-2.4]_ - |checkmark| - - - |checkmark| - + - + - |checkmark| * - 7.0 - @@ -207,21 +198,9 @@ is deprecated. - |checkmark| [#ruby-2.4]_ - |checkmark| - |checkmark| [#ruby-2.2]_ - - |checkmark| - - - - * - 6.4 - - - - - - - |checkmark| - - |checkmark| - - |checkmark| [#ruby-2.4]_ - - |checkmark| - - |checkmark| [#ruby-2.2]_ - - |checkmark| - - .. [#mongoid-7.3-ruby-3.0] Mongoid version 7.3.2 or higher is required. @@ -267,46 +246,7 @@ and will be removed in a next version. - MongoDB 3.0 - MongoDB 2.6 - * - 8.1 - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - - - - - * - 8.0 - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - - - - - * - 7.5 - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - D - - D - - D - - D - - * - 7.4 + * - 8.0 thru 9.0 - |checkmark| - |checkmark| - |checkmark| @@ -314,56 +254,17 @@ and will be removed in a next version. - |checkmark| - |checkmark| - |checkmark| - - D - - D - - D - - D - - * - 7.3 - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - D - - D - - D - - D - - * - 7.2 - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - D - - D - - D - - D - * - 7.1 - - - - + * - 7.4 thru 7.5 - |checkmark| - |checkmark| - |checkmark| - |checkmark| - |checkmark| - - D - - D - - D - - D - - * - 7.0 - - - - - - |checkmark| - - |checkmark| - - |checkmark| - |checkmark| - |checkmark| - D @@ -371,7 +272,7 @@ and will be removed in a next version. - D - D - * - 6.4 + * - 7.0 thru 7.3 - - - |checkmark| @@ -406,15 +307,15 @@ are supported by Mongoid. - Rails 5.2 - Rails 5.1 - * - 8.1 + * - 9.0 - |checkmark| [#rails-7.1]_ - |checkmark| - |checkmark| - |checkmark| - - |checkmark| [#rails-5-ruby-3.0]_ + - - - * - 8.0 + * - 8.0 thru 8.1 - |checkmark| [#rails-7.1]_ - |checkmark| - |checkmark| @@ -470,14 +371,6 @@ are supported by Mongoid. - |checkmark| - |checkmark| - * - 6.4 - - - - - - - - - - |checkmark| - - |checkmark| - .. [#rails-5-ruby-3.0] Using Rails 5.x with Ruby 3 is not supported. .. [#rails-6] Rails 6.0 requires Mongoid 7.0.5 or later. @@ -491,3 +384,85 @@ are supported by Mongoid. 8.0 and 8.1 stable branches. .. include:: /includes/unicode-checkmark.rst +.. include:: /includes/unicode-ballot-x.rst + +Rails Frameworks Support +------------------------ + +Ruby on Rails is comprised of a number of frameworks, which Mongoid attempts to +provide compatibility with wherever possible. + +Though Mongoid attempts to offer API compatibility with `Active Record `_, +libraries that depend directly on Active Record may not work as expected when +Mongoid is used as a drop-in replacement. + +.. note:: + + Mongoid can be used alongside Active Record within the same application without issue. + +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :class: compatibility-large no-padding + + * - Rails Framework + - Supported? + + * - ``ActionCable`` + - |checkmark| [#rails-actioncable-dependency]_ + + * - ``ActionMailbox`` + - |x| [#rails-activerecord-dependency]_ + + * - ``ActionMailer`` + - |checkmark| + + * - ``ActionPack`` + - |checkmark| + + * - ``ActionText`` + - |x| [#rails-activerecord-dependency]_ + + * - ``ActionView`` + - |checkmark| + + * - ``ActiveJob`` + - |checkmark| [#rails-activejob-dependency]_ + + * - ``ActiveModel`` + - |checkmark| [#rails-activemodel-dependency]_ + + * - ``ActiveStorage`` + - |x| [#rails-activerecord-dependency]_ + + * - ``ActiveSupport`` + - |checkmark| [#rails-activesupport-dependency]_ + +.. [#rails-actioncable-dependency] There is currently no MongoDB adapter for + ``ActionCable``, however any existing adapter (such as `Redis `_) + can be used successfully in conjunction with Mongoid models + +.. [#rails-activerecord-dependency] Depends directly on ``ActiveRecord`` + +.. [#rails-activemodel-dependency] ``Mongoid::Document`` includes ``ActiveModel::Model`` + and leverages ``ActiveModel::Validations`` for validations + +.. [#rails-activesupport-dependency] ``Mongoid`` requires ``ActiveSupport`` and + uses it extensively, including ``ActiveSupport::TimeWithZone`` for time handling. + + + +.. [#rails-activejob-dependency] Serialization of BSON & Mongoid objects works best + if you explicitly send ``BSON::ObjectId``'s as strings, and reconstitute them in the job: + + .. code-block:: ruby + + record = Model.find(...) + MyJob.perform_later(record._id.to_s) + + class MyJob < ApplicationJob + def perform(id_as_string) + record = Model.find(id_as_string) + # ... + end + end diff --git a/docs/reference/configuration.txt b/docs/reference/configuration.txt index ffb4e8e36e..92b1a44e21 100644 --- a/docs/reference/configuration.txt +++ b/docs/reference/configuration.txt @@ -131,100 +131,86 @@ for details on driver options. development: # Configure available database clients. (required) clients: - # Define the default client. (required) + # Defines the default client. (required) default: - # A uri may be defined for a client: - # uri: 'mongodb://user:password@myhost1.mydomain.com:27017/my_db' - # Please see driver documentation for details. Alternatively, you can - # define the following: - # - # Define the name of the default database that Mongoid can connect to. + # Mongoid can connect to a URI accepted by the driver: + # uri: mongodb://user:password@mongodb.domain.com:27017/my_db_development + + # Otherwise define the parameters separately. + # This defines the name of the default database that Mongoid can connect to. # (required). - database: my_db - # Provide the hosts the default client can connect to. Must be an array + database: my_db_development + # Provides the hosts the default client can connect to. Must be an array # of host:port pairs. (required) hosts: - - myhost1.mydomain.com:27017 - - myhost2.mydomain.com:27017 - - myhost3.mydomain.com:27017 + - localhost:27017 options: - # These options are Ruby driver options, documented in - # https://mongodb.com/docs/ruby-driver/current/reference/create-client/ + # Note that all options listed below are Ruby driver client options (the mongo gem). + # Please refer to the driver documentation of the version of the mongo gem you are using + # for the most up-to-date list of options. # Change the default write concern. (default = { w: 1 }) - write: - w: 1 + # write: + # w: 1 # Change the default read preference. Valid options for mode are: :secondary, # :secondary_preferred, :primary, :primary_preferred, :nearest # (default: primary) - read: - mode: :secondary_preferred - tag_sets: - - use: web + # read: + # mode: :secondary_preferred + # tag_sets: + # - use: web # The name of the user for authentication. - user: 'user' + # user: 'user' # The password of the user for authentication. - password: 'password' + # password: 'password' # The user's database roles. - roles: - - 'dbOwner' - - # Change the default authentication mechanism. Please see the - # driver documentation linked above for details on how to configure - # authentication. Valid options are :aws, :gssapi, :mongodb_cr, - # :mongodb_x509, :plain, :scram and :scram256 (default on 3.0 - # and higher servers is :scram, default on 2.6 servers is :plain) - auth_mech: :scram - - # Specify the auth source, i.e. the database or other source which - # contains the user's login credentials. Allowed values for auth source - # depend on the authentication mechanism, as explained in the server documentation: - # https://mongodb.com/docs/manual/reference/connection-string/#mongodb-urioption-urioption.authSource - # If no auth source is specified, the default auth source as - # determined by the driver will be used. Please refer to: - # https://mongodb.com/docs/ruby-driver/current/reference/authentication/#auth-source - auth_source: admin - - # Connect directly to and perform all operations on the specified - # server, bypassing replica set node discovery and monitoring. - # Exactly one host address must be specified. (default: false) - #direct_connection: true - - # Deprecated. Force the driver to connect in a specific way instead - # of automatically discovering the deployment type and connecting - # accordingly. To connect directly to a replica set node bypassing - # node discovery and monitoring, use direct_connection: true instead - # of this option. Possible values: :direct, :replica_set, :sharded. - # (default: none) - #connect: :direct - - # Change the default time in seconds the server monitors refresh their status + # roles: + # - 'dbOwner' + + # Change the default authentication mechanism. Valid options include: + # :scram, :scram256, :mongodb_cr, :mongodb_x509, :gssapi, :aws, :plain. + # MongoDB Server defaults to :scram, which will use "SCRAM-SHA-256" if available, + # otherwise fallback to "SCRAM-SHA-1" (:scram256 will always use "SCRAM-SHA-256".) + # This setting is handled by the MongoDB Ruby Driver. Please refer to: + # https://mongodb.com/docs/ruby-driver/current/reference/authentication/ + # auth_mech: :scram + + # The database or source to authenticate the user against. + # (default: the database specified above or admin) + # auth_source: admin + + # Force a the driver cluster to behave in a certain manner instead of auto- + # discovering. Can be one of: :direct, :replica_set, :sharded. Set to :direct + # when connecting to hidden members of a replica set. + # connect: :direct + + # Changes the default time in seconds the server monitors refresh their status # via hello commands. (default: 10) - heartbeat_frequency: 10 + # heartbeat_frequency: 10 # The time in seconds for selecting servers for a near read preference. (default: 0.015) - local_threshold: 0.015 + # local_threshold: 0.015 # The timeout in seconds for selecting a server for an operation. (default: 30) - server_selection_timeout: 30 + # server_selection_timeout: 30 # The maximum number of connections in the connection pool. (default: 5) - max_pool_size: 5 + # max_pool_size: 5 # The minimum number of connections in the connection pool. (default: 1) - min_pool_size: 1 + # min_pool_size: 1 # The time to wait, in seconds, in the connection pool for a connection - # to be checked in before timing out. (default: 1) - wait_queue_timeout: 1 + # to be checked in before timing out. (default: 5) + # wait_queue_timeout: 5 # The time to wait to establish a connection before timing out, in seconds. # (default: 10) - connect_timeout: 10 + # connect_timeout: 10 # How long to wait for a response for each operation sent to the # server. This timeout should be set to a value larger than the @@ -233,140 +219,193 @@ for details on driver options. # the server may continue executing an operation after the client # aborts it with the SocketTimeout exception. # (default: nil, meaning no timeout) - socket_timeout: 5 + # socket_timeout: 5 # The name of the replica set to connect to. Servers provided as seeds that do # not belong to this replica set will be ignored. - replica_set: my_replica_set + # replica_set: name + + # Compressors to use for wire protocol compression. (default is to not use compression) + # "zstd" requires zstd-ruby gem. "snappy" requires snappy gem. + # Refer to: https://www.mongodb.com/docs/ruby-driver/current/reference/create-client/#compression + # compressors: ["zstd", "snappy", "zlib"] # Whether to connect to the servers via ssl. (default: false) - ssl: true + # ssl: true # The certificate file used to identify the connection against MongoDB. - ssl_cert: /path/to/my.cert + # ssl_cert: /path/to/my.cert # The private keyfile used to identify the connection against MongoDB. # Note that even if the key is stored in the same file as the certificate, # both need to be explicitly specified. - ssl_key: /path/to/my.key + # ssl_key: /path/to/my.key # A passphrase for the private key. - ssl_key_pass_phrase: password + # ssl_key_pass_phrase: password - # Whether or not to do peer certification validation. (default: true) - ssl_verify: true + # Whether to do peer certification validation. (default: true) + # ssl_verify: true - # The file containing a set of concatenated certification authority certifications + # The file containing concatenated certificate authority certificates # used to validate certs passed from the other end of the connection. - ssl_ca_cert: /path/to/ca.cert + # ssl_ca_cert: /path/to/ca.cert - # Compressors to use. (default is to not use compression) - compressors: [zlib] + # Whether to truncate long log lines. (default: true) + # truncate_logs: true # Configure Mongoid-specific options. (optional) options: + # Allow BSON::Decimal128 to be parsed and returned directly in + # field values. When BSON 5 is present and the this option is set to false + # (the default), BSON::Decimal128 values in the database will be returned + # as BigDecimal. + # + # @note this option only has effect when BSON 5+ is present. Otherwise, + # the setting is ignored. + # allow_bson5_decimal128: false + # Application name that is printed to the MongoDB logs upon establishing - # a connection in server versions 3.4 or greater. Note that the name - # cannot exceed 128 bytes in length. It is also used as the database name - # if the database name is not explicitly defined. (default: nil) - app_name: MyApplicationName + # a connection. Note that the name cannot exceed 128 bytes in length. + # It is also used as the database name if the database name is not + # explicitly defined. (default: nil) + # app_name: nil - # Type of executor for queries scheduled using ``load_async`` method. + # When this flag is false, callbacks for embedded documents will not be + # called. This is the default in 9.0. # - # There are two possible values for this option: + # Setting this flag to true restores the pre-9.0 behavior, where callbacks + # for embedded documents are called. This may lead to stack overflow errors + # if there are more than cicrca 1000 embedded documents in the root + # document's dependencies graph. + # See https://jira.mongodb.org/browse/MONGOID-5658 for more details. + # around_callbacks_for_embeds: false + + # Sets the async_query_executor for the application. By default the thread pool executor + # is set to `:immediate`. Options are: # - # - :immediate - Queries will be immediately executed on a current thread. - # This is the default option. - # - :global_thread_pool - Queries will be executed asynchronously in - # background using a thread pool. - #async_query_executor: :immediate + # - :immediate - Initializes a single +Concurrent::ImmediateExecutor+ + # - :global_thread_pool - Initializes a single +Concurrent::ThreadPoolExecutor+ + # that uses the +async_query_concurrency+ for the +max_threads+ value. + # async_query_executor: :immediate # Mark belongs_to associations as required by default, so that saving a # model with a missing belongs_to association will trigger a validation - # error. (default: true) - belongs_to_required_by_default: true + # error. + # belongs_to_required_by_default: true - # Set the global discriminator key. (default: "_type") - discriminator_key: "_type" + # Set the global discriminator key. + # discriminator_key: "_type" - # Raise an exception when a field is redefined. (default: false) - duplicate_fields_exception: false + # Raise an exception when a field is redefined. + # duplicate_fields_exception: false # Defines how many asynchronous queries can be executed concurrently. - # This option should be set only if `async_query_executor` option is set + # This option should be set only if `async_query_executor` is set # to `:global_thread_pool`. - #global_executor_concurrency: nil + # global_executor_concurrency: nil - # Include the root model name in json serialization. (default: false) - include_root_in_json: false + # When this flag is true, any attempt to change the _id of a persisted + # document will raise an exception (`Errors::ImmutableAttribute`). + # This is the default in 9.0. Setting this flag to false restores the + # pre-9.0 behavior, where changing the _id of a persisted + # document might be ignored, or it might work, depending on the situation. + # immutable_ids: true - # Include the _type field in serialization. (default: false) - include_type_for_serialization: false + # Include the root model name in json serialization. + # include_root_in_json: false + + # # Include the _type field in serialization. + # include_type_for_serialization: false # Whether to join nested persistence contexts for atomic operations - # to parent contexts by default. (default: false) - join_contexts: false + # to parent contexts by default. + # join_contexts: false + + # When this flag is false (the default as of Mongoid 9.0), a document that + # is created or loaded will remember the storage options that were active + # when it was loaded, and will use those same options by default when + # saving or reloading itself. + # + # When this flag is true you'll get pre-9.0 behavior, where a document will + # not remember the storage options from when it was loaded/created, and + # subsequent updates will need to explicitly set up those options each time. + # + # For example: + # + # record = Model.with(collection: 'other_collection') { Model.first } + # + # This will try to load the first document from 'other_collection' and + # instantiate it as a Model instance. Pre-9.0, the record object would + # not remember that it came from 'other_collection', and attempts to + # update it or reload it would fail unless you first remembered to + # explicitly specify the collection every time. + # + # As of Mongoid 9.0, the record will remember that it came from + # 'other_collection', and updates and reloads will automatically default + # to that collection, for that record object. + # legacy_persistence_context_behavior: false # When this flag is false, a document will become read-only only once the # #readonly! method is called, and an error will be raised on attempting # to save or update such documents, instead of just on delete. When this # flag is true, a document is only read-only if it has been projected # using #only or #without, and read-only documents will not be - # deletable/destroyable, but will be savable/updatable. + # deletable/destroyable, but they will be savable/updatable. # When this feature flag is turned on, the read-only state will be reset on # reload, but when it is turned off, it won't be. - # (default: false) - #legacy_readonly: true - - # Set the Mongoid and Ruby driver log levels when Mongoid is not using - # Ruby on Rails logger instance. (default: :info) - log_level: :info - - # When using the BigDecimal field type, store the value in the database - # as a BSON::Decimal128 instead of a string. (default: true) - #map_big_decimal_to_decimal128: true + # legacy_readonly: false - # Preload all models in development, needed when models use - # inheritance. (default: false) - preload_models: false + # The log level. + # + # It must be set prior to referencing clients or Mongo.logger, + # changes to this option are not be propagated to any clients and + # loggers that already exist. + # + # Additionally, only when the clients are configured via the + # configuration file is the log level given by this option honored. + # log_level: :info + + # Store BigDecimals as Decimal128s instead of strings in the db. + # map_big_decimal_to_decimal128: true + + # Preload all models in development, needed when models use inheritance. + # preload_models: false + + # When this flag is true, callbacks for every embedded document will be + # called only once, even if the embedded document is embedded in multiple + # documents in the root document's dependencies graph. + # This is the default in 9.0. Setting this flag to false restores the + # pre-9.0 behavior, where callbacks are called for every occurrence of an + # embedded document. The pre-9.0 behavior leads to a problem that for multi + # level nested documents callbacks are called multiple times. + # See https://jira.mongodb.org/browse/MONGOID-5542 + # prevent_multiple_calls_of_embedded_callbacks: true # Raise an error when performing a #find and the document is not found. - # (default: true) - raise_not_found_error: true + # raise_not_found_error: true # Raise an error when defining a scope with the same name as an - # existing method. (default: false) - scope_overwrite_exception: false - - # Return stored times as UTC. See the time zone section below for - # further information. Most applications should not use this option. - # (default: false) - use_utc: false + # existing method. + # scope_overwrite_exception: false - # (Deprecated) In MongoDB 4.0 and earlier, set whether to create - # indexes in the background by default. (default: false) - background_indexing: false + # Return stored times as UTC. + # use_utc: false - # Configure driver-specific options. (optional) + # Configure Driver-specific options. (optional) driver_options: - # When this flag is turned off, inline options will be correctly - # propagated to Mongoid and Driver finder methods. When this flag is turned - # on those options will be ignored. For example, with this flag turned - # off, Band.all.limit(1).count will take the limit into account, while - # when this flag is turned on, that limit is ignored. The affected driver - # methods are: aggregate, count, count_documents, distinct, and - # estimated_document_count. The corresponding Mongoid methods are also - # affected. (default: false, driver version: 2.18.0+) - #broken_view_options: false - - # Validates that there are no atomic operators (those that start with $) - # in the root of a replacement document, and that there are only atomic - # operators at the root of an update document. If this feature flag is on, - # an error will be raised on an invalid update or replacement document, - # if not, a warning will be output to the logs. This flag does not affect - # Mongoid as of 8.0, but will affect calls to driver update/replace - # methods. (default: false, driver version: 2.18.0+) - #validate_update_replace: false + # When this flag is off, an aggregation done on a view will be executed over + # the documents included in that view, instead of all documents in the + # collection. When this flag is on, the view fiter is ignored. + # broken_view_aggregate: true + + # When this flag is set to false, the view options will be correctly + # propagated to readable methods. + # broken_view_options: true + + # When this flag is set to true, the update and replace methods will + # validate the paramters and raise an error if they are invalid. + # validate_update_replace: false .. _load-defaults: @@ -755,9 +794,7 @@ the Ruby driver, which implements the three algorithms that are supported by Mon requires the `zstd-ruby `_ library to be installed. -To use wire protocol compression, at least one compressor must be explicitly requested -using either the `compressors URI option `_, -or directly within the ``mongoid.yml``: +To use wire protocol compression, configure the Ruby driver options within ``mongoid.yml``: .. code-block:: yaml diff --git a/docs/reference/fields.txt b/docs/reference/fields.txt index c3f3f54e2d..6687a6a420 100644 --- a/docs/reference/fields.txt +++ b/docs/reference/fields.txt @@ -1411,10 +1411,12 @@ Mongoid permits dynamic field names to include spaces and punctuation: # => "MDB" +.. _localized-fields: + Localized Fields ================ -Mongoid supports localized fields via `i18n `_. +Mongoid supports localized fields via the `I18n gem `_. .. code-block:: ruby diff --git a/docs/reference/nested-attributes.txt b/docs/reference/nested-attributes.txt index 18c6e54df6..5803206ec8 100644 --- a/docs/reference/nested-attributes.txt +++ b/docs/reference/nested-attributes.txt @@ -49,5 +49,92 @@ Mongoid will call the appropriate setter under the covers. band.producer_attributes = { name: "Flood" } band.attributes = { producer_attributes: { name: "Flood" }} -Note that this will work with any attribute based setter method in Mongoid. This includes: -``update_attributes``, ``update_attributes!`` and ``attributes=``. +Note that this will work with any attribute based setter method in Mongoid, +including ``update``, ``update_attributes`` and ``attributes=``, as well as +``create`` (and all of their corresponding bang methods). For example, creating +a new person with associated address records can be done in a single +statement, like this: + +.. code-block:: ruby + + person = Person.create( + name: 'John Schmidt', + addresses_attributes: [ + { type: 'home', street: '1234 Street Ave.', city: 'Somewhere' }, + { type: 'work', street: 'Parkway Blvd.', city: 'Elsewehre' }, + ]) + + +Creating Records +---------------- + +You can create new nested records via nested attributes by omitting +an ``_id`` field: + +.. code-block:: ruby + + person = Person.first + person.update(addresses_attributes: [ + { type: 'prior', street: '221B Baker St', city: 'London' } ]) + +This will append the new record to the existing set; existing records will +not be changed. + + +Updating Records +---------------- + +If you specify an ``_id`` field for any of the nested records, the attributes +will be used to update the record with that id: + +.. code-block:: ruby + + person = Person.first + address = person.addresses.first + person.update(addresses_attributes: [ + { _id: address._id, city: 'Lisbon' } ]) + +Note that if there is no record with that id, a ``Mongoid::Errors::DocumentNotFound`` +exception will be raised. + + +Destroying Records +------------------ + +You can also destroy records this way, by specifying a special +``_destroy`` attribute. In order to use this, you must have passed +``allow_destroy: true`` with the ``accepts_nested_attributes_for`` +declaration: + +.. code-block:: ruby + + class Person + # ... + + accepts_nested_attributes_for :addresses, allow_destroy: true + end + + person = Person.first + address = person.addresses.first + person.update(addresses_attributes: [ + { _id: address._id, _destroy: true } ]) + +Note that, as with updates, if there is no record with that id, +a ``Mongoid::Errors::DocumentNotFound`` exception will be raised. + + +Combining Operations +-------------------- + +Nested attributes allow you to combine all of these operations in +a single statement! Here's an example that creates an address, +updates another address, and destroys yet another address, all in +a single command: + +.. code-block:: ruby + + person = Person.first + person.update(addresses_attributes: [ + { type: 'alt', street: '1234 Somewhere St.', city: 'Cititon' }, + { _id: an_address_id, city: 'Changed City' }, + { _id: another_id, _destroy: true } ]) diff --git a/docs/reference/queries.txt b/docs/reference/queries.txt index 6f1af85af7..b8dd5c9026 100644 --- a/docs/reference/queries.txt +++ b/docs/reference/queries.txt @@ -1422,8 +1422,7 @@ Mongoid also has some helpful methods on criteria. *Find documents for a provided JavaScript expression, optionally with the specified variables added to the evaluation scope. The scope argument is supported in MongoDB 4.2 and lower.* - *In MongoDB 3.6 and higher, prefer* :manual:`$expr - ` *over* ``for_js``. + *Prefer* :manual:`$expr ` *over* ``for_js``. - .. code-block:: ruby diff --git a/docs/reference/sessions.txt b/docs/reference/sessions.txt index 47eafb62ab..4faf35c078 100644 --- a/docs/reference/sessions.txt +++ b/docs/reference/sessions.txt @@ -12,11 +12,10 @@ Sessions :depth: 2 :class: singlecol -Versions 3.6 and higher of the MongoDB server support sessions. You can use sessions with Mongoid in a similar way -that you would execute a transaction in ActiveRecord. Namely, you can call a method, ``#with_session`` on a model class -or on an instance of a model and execute some operations in a block. All operations in the block will be -executed in the context of single session. Please see the MongoDB Ruby driver documentation for what session options -are available. +You can use sessions with Mongoid in a similar way that you would execute a transaction in ActiveRecord. +Namely, you can call a method, ``#with_session`` on a model class or on an instance of a model and execute +some operations in a block. All operations in the block will be executed in the context of single session. +Please see the MongoDB Ruby driver documentation for what session options are available. Please note the following limitations of sessions: @@ -26,8 +25,6 @@ Please note the following limitations of sessions: - All model classes and instances used within the session block must use the same driver client. For example, if you have specified different ``storage_options`` for another model used in the block than that of the model class or instance on which ``#with_session`` is called, you will get an error. -- All connected MongoDB servers must be version 3.6 or higher. - Using a Session via Model#with_session ====================================== diff --git a/docs/release-notes/mongoid-9.0.txt b/docs/release-notes/mongoid-9.0.txt index 85e496eb81..fa9c74160d 100644 --- a/docs/release-notes/mongoid-9.0.txt +++ b/docs/release-notes/mongoid-9.0.txt @@ -18,6 +18,25 @@ please consult GitHub releases for detailed release notes and JIRA for the complete list of issues fixed in each release, including bug fixes. +Support for Ruby 2.6 and JRuby 9.3 Dropped +------------------------------------------- + +Mongoid 9 requires Ruby 2.7 or newer or JRuby 9.4. Earlier Ruby and JRuby +versions are not supported. + + +Support for Rails 5 Dropped +----------------------------- + +Mongoid 9 requires Rails 6.0 or newer. Earlier Rails versions are not supported. + + +Deprecated class ``Mongoid::Errors::InvalidStorageParent`` removed +------------------------------------------------------------------ + +The deprecated class ``Mongoid::Errors::InvalidStorageParent`` has been removed. + + ``around_*`` callbacks for embedded documents are now ignored ------------------------------------------------------------- @@ -439,8 +458,40 @@ by running the following command: ... code-block:: bash -$ ruby -ractive_support/values/time_zone \ - -e 'puts ActiveSupport::TimeZone::MAPPING.keys' + $ ruby -ractive_support/values/time_zone \ + -e 'puts ActiveSupport::TimeZone::MAPPING.keys' + + +Records now remember the persistence context in which they were loaded/created +------------------------------------------------------------------------------ + +Consider the following code: + +... code-block:: ruby + + record = Model.with(collection: 'other_collection') { Model.first } + record.update(field: 'value') + +Prior to Mongoid 9.0, this could would silently fail to execute the update, +because the storage options (here, the specification of an alternate +collection for the model) would not be remembered by the record. Thus, the +record would be loaded from "other_collection", but when updated, would attempt +to look for and update the document in the default collection for Model. To +make this work, you would have had to specify the collection explicitly for +every update. + +As of Mongoid 9.0, records that are created or loaded under explicit storage +options, will remember those options (including a named client, +a different database, or a different collection). + +If you need the legacy (pre-9.0) behavior, you can enable it with the following +flag: + +... code-block:: ruby + + Mongoid.legacy_persistence_context_behavior = true + +This flag defaults to false in Mongoid 9. Bug Fixes and Improvements diff --git a/docs/tutorials/getting-started-rails6.txt b/docs/tutorials/getting-started-rails6.txt index c071407baf..2a7da41025 100644 --- a/docs/tutorials/getting-started-rails6.txt +++ b/docs/tutorials/getting-started-rails6.txt @@ -44,7 +44,7 @@ In order to do so, the first step is to install the ``rails`` gem: .. code-block:: sh - gem install rails -v '~> 6.0.0' + gem install rails -v '~> 6.0' Create New Application @@ -107,7 +107,7 @@ Add Mongoid .. code-block:: ruby :caption: Gemfile - gem 'mongoid', '~> 7.0.5' + gem 'mongoid' .. note:: @@ -125,7 +125,7 @@ Add Mongoid bin/rails g mongoid:config -This generator will create the ``config/mongoid.yml`` configuration file +This generator will create the ``config/mongoid.yml`` configuration file (used to configure the connection to the MongoDB deployment) and the ``config/initializers/mongoid.rb`` initializer file (which may be used for other Mongoid-related configuration). Note that as we are not using @@ -378,7 +378,7 @@ mentioned in ``Gemfile``, and add ``mongoid``: .. code-block:: ruby :caption: Gemfile - gem 'mongoid', '~> 7.0.5' + gem 'mongoid' .. note:: @@ -466,7 +466,7 @@ Generate the default Mongoid configuration: bin/rails g mongoid:config -This generator will create the ``config/mongoid.yml`` configuration file +This generator will create the ``config/mongoid.yml`` configuration file (used to configure the connection to the MongoDB deployment) and the ``config/initializers/mongoid.rb`` initializer file (which may be used for other Mongoid-related configuration). In general, it is recommended to use diff --git a/gemfiles/bson_min.gemfile b/gemfiles/bson_min.gemfile index a17fb792ef..d68b7f839c 100644 --- a/gemfiles/bson_min.gemfile +++ b/gemfiles/bson_min.gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" gemspec path: '..' -gem 'bson', '4.14.0' +gem 'bson', '4.14.1' gem 'mongo' gem 'actionpack' diff --git a/gemfiles/i18n-1.0.gemfile b/gemfiles/i18n-1.0.gemfile deleted file mode 100644 index d41c5ef6d8..0000000000 --- a/gemfiles/i18n-1.0.gemfile +++ /dev/null @@ -1,12 +0,0 @@ -# rubocop:todo all -source "https://rubygems.org" - -gem 'actionpack' -# https://jira.mongodb.org/browse/MONGOID-4614 -gem 'i18n', '~> 1.0.0' - -gemspec path: '..' - -require_relative './standard' - -standard_dependencies diff --git a/gemfiles/rails-5.2.gemfile b/gemfiles/rails-7.1.gemfile similarity index 69% rename from gemfiles/rails-5.2.gemfile rename to gemfiles/rails-7.1.gemfile index b3d7c2f237..3b6debf9ec 100644 --- a/gemfiles/rails-5.2.gemfile +++ b/gemfiles/rails-7.1.gemfile @@ -1,8 +1,8 @@ # rubocop:todo all source 'https://rubygems.org' -gem 'actionpack', '~> 5.2' -gem 'activemodel', '~> 5.2' +gem 'actionpack', '~> 7.1' +gem 'activemodel', '~> 7.1' gemspec path: '..' diff --git a/lib/mongoid/association/embedded/embeds_many/proxy.rb b/lib/mongoid/association/embedded/embeds_many/proxy.rb index f7e5d810a1..a2d14a035e 100644 --- a/lib/mongoid/association/embedded/embeds_many/proxy.rb +++ b/lib/mongoid/association/embedded/embeds_many/proxy.rb @@ -337,8 +337,8 @@ def exists?(id_or_conditions = :none) # @yield [ Object ] Yields each enumerable element to the block. # # @return [ Document | Array | nil ] A document or matching documents. - def find(*args, &block) - criteria.find(*args, &block) + def find(...) + criteria.find(...) end # Get all the documents in the association that are loaded into memory. diff --git a/lib/mongoid/association/referenced/auto_save.rb b/lib/mongoid/association/referenced/auto_save.rb index da2f2fe708..43a55118ea 100644 --- a/lib/mongoid/association/referenced/auto_save.rb +++ b/lib/mongoid/association/referenced/auto_save.rb @@ -60,7 +60,7 @@ def self.define_autosave!(association) __autosaving__ do if assoc_value = ivar(association.name) Array(assoc_value).each do |doc| - pc = doc.persistence_context? ? doc.persistence_context : persistence_context + pc = doc.persistence_context? ? doc.persistence_context : persistence_context.for_child(doc) doc.with(pc) do |d| d.save end diff --git a/lib/mongoid/association/referenced/counter_cache.rb b/lib/mongoid/association/referenced/counter_cache.rb index 5f2f46f929..0f2588de6c 100644 --- a/lib/mongoid/association/referenced/counter_cache.rb +++ b/lib/mongoid/association/referenced/counter_cache.rb @@ -108,7 +108,7 @@ def self.define_callbacks!(association) original, current = send("#{foreign_key}_previous_change") unless original.nil? - association.klass.with(persistence_context) do |_class| + association.klass.with(persistence_context.for_child(association.klass)) do |_class| _class.decrement_counter(cache_column, original) end end diff --git a/lib/mongoid/clients/options.rb b/lib/mongoid/clients/options.rb index cca748c1b3..8d0d9582aa 100644 --- a/lib/mongoid/clients/options.rb +++ b/lib/mongoid/clients/options.rb @@ -78,15 +78,15 @@ def mongo_client # @example Get the current persistence context. # document.persistence_context # - # @return [ Mongoid::PersistenceContent ] The current persistence + # @return [ Mongoid::PersistenceContext ] The current persistence # context. def persistence_context if embedded? && !_root? _root.persistence_context else PersistenceContext.get(self) || - PersistenceContext.get(self.class) || - PersistenceContext.new(self.class) + PersistenceContext.get(self.class) || + PersistenceContext.new(self.class, storage_options) end end @@ -104,7 +104,9 @@ def persistence_context? if embedded? && !_root? _root.persistence_context? else - !!(PersistenceContext.get(self) || PersistenceContext.get(self.class)) + remembered_storage_options&.any? || + PersistenceContext.get(self).present? || + PersistenceContext.get(self.class).present? end end diff --git a/lib/mongoid/clients/sessions.rb b/lib/mongoid/clients/sessions.rb index 29c05cad3a..b859069789 100644 --- a/lib/mongoid/clients/sessions.rb +++ b/lib/mongoid/clients/sessions.rb @@ -16,6 +16,10 @@ def self.included(base) module ClassMethods + # Actions that can be used to trigger transactional callbacks. + # @api private + CALLBACK_ACTIONS = [:create, :destroy, :update] + # Execute a block within the context of a session. # # @example Execute some operations in the context of a session. @@ -101,6 +105,49 @@ def transaction(options = {}, session_options: {}) end end + # Sets up a callback is called after a commit of a transaction. + # The callback is called only if the document is created, updated, or destroyed + # in the transaction. + # + # See +ActiveSupport::Callbacks::ClassMethods::set_callback+ for more + # information about method parameters and possible options. + def after_commit(*args, &block) + set_options_for_callbacks!(args) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for +after_commit :hook, on: [ :create, :update ]+ + def after_save_commit(*args, &block) + set_options_for_callbacks!(args, on: [ :create, :update ]) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for +after_commit :hook, on: :create+. + def after_create_commit(*args, &block) + set_options_for_callbacks!(args, on: :create) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for +after_commit :hook, on: :update+. + def after_update_commit(*args, &block) + set_options_for_callbacks!(args, on: :update) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for +after_commit :hook, on: :destroy+. + def after_destroy_commit(*args, &block) + set_options_for_callbacks!(args, on: :destroy) + set_callback(:commit, :after, *args, &block) + end + + # This callback is called after a create, update, or destroy are rolled back. + # + # Please check the documentation of +after_commit+ for options. + def after_rollback(*args, &block) + set_options_for_callbacks!(args) + set_callback(:rollback, :after, *args, &block) + end + private # @return [ Mongo::Session ] Session for the current client. @@ -145,6 +192,46 @@ def abort_transaction(session) doc.run_after_callbacks(:rollback) end end + + # Transforms custom options for after_commit and after_rollback callbacks + # into options for +set_callback+. + def set_options_for_callbacks!(args) + options = args.extract_options! + args << options + + if options[:on] + fire_on = Array(options[:on]) + assert_valid_transaction_action(fire_on) + options[:if] = [ + -> { transaction_include_any_action?(fire_on) }, + *options[:if] + ] + end + end + + # Asserts that the given actions are valid for after_commit + # and after_rollback callbacks. + # + # @param [ Array ] actions Actions to be checked. + # @raise [ ArgumentError ] If any of the actions is not valid. + def assert_valid_transaction_action(actions) + if (actions - CALLBACK_ACTIONS).any? + raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{CALLBACK_ACTIONS}" + end + end + + def transaction_include_any_action?(actions) + actions.any? do |action| + case action + when :create + persisted? && previously_new_record? + when :update + !(previously_new_record? || destroyed?) + when :destroy + destroyed? + end + end + end end end end diff --git a/lib/mongoid/clients/storage_options.rb b/lib/mongoid/clients/storage_options.rb index b65377ce62..7bd857ccf1 100644 --- a/lib/mongoid/clients/storage_options.rb +++ b/lib/mongoid/clients/storage_options.rb @@ -11,7 +11,38 @@ module StorageOptions extend ActiveSupport::Concern included do - class_attribute :storage_options, instance_writer: false, default: storage_options_defaults + class_attribute :storage_options, instance_accessor: false, default: storage_options_defaults + end + + # Remembers the storage options that were active when the current object + # was instantiated/created. + # + # @return [ Hash | nil ] the storage options that have been cached for + # this object instance (or nil if no storage options have been + # cached). + # + # @api private + attr_accessor :remembered_storage_options + + # The storage options that apply to this record, consisting of both + # the class-level declared storage options (e.g. store_in) merged with + # any remembered storage options. + # + # @return [ Hash ] the storage options for the record + # + # @api private + def storage_options + self.class.storage_options.merge(remembered_storage_options || {}) + end + + # Saves the storage options from the current persistence context. + # + # @api private + def remember_storage_options! + return if Mongoid.legacy_persistence_context_behavior + + opts = persistence_context.requested_storage_options + self.remembered_storage_options = opts if opts end module ClassMethods diff --git a/lib/mongoid/config.rb b/lib/mongoid/config.rb index 52eb3a7e9d..352f36bc12 100644 --- a/lib/mongoid/config.rb +++ b/lib/mongoid/config.rb @@ -23,10 +23,10 @@ module Config LOCK = Mutex.new - # Application name that is printed to the mongodb logs upon establishing - # a connection in server versions >= 3.4. Note that the name cannot - # exceed 128 bytes. It is also used as the database name if the - # database name is not explicitly defined. + # Application name that is printed to the MongoDB logs upon establishing + # a connection. Note that the name cannot exceed 128 bytes in length. + # It is also used as the database name if the database name is not + # explicitly defined. option :app_name, default: nil # (Deprecated) In MongoDB 4.0 and earlier, set whether to create @@ -120,6 +120,30 @@ module Config # reload, but when it is turned off, it won't be. option :legacy_readonly, default: false + # When this flag is false (the default as of Mongoid 9.0), a document that + # is created or loaded will remember the storage options that were active + # when it was loaded, and will use those same options by default when + # saving or reloading itself. + # + # When this flag is true you'll get pre-9.0 behavior, where a document will + # not remember the storage options from when it was loaded/created, and + # subsequent updates will need to explicitly set up those options each time. + # + # For example: + # + # record = Model.with(collection: 'other_collection') { Model.first } + # + # This will try to load the first document from 'other_collection' and + # instantiate it as a Model instance. Pre-9.0, the record object would + # not remember that it came from 'other_collection', and attempts to + # update it or reload it would fail unless you first remembered to + # explicitly specify the collection every time. + # + # As of Mongoid 9.0, the record will remember that it came from + # 'other_collection', and updates and reloads will automatically default + # to that collection, for that record object. + option :legacy_persistence_context_behavior, default: false + # When this flag is true, any attempt to change the _id of a persisted # document will raise an exception (`Errors::ImmutableAttribute`). # This is the default in 9.0. Setting this flag to false restores the diff --git a/lib/mongoid/config/defaults.rb b/lib/mongoid/config/defaults.rb index 11d3e2b28e..b1981646be 100644 --- a/lib/mongoid/config/defaults.rb +++ b/lib/mongoid/config/defaults.rb @@ -15,17 +15,8 @@ module Defaults # # raises [ ArgumentError ] if an invalid version is given. def load_defaults(version) - # Note that for 7.x, since all of the feature flag defaults have been - # flipped to the new functionality, all of the settings for those - # versions are to give old functionality. Because of this, it is - # possible to recurse to later version to get all of the options to - # turn off. Note that this won't be true when adding feature flags to - # 9.x, since the default will be the old functionality until the next - # major version is released. More likely, the recursion will have to go - # in the other direction (towards earlier versions). - case version.to_s - when "7.3", "7.4", "7.5" + when /^[0-7]\./ raise ArgumentError, "Version no longer supported: #{version}" when "8.0" self.legacy_readonly = true @@ -33,6 +24,7 @@ def load_defaults(version) load_defaults "8.1" when "8.1" self.immutable_ids = false + self.legacy_persistence_context_behavior = true load_defaults "9.0" when "9.0" diff --git a/lib/mongoid/config/environment.rb b/lib/mongoid/config/environment.rb index 430087da7a..a04ce5f522 100644 --- a/lib/mongoid/config/environment.rb +++ b/lib/mongoid/config/environment.rb @@ -66,11 +66,7 @@ def load_yaml(path, environment = nil) ] result = ERB.new(contents).result - data = if RUBY_VERSION < '2.6' - YAML.safe_load(result, permitted_classes, [], true) - else - YAML.safe_load(result, permitted_classes: permitted_classes, aliases: true) - end + data = YAML.safe_load(result, permitted_classes: permitted_classes, aliases: true) unless data.is_a?(Hash) raise Mongoid::Errors::InvalidConfigFile.new(path) diff --git a/lib/mongoid/config/options.rb b/lib/mongoid/config/options.rb index f6a12baa47..abc85f8bf1 100644 --- a/lib/mongoid/config/options.rb +++ b/lib/mongoid/config/options.rb @@ -57,7 +57,11 @@ def option(name, options = {}) # # @return [ Hash ] The defaults. def reset - settings.replace(defaults) + # do this via the setter for each option, so that any defined on_change + # handlers can be invoked. + defaults.each do |setting, default| + send(:"#{setting}=", default) + end end # Get the settings or initialize a new empty hash. @@ -79,8 +83,8 @@ def settings def log_level if level = settings[:log_level] unless level.is_a?(Integer) - level = level.upcase.to_s - level = "Logger::#{level}".constantize + # JRuby String#constantize does not work here. + level = Logger.const_get(level.upcase.to_s) end level end diff --git a/lib/mongoid/document.rb b/lib/mongoid/document.rb index b619ace2b8..e55d932c01 100644 --- a/lib/mongoid/document.rb +++ b/lib/mongoid/document.rb @@ -443,6 +443,7 @@ def instantiate_document(attrs = nil, selected_fields = nil, options = {}, &bloc doc._handle_callbacks_after_instantiation(execute_callbacks, &block) + doc.remember_storage_options! doc end diff --git a/lib/mongoid/errors/invalid_storage_parent.rb b/lib/mongoid/errors/invalid_storage_parent.rb deleted file mode 100644 index a81c7772b5..0000000000 --- a/lib/mongoid/errors/invalid_storage_parent.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true -# rubocop:todo all - -module Mongoid - module Errors - - # Raised when calling store_in in a sub-class of Mongoid::Document - # - # @deprecated - class InvalidStorageParent < MongoidError - - # Create the new error. - # - # @example Create the new error. - # InvalidStorageParent.new(Person) - # - # @param [ Class ] klass The model class. - def initialize(klass) - super( - compose_message( - "invalid_storage_parent", - { klass: klass } - ) - ) - end - end - end -end diff --git a/lib/mongoid/extensions/range.rb b/lib/mongoid/extensions/range.rb index e5dc8e89c3..190bd8aa81 100644 --- a/lib/mongoid/extensions/range.rb +++ b/lib/mongoid/extensions/range.rb @@ -51,8 +51,6 @@ module ClassMethods # @param [ Hash ] object The object to demongoize. # # @return [ Range | nil ] The range, or nil if object cannot be represented as range. - # - # @note Ruby 2.6 and lower do not support endless ranges that Ruby 2.7+ support. def demongoize(object) return if object.nil? if object.is_a?(Hash) @@ -62,7 +60,7 @@ def demongoize(object) ::Range.new(hash["min"] || hash[:min], hash["max"] || hash[:max], hash["exclude_end"] || hash[:exclude_end]) - rescue ArgumentError # can be removed when Ruby version >= 2.7 + rescue ArgumentError nil end end diff --git a/lib/mongoid/interceptable.rb b/lib/mongoid/interceptable.rb index 9b5f205f88..c9f3bf7b30 100644 --- a/lib/mongoid/interceptable.rb +++ b/lib/mongoid/interceptable.rb @@ -44,7 +44,9 @@ module Interceptable # @api private define_model_callbacks :persist_parent - define_model_callbacks :commit, :rollback, only: :after + define_callbacks :commit, :rollback, + only: :after, + scope: [:kind, :name] attr_accessor :before_callback_halted end diff --git a/lib/mongoid/persistable/creatable.rb b/lib/mongoid/persistable/creatable.rb index 50147b5564..bf4f89f80a 100644 --- a/lib/mongoid/persistable/creatable.rb +++ b/lib/mongoid/persistable/creatable.rb @@ -84,6 +84,7 @@ def insert_as_root # @return [ true ] true. def post_process_insert self.new_record = false + remember_storage_options! flag_descendants_persisted true end diff --git a/lib/mongoid/persistable/updatable.rb b/lib/mongoid/persistable/updatable.rb index 9dc5aeeaf7..f31ae88cd4 100644 --- a/lib/mongoid/persistable/updatable.rb +++ b/lib/mongoid/persistable/updatable.rb @@ -129,16 +129,15 @@ def update_document(options = {}) unless updates.empty? coll = collection(_root) selector = atomic_selector + + # TODO: DRIVERS-716: If a new "Bulk Write" API is introduced, it may + # become possible to handle the writes for conflicts in the following call. coll.find(selector).update_one(positionally(selector, updates), session: _session) # The following code applies updates which would cause # path conflicts in MongoDB, for example when changing attributes # of foo.0.bars while adding another foo. Each conflicting update # is applied using its own write. - # - # TODO: MONGOID-5026: reduce the number of writes performed by - # more intelligently combining the writes such that there are - # fewer conflicts. conflicts.each_pair do |modifier, changes| # Group the changes according to their root key which is diff --git a/lib/mongoid/persistence_context.rb b/lib/mongoid/persistence_context.rb index 1b1c285776..ad0a7969a0 100644 --- a/lib/mongoid/persistence_context.rb +++ b/lib/mongoid/persistence_context.rb @@ -11,7 +11,7 @@ class PersistenceContext # Delegate the cluster method to the client. def_delegators :client, :cluster - # Delegate the storage options method to the object. + # Delegate the storage_options method to the object. def_delegators :@object, :storage_options # The options defining this persistence context. @@ -47,6 +47,26 @@ def initialize(object, opts = {}) set_options!(opts) end + # Returns a new persistence context that is consistent with the given + # child document, inheriting most appropriate settings. + # + # @param [ Mongoid::Document | Class ] document the child document + # + # @return [ PersistenceContext ] the new persistence context + # + # @api private + def for_child(document) + if document.is_a?(Class) + return self if document == (@object.is_a?(Class) ? @object : @object.class) + elsif document.is_a?(Mongoid::Document) + return self if document.class == (@object.is_a?(Class) ? @object : @object.class) + else + raise ArgumentError, 'must specify a class or a document instance' + end + + PersistenceContext.new(document, options.merge(document.storage_options)) + end + # Get the collection for this persistence context. # # @example Get the collection for this persistence context. @@ -116,7 +136,7 @@ def client def client_name @client_name ||= options[:client] || Threaded.client_override || - storage_options && __evaluate__(storage_options[:client]) + __evaluate__(storage_options[:client]) end # Determine if this persistence context is equal to another. @@ -147,6 +167,18 @@ def reusable_client? @options.keys == [:client] end + # The subset of provided options that may be used as storage + # options. + # + # @return [ Hash | nil ] the requested storage options, or nil if + # none were specified. + # + # @api private + def requested_storage_options + slice = @options.slice(*Mongoid::Clients::Validators::Storage::VALID_OPTIONS) + slice.any? ? slice : nil + end + private def set_options!(opts) @@ -178,7 +210,7 @@ def client_options def database_name_option @database_name_option ||= options[:database] || Threaded.database_override || - storage_options && storage_options[:database] + storage_options[:database] end class << self diff --git a/lib/mongoid/scopable.rb b/lib/mongoid/scopable.rb index 08cb8ecba1..35635acdc3 100644 --- a/lib/mongoid/scopable.rb +++ b/lib/mongoid/scopable.rb @@ -290,9 +290,9 @@ def check_scope_validity(value) def define_scope_method(name) singleton_class.class_eval do ruby2_keywords( - define_method(name) do |*args| + define_method(name) do |*args, **kwargs| scoping = _declared_scopes[name] - scope = instance_exec(*args, &scoping[:scope]) + scope = instance_exec(*args, **kwargs, &scoping[:scope]) extension = scoping[:extension] to_merge = scope || queryable criteria = to_merge.empty_and_chainable? ? to_merge : with_default_scope.merge(to_merge) diff --git a/lib/rails/generators/mongoid/config/templates/mongoid.yml b/lib/rails/generators/mongoid/config/templates/mongoid.yml index 055d6cb255..8cbb62d58c 100644 --- a/lib/rails/generators/mongoid/config/templates/mongoid.yml +++ b/lib/rails/generators/mongoid/config/templates/mongoid.yml @@ -18,7 +18,7 @@ development: # Note that all options listed below are Ruby driver client options (the mongo gem). # Please refer to the driver documentation of the version of the mongo gem you are using # for the most up-to-date list of options. - # + # Change the default write concern. (default = { w: 1 }) # write: # w: 1 @@ -41,18 +41,20 @@ development: # roles: # - 'dbOwner' - # Change the default authentication mechanism. Valid options are: :scram, - # :mongodb_cr, :mongodb_x509, and :plain. Note that all authentication - # mechanisms require username and password, with the exception of :mongodb_x509. - # Default on mongoDB 3.0 is :scram, default on 2.4 and 2.6 is :plain. + # Change the default authentication mechanism. Valid options include: + # :scram, :scram256, :mongodb_cr, :mongodb_x509, :gssapi, :aws, :plain. + # MongoDB Server defaults to :scram, which will use "SCRAM-SHA-256" if available, + # otherwise fallback to "SCRAM-SHA-1" (:scram256 will always use "SCRAM-SHA-256".) + # This setting is handled by the MongoDB Ruby Driver. Please refer to: + # https://mongodb.com/docs/ruby-driver/current/reference/authentication/ # auth_mech: :scram # The database or source to authenticate the user against. # (default: the database specified above or admin) # auth_source: admin - # Force a the driver cluster to behave in a certain manner instead of auto- - # discovering. Can be one of: :direct, :replica_set, :sharded. Set to :direct + # Force the driver cluster to behave in a certain manner instead of auto-discovering. + # Can be one of: :direct, :replica_set, :sharded. Set to :direct # when connecting to hidden members of a replica set. # connect: :direct @@ -93,6 +95,11 @@ development: # not belong to this replica set will be ignored. # replica_set: name + # Compressors to use for wire protocol compression. (default is to not use compression) + # "zstd" requires zstd-ruby gem. "snappy" requires snappy gem. + # Refer to: https://www.mongodb.com/docs/ruby-driver/current/reference/create-client/#compression + # compressors: ["zstd", "snappy", "zlib"] + # Whether to connect to the servers via ssl. (default: false) # ssl: true @@ -113,17 +120,32 @@ development: # The file containing concatenated certificate authority certificates # used to validate certs passed from the other end of the connection. # ssl_ca_cert: /path/to/ca.cert - + # Whether to truncate long log lines. (default: true) # truncate_logs: true - # Configure Mongoid specific options. (optional) + # Configure Mongoid-specific options. (optional) options: <%- Mongoid::Config::Introspection.options.each do |opt| -%> <%= opt.indented_comment(indent: 4) %> # <%= opt.name %>: <%= opt.default %> <%- end -%> + # Configure Driver-specific options. (optional) + driver_options: + # When this flag is off, an aggregation done on a view will be executed over + # the documents included in that view, instead of all documents in the + # collection. When this flag is on, the view fiter is ignored. + # broken_view_aggregate: true + + # When this flag is set to false, the view options will be correctly + # propagated to readable methods. + # broken_view_options: true + + # When this flag is set to true, the update and replace methods will + # validate the paramters and raise an error if they are invalid. + # validate_update_replace: false + test: clients: diff --git a/mongoid.gemspec b/mongoid.gemspec index 0a12d74ecb..d6266ea5c4 100644 --- a/mongoid.gemspec +++ b/mongoid.gemspec @@ -32,7 +32,7 @@ Gem::Specification.new do |s| warn "[#{s.name}] Warning: No private key present, creating unsigned gem." end - s.required_ruby_version = ">= 2.6" + s.required_ruby_version = ">= 2.7" s.required_rubygems_version = ">= 1.3.6" # Ruby 3.0 requires ActiveModel 6.0 or higher. diff --git a/spec/integration/app_spec.rb b/spec/integration/app_spec.rb index 1ebdfcbc58..ca67b44cd7 100644 --- a/spec/integration/app_spec.rb +++ b/spec/integration/app_spec.rb @@ -305,7 +305,7 @@ def adjust_app_gemfile(rails_version: SpecConfig.instance.rails_version) gemfile_lines << "gem 'mongoid', path: '#{File.expand_path(BASE)}'\n" if rails_version gemfile_lines.delete_if do |line| - line =~ /rails/ + line =~ /gem ['"]rails['"]/ end if rails_version == 'master' gemfile_lines << "gem 'rails', git: 'https://github.com/rails/rails'\n" diff --git a/spec/integration/callbacks_spec.rb b/spec/integration/callbacks_spec.rb index c580258d4b..f81bd89b84 100644 --- a/spec/integration/callbacks_spec.rb +++ b/spec/integration/callbacks_spec.rb @@ -586,6 +586,7 @@ def will_save_change_to_attribute_values_before context 'cascade callbacks' do ruby_version_gte '3.0' + require_mri let(:book) do Book.new diff --git a/spec/integration/i18n_fallbacks_spec.rb b/spec/integration/i18n_fallbacks_spec.rb index 1dc361d6c8..7b4e247a5e 100644 --- a/spec/integration/i18n_fallbacks_spec.rb +++ b/spec/integration/i18n_fallbacks_spec.rb @@ -36,39 +36,12 @@ end context 'when translation is missing in all locales' do - - context 'i18n >= 1.1' do - - before(:all) do - unless Gem::Version.new(I18n::VERSION) >= Gem::Version.new('1.1') - skip "Test requires i18n >= 1.1, we have #{I18n::VERSION}" - end - end - - it 'returns nil' do - product = Product.new - I18n.locale = :en - product.description = "Marvelous!" - I18n.locale = :ru - product.description.should be nil - end - end - - context 'i18n 1.0' do - - before(:all) do - unless Gem::Version.new(I18n::VERSION) < Gem::Version.new('1.1') - skip "Test requires i18n < 1.1, we have #{I18n::VERSION}" - end - end - - it 'falls back on default locale' do - product = Product.new - I18n.locale = :en - product.description = "Marvelous!" - I18n.locale = :ru - product.description.should == 'Marvelous!' - end + it 'returns nil' do + product = Product.new + I18n.locale = :en + product.description = "Marvelous!" + I18n.locale = :ru + product.description.should be nil end end end diff --git a/spec/integration/matcher_operator_data/bits_all_clear.yml b/spec/integration/matcher_operator_data/bits_all_clear.yml index efaa3d5dff..cd6a9afd68 100644 --- a/spec/integration/matcher_operator_data/bits_all_clear.yml +++ b/spec/integration/matcher_operator_data/bits_all_clear.yml @@ -6,7 +6,6 @@ a: $bitsAllClear: 35 matches: true - min_server_version: 3.2 - name: existing field - does not match with int document: @@ -16,7 +15,6 @@ a: $bitsAllClear: 24 matches: false - min_server_version: 3.2 - name: existing field - matches with binData document: @@ -29,7 +27,6 @@ IW== type: :generic matches: true - min_server_version: 3.2 - name: existing field - does not match with binData document: @@ -42,7 +39,6 @@ IW== type: :generic matches: false - min_server_version: 3.2 - name: existing binData field matches document: @@ -54,7 +50,6 @@ a: $bitsAllClear: 20 matches: true - min_server_version: 3.2 - name: existing binData field does not match document: @@ -66,7 +61,6 @@ a: $bitsAllClear: 15 matches: false - min_server_version: 3.2 - name: existing field - matches with array document: @@ -76,7 +70,6 @@ a: $bitsAllClear: [0, 3] matches: true - min_server_version: 3.2 - name: existing field - does not match with array document: @@ -86,7 +79,6 @@ a: $bitsAllClear: [0, 2] matches: false - min_server_version: 3.2 - name: float condition representable as an integer document: @@ -96,7 +88,6 @@ a: $bitsAllClear: 35.0 matches: true - min_server_version: 3.2 - name: float condition not representable as an integer document: @@ -106,7 +97,6 @@ a: $bitsAllClear: 35.1 error: true - min_server_version: 3.2 - name: string condition document: @@ -116,7 +106,6 @@ a: $bitsAllClear: hello error: true - min_server_version: 3.2 - name: empty array condition document: @@ -126,7 +115,6 @@ a: $bitsAllClear: [] matches: true - min_server_version: 3.2 - name: integer 0 condition document: @@ -136,7 +124,6 @@ a: $bitsAllClear: 0 matches: true - min_server_version: 3.2 - name: negative integer condition document: @@ -146,7 +133,6 @@ a: $bitsAllClear: -1 error: true - min_server_version: 3.2 - name: negative float condition document: @@ -156,4 +142,3 @@ a: $bitsAllClear: -1.0 error: true - min_server_version: 3.2 diff --git a/spec/integration/matcher_operator_data/bits_all_set.yml b/spec/integration/matcher_operator_data/bits_all_set.yml index 87d60305a6..3b0df8a797 100644 --- a/spec/integration/matcher_operator_data/bits_all_set.yml +++ b/spec/integration/matcher_operator_data/bits_all_set.yml @@ -6,7 +6,6 @@ a: $bitsAllSet: 50 matches: true - min_server_version: 3.2 - name: existing field - does not match with int document: @@ -16,7 +15,6 @@ a: $bitsAllSet: 50 matches: false - min_server_version: 3.2 - name: existing field - matches with binData document: @@ -29,7 +27,6 @@ Ng== type: :generic matches: true - min_server_version: 3.2 - name: existing field - does not match with binData document: @@ -42,7 +39,6 @@ MC== type: :generic matches: false - min_server_version: 3.2 - name: existing binData field matches document: @@ -54,7 +50,6 @@ a: $bitsAllSet: 48 matches: true - min_server_version: 3.2 - name: existing binData field does not match document: @@ -66,7 +61,6 @@ a: $bitsAllSet: 54 matches: false - min_server_version: 3.2 - name: existing field - matches with array document: @@ -76,7 +70,6 @@ a: $bitsAllSet: [2, 4] matches: true - min_server_version: 3.2 - name: existing field - does not match with array document: @@ -86,7 +79,6 @@ a: $bitsAllSet: [0, 3] matches: false - min_server_version: 3.2 - name: float condition representable as an integer document: @@ -96,7 +88,6 @@ a: $bitsAllSet: 50.0 matches: true - min_server_version: 3.2 - name: float condition not representable as an integer document: @@ -106,7 +97,6 @@ a: $bitsAllSet: 50.1 error: true - min_server_version: 3.2 - name: string condition document: @@ -116,7 +106,6 @@ a: $bitsAllSet: hello error: true - min_server_version: 3.2 - name: empty array condition document: @@ -126,7 +115,6 @@ a: $bitsAllSet: [] matches: true - min_server_version: 3.2 - name: integer 0 condition document: @@ -136,7 +124,6 @@ a: $bitsAllSet: 0 matches: true - min_server_version: 3.2 - name: negative integer condition document: @@ -146,7 +133,6 @@ a: $bitsAllSet: -1 error: true - min_server_version: 3.2 - name: negative float condition document: @@ -156,4 +142,3 @@ a: $bitsAllSet: -1.0 error: true - min_server_version: 3.2 diff --git a/spec/integration/matcher_operator_data/bits_any_clear.yml b/spec/integration/matcher_operator_data/bits_any_clear.yml index e3b392fdb5..ee16d3faec 100644 --- a/spec/integration/matcher_operator_data/bits_any_clear.yml +++ b/spec/integration/matcher_operator_data/bits_any_clear.yml @@ -6,7 +6,6 @@ a: $bitsAnyClear: 35 matches: true - min_server_version: 3.2 - name: existing field - does not match with int document: @@ -16,7 +15,6 @@ a: $bitsAnyClear: 35 matches: true - min_server_version: 3.2 - name: existing field - matches with binData document: @@ -29,7 +27,6 @@ IW== type: :generic matches: true - min_server_version: 3.2 - name: existing field - does not match with binData document: @@ -42,7 +39,6 @@ IW== type: :generic matches: false - min_server_version: 3.2 - name: existing binData field matches document: @@ -54,7 +50,6 @@ a: $bitsAnyClear: 35 matches: true - min_server_version: 3.2 - name: existing binData field does not match document: @@ -66,7 +61,6 @@ a: $bitsAnyClear: 32 matches: false - min_server_version: 3.2 - name: existing field - matches with array document: @@ -76,7 +70,6 @@ a: $bitsAnyClear: [0, 2] matches: true - min_server_version: 3.2 - name: existing field - does not match with array document: @@ -86,7 +79,6 @@ a: $bitsAnyClear: [2, 4] matches: false - min_server_version: 3.2 - name: float condition representable as an integer document: @@ -96,7 +88,6 @@ a: $bitsAnyClear: 35.0 matches: true - min_server_version: 3.2 - name: float condition not representable as an integer document: @@ -106,7 +97,6 @@ a: $bitsAnyClear: 35.1 error: true - min_server_version: 3.2 - name: string condition document: @@ -116,7 +106,6 @@ a: $bitsAnyClear: 'hello' error: true - min_server_version: 3.2 - name: empty array condition document: @@ -126,7 +115,6 @@ a: $bitsAnyClear: [] matches: false - min_server_version: 3.2 - name: integer 0 condition document: @@ -136,7 +124,6 @@ a: $bitsAnyClear: 0 matches: false - min_server_version: 3.2 - name: negative integer condition document: @@ -146,7 +133,6 @@ a: $bitsAnyClear: -1 error: true - min_server_version: 3.2 - name: negative float condition document: @@ -156,4 +142,3 @@ a: $bitsAnyClear: -1.0 error: true - min_server_version: 3.2 diff --git a/spec/integration/matcher_operator_data/bits_any_set.yml b/spec/integration/matcher_operator_data/bits_any_set.yml index f95d328167..3f41e33dee 100644 --- a/spec/integration/matcher_operator_data/bits_any_set.yml +++ b/spec/integration/matcher_operator_data/bits_any_set.yml @@ -6,7 +6,6 @@ a: $bitsAnySet: 35 matches: true - min_server_version: 3.2 - name: existing field - does not match with int document: @@ -16,7 +15,6 @@ a: $bitsAllSet: 35 matches: false - min_server_version: 3.2 - name: existing field - matches with binData document: @@ -29,7 +27,6 @@ IW== type: :generic matches: true - min_server_version: 3.2 - name: existing field - does not match with binData document: @@ -42,7 +39,6 @@ IW== type: :generic matches: false - min_server_version: 3.2 - name: existing binData field matches document: @@ -54,7 +50,6 @@ a: $bitsAnySet: 37 matches: true - min_server_version: 3.2 - name: existing binData field does not match document: @@ -66,7 +61,6 @@ a: $bitsAnySet: 20 matches: false - min_server_version: 3.2 - name: existing field - matches with array document: @@ -76,7 +70,6 @@ a: $bitsAnySet: [0, 2] matches: true - min_server_version: 3.2 - name: existing field - does not match with array document: @@ -86,7 +79,6 @@ a: $bitsAnySet: [0, 3] matches: false - min_server_version: 3.2 - name: float condition representable as an integer document: @@ -96,7 +88,6 @@ a: $bitsAnySet: 35 matches: true - min_server_version: 3.2 - name: float condition not representable as an integer document: @@ -106,7 +97,6 @@ a: $bitsAnySet: 35.1 error: true - min_server_version: 3.2 - name: string condition document: @@ -116,7 +106,6 @@ a: $bitsAnySet: hello error: true - min_server_version: 3.2 - name: empty array condition document: @@ -126,7 +115,6 @@ a: $bitsAnySet: [] matches: false - min_server_version: 3.2 - name: integer 0 condition document: @@ -136,7 +124,6 @@ a: $bitsAnySet: 0 matches: false - min_server_version: 3.2 - name: negative integer condition document: @@ -146,7 +133,6 @@ a: $bitsAnySet: -1 error: true - min_server_version: 3.2 - name: negative float condition document: @@ -156,4 +142,3 @@ a: $bitsAnySet: -1.0 error: true - min_server_version: 3.2 diff --git a/spec/integration/matcher_operator_data/size.yml b/spec/integration/matcher_operator_data/size.yml index 43a950bacc..362dcd4657 100644 --- a/spec/integration/matcher_operator_data/size.yml +++ b/spec/integration/matcher_operator_data/size.yml @@ -86,7 +86,6 @@ position: $size: foo error: true - min_server_version: 3.4 - name: floating point argument document: @@ -95,7 +94,6 @@ position: $size: 2.5 error: true - min_server_version: 3.4 - name: negative integer argument document: @@ -104,7 +102,6 @@ position: $size: -2 error: true - min_server_version: 3.4 - name: empty array field value document: diff --git a/spec/integration/matcher_operator_data/type.yml b/spec/integration/matcher_operator_data/type.yml index 50fe20e38d..55188b3863 100644 --- a/spec/integration/matcher_operator_data/type.yml +++ b/spec/integration/matcher_operator_data/type.yml @@ -14,7 +14,6 @@ pi: $type: -2 error: true - min_server_version: 3.2 - name: type is too large document: @@ -23,7 +22,6 @@ pi: $type: 42 error: true - min_server_version: 3.2 - name: array argument for type - matches document: @@ -32,7 +30,6 @@ pi: $type: [1] matches: true - min_server_version: 3.6 - name: array argument for type - does not match document: @@ -41,7 +38,6 @@ pi: $type: [2] matches: false - min_server_version: 3.6 - name: hash argument document: @@ -58,7 +54,6 @@ pi: $type: [1, 2] matches: true - min_server_version: 3.6 - name: array with multiple elements - does not match document: @@ -67,4 +62,3 @@ pi: $type: [2, 3] matches: false - min_server_version: 3.6 diff --git a/spec/integration/matcher_operator_data/type_array.yml b/spec/integration/matcher_operator_data/type_array.yml index 3661e5c883..5e13df71f2 100644 --- a/spec/integration/matcher_operator_data/type_array.yml +++ b/spec/integration/matcher_operator_data/type_array.yml @@ -5,7 +5,6 @@ prices: $type: 4 matches: true - min_server_version: 3.6 - name: existing field - does not match array document: diff --git a/spec/integration/matcher_operator_data/type_decimal.yml b/spec/integration/matcher_operator_data/type_decimal.yml index 2be9f8a648..9c78345919 100644 --- a/spec/integration/matcher_operator_data/type_decimal.yml +++ b/spec/integration/matcher_operator_data/type_decimal.yml @@ -7,7 +7,6 @@ number: $type: 19 matches: true - min_server_version: 3.4 - name: Decimal128 field - matches document: @@ -18,7 +17,6 @@ decimal128_field: $type: 19 matches: true - min_server_version: 3.4 - name: existing field - does not match decimal document: @@ -27,7 +25,6 @@ name: $type: 19 matches: false - min_server_version: 3.4 # Requires :map_big_decimal_to_decimal128 option to be true. - name: BigDecimal field - matches @@ -38,4 +35,3 @@ big_decimal_field: $type: 19 matches: true - min_server_version: 3.4 diff --git a/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb b/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb index 1bccfb1229..d8cad40347 100644 --- a/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +++ b/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb @@ -2761,7 +2761,6 @@ end context 'when providing a collation' do - min_server_version '3.4' let(:preferences) do person.preferences.where(name: "FIRST").collation(locale: 'en_US', strength: 2).to_a diff --git a/spec/mongoid/association/referenced/has_many/proxy_spec.rb b/spec/mongoid/association/referenced/has_many/proxy_spec.rb index 3f1f345a05..1e9f0d1b21 100644 --- a/spec/mongoid/association/referenced/has_many/proxy_spec.rb +++ b/spec/mongoid/association/referenced/has_many/proxy_spec.rb @@ -2578,8 +2578,6 @@ def with_transaction_via(model, &block) end context 'when providing a collation' do - min_server_version '3.4' - let(:posts) { person.posts.where(title: 'FIRST').collation(locale: 'en_US', strength: 2) } it 'applies the collation option to the query' do diff --git a/spec/mongoid/clients/options_spec.rb b/spec/mongoid/clients/options_spec.rb index 46f3d48b72..d7bb94dbdb 100644 --- a/spec/mongoid/clients/options_spec.rb +++ b/spec/mongoid/clients/options_spec.rb @@ -142,7 +142,8 @@ end it 'does not create a new cluster' do - expect(connections_during).to eq(connections_before) + # https://jira.mongodb.org/browse/MONGOID-5130 + # expect(connections_during).to eq(connections_before) cluster_during.should be cluster_before end @@ -343,7 +344,7 @@ it 'clears the persistence context' do begin; persistence_context; rescue Mongoid::Errors::InvalidPersistenceOption; end - expect(test_model.persistence_context).to eq(Mongoid::PersistenceContext.new(test_model)) + expect(test_model.persistence_context).to eq(Mongoid::PersistenceContext.new(test_model, test_model.storage_options)) end end @@ -441,7 +442,7 @@ let(:options) { { read: :secondary } } it 'does not create a new cluster' do - expect(connections_during).to eq(connections_before) + expect(connections_during).to be <= connections_before end it 'does not disconnect the original cluster' do diff --git a/spec/mongoid/clients/sessions_spec.rb b/spec/mongoid/clients/sessions_spec.rb index 75dba0fbb3..87783dd1ca 100644 --- a/spec/mongoid/clients/sessions_spec.rb +++ b/spec/mongoid/clients/sessions_spec.rb @@ -39,7 +39,6 @@ context 'when a session is used on a model class' do context 'when sessions are supported' do - min_server_version '3.6' around do |example| Mongoid::Clients.with_name(:other).database.collections.each(&:drop) @@ -178,7 +177,6 @@ end context 'when sessions are supported' do - min_server_version '3.6' around do |example| Mongoid::Clients.with_name(:other).database.collections.each(&:drop) diff --git a/spec/mongoid/clients/transactions_spec.rb b/spec/mongoid/clients/transactions_spec.rb index cdc62f1c0a..a166864b90 100644 --- a/spec/mongoid/clients/transactions_spec.rb +++ b/spec/mongoid/clients/transactions_spec.rb @@ -786,49 +786,134 @@ def capture_exception before do Mongoid::Clients.with_name(:default).database.collections.each(&:drop) TransactionsSpecPerson.collection.create + TransactionsSpecPersonWithOnCreate.collection.create + TransactionsSpecPersonWithOnUpdate.collection.create + TransactionsSpecPersonWithOnDestroy.collection.create TransactionSpecRaisesBeforeSave.collection.create TransactionSpecRaisesAfterSave.collection.create end context 'when commit the transaction' do context 'create' do - let!(:subject) do - person = nil - TransactionsSpecPerson.transaction do - person = TransactionsSpecPerson.create!(name: 'James Bond') + context 'without :on option' do + let!(:subject) do + person = nil + TransactionsSpecPerson.transaction do + person = TransactionsSpecPerson.create!(name: 'James Bond') + end + person end - person + + it_behaves_like 'commit callbacks are called' end - it_behaves_like 'commit callbacks are called' + context 'when callback has :on option' do + let!(:subject) do + person = nil + TransactionsSpecPersonWithOnCreate.transaction do + person = TransactionsSpecPersonWithOnCreate.create!(name: 'James Bond') + end + person + end + + it_behaves_like 'commit callbacks are called' + end end context 'save' do - let(:subject) do - TransactionsSpecPerson.create!(name: 'James Bond').tap do |subject| - subject.after_commit_counter.reset - subject.after_rollback_counter.reset + context 'without :on option' do + let(:subject) do + TransactionsSpecPerson.create!(name: 'James Bond').tap do |subject| + subject.after_commit_counter.reset + subject.after_rollback_counter.reset + end + end + + context 'when modified once' do + before do + subject.transaction do + subject.name = 'Austin Powers' + subject.save! + end + end + + it_behaves_like 'commit callbacks are called' + end + + context 'when modified multiple times' do + before do + subject.transaction do + subject.name = 'Austin Powers' + subject.save! + subject.name = 'Jason Bourne' + subject.save! + end + end + + it_behaves_like 'commit callbacks are called' end end - context 'when modified once' do + context 'with :on option' do + let(:subject) do + TransactionsSpecPersonWithOnUpdate.create!(name: 'James Bond').tap do |subject| + subject.after_commit_counter.reset + subject.after_rollback_counter.reset + end + end + + context 'when modified once' do + before do + subject.transaction do + subject.name = 'Austin Powers' + subject.save! + end + end + + it_behaves_like 'commit callbacks are called' + end + + context 'when modified multiple times' do + before do + subject.transaction do + subject.name = 'Austin Powers' + subject.save! + subject.name = 'Jason Bourne' + subject.save! + end + end + + it_behaves_like 'commit callbacks are called' + end + end + end + + context 'update_attributes' do + context 'without :on option' do + let(:subject) do + TransactionsSpecPerson.create!(name: 'James Bond').tap do |subject| + subject.after_commit_counter.reset + subject.after_rollback_counter.reset + end + end + before do subject.transaction do - subject.name = 'Austin Powers' - subject.save! + subject.update_attributes!(name: 'Austin Powers') end end it_behaves_like 'commit callbacks are called' end - context 'when modified multiple times' do + context 'when callback has on option' do + let(:subject) do + TransactionsSpecPersonWithOnUpdate.create!(name: 'Jason Bourne') + end + before do - subject.transaction do - subject.name = 'Austin Powers' - subject.save! - subject.name = 'Jason Bourne' - subject.save! + TransactionsSpecPersonWithOnUpdate.transaction do + subject.update_attributes!(name: 'Foma Kiniaev') end end @@ -836,61 +921,86 @@ def capture_exception end end - context 'update_attributes' do - let(:subject) do - TransactionsSpecPerson.create!(name: 'James Bond').tap do |subject| - subject.after_commit_counter.reset - subject.after_rollback_counter.reset + context 'destroy' do + context 'without :on option' do + let(:after_commit_counter) do + TransactionsSpecCounter.new end - end - before do - subject.transaction do - subject.update_attributes!(name: 'Austin Powers') + let(:after_rollback_counter) do + TransactionsSpecCounter.new end - end - it_behaves_like 'commit callbacks are called' - end + let(:subject) do + TransactionsSpecPerson.create!(name: 'James Bond').tap do |p| + p.after_commit_counter = after_commit_counter + p.after_rollback_counter = after_rollback_counter + end + end - context 'destroy' do - let(:after_commit_counter) do - TransactionsSpecCounter.new - end + before do + subject.transaction do + subject.destroy + end + end - let(:after_rollback_counter) do - TransactionsSpecCounter.new + it_behaves_like 'commit callbacks are called' end - let(:subject) do - TransactionsSpecPerson.create!(name: 'James Bond').tap do |p| - p.after_commit_counter = after_commit_counter - p.after_rollback_counter = after_rollback_counter + context 'with :on option' do + let(:after_commit_counter) do + TransactionsSpecCounter.new end - end - before do - subject.transaction do - subject.destroy + let(:after_rollback_counter) do + TransactionsSpecCounter.new end - end - it_behaves_like 'commit callbacks are called' + let(:subject) do + TransactionsSpecPersonWithOnDestroy.create!(name: 'James Bond').tap do |p| + p.after_commit_counter = after_commit_counter + p.after_rollback_counter = after_rollback_counter + end + end + + before do + subject.transaction do + subject.destroy + end + end + + it_behaves_like 'commit callbacks are called' + end end end context 'when rollback the transaction' do context 'create' do - let!(:subject) do - person = nil - TransactionsSpecPerson.transaction do - person = TransactionsSpecPerson.create!(name: 'James Bond') - raise Mongoid::Errors::Rollback + context 'without :on option' do + let!(:subject) do + person = nil + TransactionsSpecPerson.transaction do + person = TransactionsSpecPerson.create!(name: 'James Bond') + raise Mongoid::Errors::Rollback + end + person end - person + + it_behaves_like 'rollback callbacks are called' end - it_behaves_like 'rollback callbacks are called' + context 'with :on option' do + let!(:subject) do + person = nil + TransactionsSpecPersonWithOnCreate.transaction do + person = TransactionsSpecPersonWithOnCreate.create!(name: 'James Bond') + raise Mongoid::Errors::Rollback + end + person + end + + it_behaves_like 'rollback callbacks are called' + end end context 'save' do diff --git a/spec/mongoid/clients/transactions_spec_models.rb b/spec/mongoid/clients/transactions_spec_models.rb index c1629c074b..eafd0f6d96 100644 --- a/spec/mongoid/clients/transactions_spec_models.rb +++ b/spec/mongoid/clients/transactions_spec_models.rb @@ -50,6 +50,50 @@ class TransactionsSpecPerson end end +class TransactionsSpecPersonWithOnCreate + include Mongoid::Document + include TransactionsSpecCountable + + field :name, type: String + + after_commit on: :create do + after_commit_counter.inc + end + + after_rollback on: :create do + after_rollback_counter.inc + end +end + +class TransactionsSpecPersonWithOnUpdate + include Mongoid::Document + include TransactionsSpecCountable + + field :name, type: String + + after_commit on: :update do + after_commit_counter.inc + end + + after_rollback on: :update do + after_rollback_counter.inc + end +end + +class TransactionsSpecPersonWithOnDestroy + include Mongoid::Document + include TransactionsSpecCountable + + field :name, type: String + + after_commit on: :destroy do + after_commit_counter.inc + end + + after_rollback on: :destroy do + after_rollback_counter.inc + end +end class TransactionSpecRaisesBeforeSave include Mongoid::Document include TransactionsSpecCountable diff --git a/spec/mongoid/config/defaults_spec.rb b/spec/mongoid/config/defaults_spec.rb index cbfca92590..ddff45b451 100644 --- a/spec/mongoid/config/defaults_spec.rb +++ b/spec/mongoid/config/defaults_spec.rb @@ -26,12 +26,14 @@ shared_examples "uses settings for 8.1" do it "uses settings for 8.1" do expect(Mongoid.immutable_ids).to be false + expect(Mongoid.legacy_persistence_context_behavior).to be true end end shared_examples "does not use settings for 8.1" do it "does not use settings for 8.1" do expect(Mongoid.immutable_ids).to be true + expect(Mongoid.legacy_persistence_context_behavior).to be false end end @@ -81,12 +83,12 @@ end context "when given version an invalid version" do - let(:version) { 4.2 } + let(:version) { '4,2' } it "raises an error" do expect do config.load_defaults(version) - end.to raise_error(ArgumentError, 'Unknown version: 4.2') + end.to raise_error(ArgumentError, 'Unknown version: 4,2') end end end diff --git a/spec/mongoid/config_spec.rb b/spec/mongoid/config_spec.rb index 49afc5eb6d..5e1f8c02a6 100644 --- a/spec/mongoid/config_spec.rb +++ b/spec/mongoid/config_spec.rb @@ -352,6 +352,13 @@ it_behaves_like "a config option" end + context 'when setting the legacy_persistence_context_behavior option in the config' do + let(:option) { :legacy_persistence_context_behavior } + let(:default) { false } + + it_behaves_like "a config option" + end + describe "#load!" do let(:file) do diff --git a/spec/mongoid/contextual/atomic_spec.rb b/spec/mongoid/contextual/atomic_spec.rb index c38a6fac1c..bfdf07052e 100644 --- a/spec/mongoid/contextual/atomic_spec.rb +++ b/spec/mongoid/contextual/atomic_spec.rb @@ -78,7 +78,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let(:criteria) do Band.where(members: [ "DAVE" ]).collation(locale: 'en_US', strength: 2) @@ -175,7 +174,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let(:criteria) do Band.where(members: [ "DAVE" ]).collation(locale: 'en_US', strength: 2) @@ -247,7 +245,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let!(:depeche_mode) do Band.create!(members: [ "Dave" ], likes: 60) @@ -327,7 +324,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let!(:depeche_mode) do Band.create!(members: [ "Dave" ]) @@ -392,7 +388,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let!(:depeche_mode) { Band.create!(members: [ "Dave" ], years: 3) } let(:criteria) do @@ -458,7 +453,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let!(:depeche_mode) do Band.create!(members: [ "Dave" ]) @@ -513,7 +507,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let!(:depeche_mode) do Band.create!(members: [ "Dave" ]) @@ -571,7 +564,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let!(:depeche_mode) do Band.create!(members: [ "Dave", "Alan", "Fletch" ]) @@ -629,7 +621,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let!(:depeche_mode) do Band.create!(members: [ "Dave" ]) @@ -695,7 +686,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let!(:depeche_mode) do Band.create!(members: [ "Dave" ]) @@ -757,7 +747,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let!(:depeche_mode) do Band.create!(members: [ "Dave" ]) @@ -967,7 +956,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let!(:depeche_mode) do Band.create!(name: "Depeche Mode", years: 10) diff --git a/spec/mongoid/contextual/mongo_spec.rb b/spec/mongoid/contextual/mongo_spec.rb index 1e38025e3e..3ee2382524 100644 --- a/spec/mongoid/contextual/mongo_spec.rb +++ b/spec/mongoid/contextual/mongo_spec.rb @@ -138,7 +138,6 @@ end context 'when a collation is specified' do - min_server_version '3.4' let(:context) do described_class.new(criteria) @@ -305,7 +304,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let(:criteria) do Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2) @@ -410,7 +408,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let(:criteria) do Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2) @@ -534,8 +531,6 @@ end context 'when a collation is specified' do - min_server_version '3.4' - before do Band.create!(name: 'DEPECHE MODE') end @@ -1165,7 +1160,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let(:criteria) do Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2) @@ -1622,7 +1616,6 @@ end context 'when a collation is specified on the criteria' do - min_server_version '3.4' let(:criteria) do Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2) @@ -1776,7 +1769,6 @@ end context 'when a collation is specified on the criteria' do - min_server_version '3.4' let(:criteria) do Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2) @@ -1851,7 +1843,6 @@ end context 'when a collation is specified on the criteria' do - min_server_version '3.4' let(:criteria) do Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2) @@ -1928,7 +1919,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let(:criteria) do Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2) @@ -2101,7 +2091,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let(:criteria) do Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2) @@ -2274,7 +2263,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let(:criteria) do Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2) @@ -2447,7 +2435,6 @@ end context 'when the criteria has a collation' do - min_server_version '3.4' let(:criteria) do Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2) @@ -3496,7 +3483,6 @@ end context 'when provided array filters' do - min_server_version '3.6' before do Band.delete_all @@ -3718,7 +3704,6 @@ end context 'when provided array filters' do - min_server_version '3.6' before do Band.delete_all diff --git a/spec/mongoid/criteria/marshalable_spec.rb b/spec/mongoid/criteria/marshalable_spec.rb index c7050d0e6c..fed243ae5f 100644 --- a/spec/mongoid/criteria/marshalable_spec.rb +++ b/spec/mongoid/criteria/marshalable_spec.rb @@ -31,8 +31,8 @@ let(:dump) { Marshal.dump(criteria) } before do - expect_any_instance_of(Mongoid::Criteria).to receive(:marshal_dump).and_wrap_original do |m, *args| - data = m.call(*args) + expect_any_instance_of(Mongoid::Criteria).to receive(:marshal_dump).and_wrap_original do |m, *args, **kwargs| + data = m.call(*args, **kwargs) data[1] = :mongo1x data end diff --git a/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb b/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb index 64de38c384..247d12e998 100644 --- a/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb +++ b/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb @@ -13,6 +13,10 @@ end end + after do + Symbol.undef_method(:fubar) + end + let(:fubar) do :testing.fubar end diff --git a/spec/mongoid/criteria_spec.rb b/spec/mongoid/criteria_spec.rb index 3808166cc1..69d1dbb3f6 100644 --- a/spec/mongoid/criteria_spec.rb +++ b/spec/mongoid/criteria_spec.rb @@ -288,23 +288,9 @@ Band.where(name: "Depeche Mode") end - # as_json changed in rails 6 to call as_json on serializable_hash. - # https://github.com/rails/rails/commit/2e5cb980a448e7f4ab00df6e9ad4c1cc456616aa - context 'rails < 6' do - max_rails_version '5.2' - - it "returns the criteria as a json hash" do - expect(criteria.as_json).to eq([ band.serializable_hash ]) - end - end - - context 'rails >= 6' do - min_rails_version '6.0' - - it "returns the criteria as a json hash" do - expect(criteria.as_json).to eq([ band.serializable_hash.as_json ]) - end + it "returns the criteria as a json hash" do + expect(criteria.as_json).to eq([ band.serializable_hash.as_json ]) end end @@ -2639,7 +2625,6 @@ def self.ages; self; end end context "when querying on a BSON::Decimal128" do - min_server_version '3.4' let(:decimal) do BSON::Decimal128.new("0.0005") diff --git a/spec/mongoid/document_persistence_context_spec.rb b/spec/mongoid/document_persistence_context_spec.rb index fae06be810..5b23757e1c 100644 --- a/spec/mongoid/document_persistence_context_spec.rb +++ b/spec/mongoid/document_persistence_context_spec.rb @@ -30,4 +30,80 @@ Person.collection_name.should == :people end end + + context 'when loaded with an overridden persistence context' do + let(:options) { { collection: 'extra_people' } } + let(:person) { Person.with(options) { Person.create username: 'zyg14' } } + + # Mongoid 9+ default persistence behavior + context 'when Mongoid.legacy_persistence_context_behavior is false' do + config_override :legacy_persistence_context_behavior, false + + it 'remembers its persistence context when created' do + expect(person.collection_name).to be == :extra_people + end + + it 'remembers its context when queried specifically' do + person_by_id = Person.with(options) { Person.find(_id: person._id) } + expect(person_by_id.collection_name).to be == :extra_people + end + + it 'remembers its context when queried generally' do + person # force the person to be created + person_generally = Person.with(options) { Person.all[0] } + expect(person_generally.collection_name).to be == :extra_people + end + + it 'can be reloaded without specifying the context' do + expect { person.reload }.not_to raise_error + expect(person.collection_name).to be == :extra_people + end + + it 'can be updated without specifying the context' do + person.update username: 'zyg15' + expect(Person.with(options) { Person.first.username }).to be == 'zyg15' + end + + it 'an explicit context takes precedence over a remembered context when persisting' do + person.username = 'bob' + # should not actually save -- the person does not exist in the + # `other` collection and so cannot be updated. + Person.with(collection: 'other') { person.save! } + expect(person.reload.username).to eq 'zyg14' + end + + it 'an explicit context takes precedence over a remembered context when reloading' do + expect { Person.with(collection: 'other') { person.reload } }.to raise_error(Mongoid::Errors::DocumentNotFound) + end + end + + # pre-9.0 default persistence behavior + context 'when Mongoid.legacy_persistence_context_behavior is true' do + config_override :legacy_persistence_context_behavior, true + + it 'does not remember its persistence context when created' do + expect(person.collection_name).to be == :people + end + + it 'does not remember its context when queried specifically' do + person_by_id = Person.with(options) { Person.find(_id: person._id) } + expect(person_by_id.collection_name).to be == :people + end + + it 'does not remember its context when queried generally' do + person # force the person to be created + person_generally = Person.with(options) { Person.all[0] } + expect(person_generally.collection_name).to be == :people + end + + it 'cannot be reloaded without specifying the context' do + expect { person.reload }.to raise_error + end + + it 'cannot be updated without specifying the context' do + person.update username: 'zyg15' + expect(Person.with(options) { Person.first.username }).to be == 'zyg14' + end + end + end end diff --git a/spec/mongoid/document_spec.rb b/spec/mongoid/document_spec.rb index 547b5f26b1..938ac8a8fa 100644 --- a/spec/mongoid/document_spec.rb +++ b/spec/mongoid/document_spec.rb @@ -458,84 +458,6 @@ class << self; attr_accessor :name; end end end end - - context 'deprecated :compact option' do - # Since rails 6 differs in how it treats id fields, - # run this test on one version of rails. Currently rails 6 is in beta, - # when it is released this version should be changed to 6. - max_rails_version '5.2' - - before do - # These tests require a specific set of defined attributes - # on the model - expect(church.as_json.keys.sort).to eq(%w(_id location name)) - end - - context 'deprecation' do - let(:church) do - Church.create!(name: 'St. Basil') - end - - let(:message) do - '#as_json :compact option is deprecated. Please call #compact on the returned Hash object instead.' - end - - it 'logs a deprecation warning when :compact is given' do - expect(Mongoid::Warnings).to receive(:warn_as_json_compact_deprecated) - church.as_json(compact: true) - end - - it 'does not log a deprecation warning when :compact is not given' do - expect(Mongoid::Warnings).to_not receive(:warn_as_json_compact_deprecated) - church.as_json - end - end - - context 'there is a nil valued attribute' do - let(:church) do - Church.create!(name: 'St. Basil') - end - - it 'returns non-nil fields and _id only' do - actual = church.as_json(compact: true) - expect(actual).to eq('_id' => church.id, 'name' => 'St. Basil') - end - end - - context 'all attrbutes are nil valued' do - let(:church) do - Church.create! - end - - it 'returns a hash with _id only' do - actual = church.as_json(compact: true) - expect(actual).to eq('_id' => church.id) - end - end - - context 'there are no nil valued attributes' do - let(:church) do - Church.create!(name: 'St. Basil', location: {}) - end - - it 'returns all fields and _id' do - actual = church.as_json(compact: true) - expect(actual).to eq('_id' => church.id, 'name' => 'St. Basil', - 'location' => {}) - end - end - - context 'when option is specified as a truthy value' do - let(:church) do - Church.create!(name: 'St. Basil') - end - - it 'returns non-nil fields and _id only' do - actual = church.as_json(compact: 1) - expect(actual).to eq('_id' => church.id, 'name' => 'St. Basil') - end - end - end end describe "#as_document" do diff --git a/spec/mongoid/errors/mongoid_error_spec.rb b/spec/mongoid/errors/mongoid_error_spec.rb index cea968413a..0f2a348749 100644 --- a/spec/mongoid/errors/mongoid_error_spec.rb +++ b/spec/mongoid/errors/mongoid_error_spec.rb @@ -10,28 +10,14 @@ let(:options) { {} } before do - # JRuby 9.3 RUBY_VERSION is set to 2.6.8, but the behavior matches Ruby 2.7. - # See https://github.com/jruby/jruby/issues/7184 - if RUBY_VERSION >= '2.7' || (BSON::Environment.jruby? && JRUBY_VERSION >= '9.3') - {"message_title" => "message", "summary_title" => "summary", "resolution_title" => "resolution"}.each do |key, name| - expect(::I18n).to receive(:translate).with("mongoid.errors.messages.#{key}", **{}).and_return(name) - end - - ["message", "summary", "resolution"].each do |name| - expect(::I18n).to receive(:translate). - with("mongoid.errors.messages.#{key}.#{name}", **{}). - and_return(name) - end - else - {"message_title" => "message", "summary_title" => "summary", "resolution_title" => "resolution"}.each do |key, name| - expect(::I18n).to receive(:translate).with("mongoid.errors.messages.#{key}", {}).and_return(name) - end - - ["message", "summary", "resolution"].each do |name| - expect(::I18n).to receive(:translate). - with("mongoid.errors.messages.#{key}.#{name}", {}). - and_return(name) - end + {"message_title" => "message", "summary_title" => "summary", "resolution_title" => "resolution"}.each do |key, name| + expect(::I18n).to receive(:translate).with("mongoid.errors.messages.#{key}", **{}).and_return(name) + end + + ["message", "summary", "resolution"].each do |name| + expect(::I18n).to receive(:translate). + with("mongoid.errors.messages.#{key}.#{name}", **{}). + and_return(name) end error.compose_message(key, options) diff --git a/spec/mongoid/extensions/big_decimal_spec.rb b/spec/mongoid/extensions/big_decimal_spec.rb index 08f8363336..dcfa8f4ee8 100644 --- a/spec/mongoid/extensions/big_decimal_spec.rb +++ b/spec/mongoid/extensions/big_decimal_spec.rb @@ -124,7 +124,7 @@ end it "returns a float" do - expect(demongoized).to eq(value) + expect(demongoized).to be_within(0.00001).of(value) end end @@ -572,7 +572,7 @@ end it "returns a float" do - expect(demongoized).to eq(value) + expect(demongoized).to be_within(0.00001).of(value) end end diff --git a/spec/mongoid/extensions/set_spec.rb b/spec/mongoid/extensions/set_spec.rb index cde2d7da01..f81e43c5fc 100644 --- a/spec/mongoid/extensions/set_spec.rb +++ b/spec/mongoid/extensions/set_spec.rb @@ -113,9 +113,7 @@ end before do - expect(BigDecimal).to receive(:mongoize).exactly(4).times.and_wrap_original do |m, *args| - 1 - end + expect(BigDecimal).to receive(:mongoize).exactly(4).times.and_return(1) end context "when the input is a set" do diff --git a/spec/mongoid/monkey_patches_spec.rb b/spec/mongoid/monkey_patches_spec.rb new file mode 100644 index 0000000000..8fdded9afd --- /dev/null +++ b/spec/mongoid/monkey_patches_spec.rb @@ -0,0 +1,204 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# @note This test ensures that we do not inadvertently introduce new monkey patches +# to Mongoid. Existing monkey patch methods which are marked with +Mongoid.deprecated+ +# are excluded from this test. +RSpec.describe('Do not add monkey patches') do # rubocop:disable RSpec/DescribeClass + classes = [ + Object, + Array, + BigDecimal, + Date, + DateTime, + FalseClass, + Float, + Hash, + Integer, + Module, + NilClass, + Range, + Regexp, + Set, + String, + Symbol, + Time, + TrueClass, + ActiveSupport::TimeWithZone, + BSON::Binary, + BSON::Decimal128, + BSON::ObjectId, + BSON::Regexp, + BSON::Regexp::Raw + ] + + expected_instance_methods = { + Object => %i[ + __add__ + __add_from_array__ + __array__ + __deep_copy__ + __evolve_object_id__ + __expand_complex__ + __intersect__ + __intersect_from_array__ + __intersect_from_object__ + __mongoize_object_id__ + __mongoize_time__ + __union__ + __union_from_object__ + ivar + mongoize + numeric? + remove_ivar + resizable? + substitutable + ], + Array => %i[ + __evolve_date__ + __evolve_time__ + __sort_option__ + __sort_pair__ + delete_one + ], + Date => %i[ + __evolve_date__ + __evolve_time__ + ], + DateTime => %i[ + __evolve_date__ + __evolve_time__ + ], + FalseClass => %i[is_a?], + Float => %i[ + __evolve_date__ + __evolve_time__ + ], + Hash => %i[ + __sort_option__ + ], + Integer => %i[ + __evolve_date__ + __evolve_time__ + ], + Module => %i[ + re_define_method + ], + NilClass => %i[ + __evolve_date__ + __evolve_time__ + __expanded__ + __override__ + collectionize + ], + Range => %i[ + __evolve_date__ + __evolve_range__ + __evolve_time__ + ], + String => %i[ + __evolve_date__ + __evolve_time__ + __expr_part__ + __mongo_expression__ + __sort_option__ + before_type_cast? + collectionize + reader + valid_method_name? + writer? + ], + Symbol => %i[ + __expr_part__ + add_to_set + all + asc + ascending + avg + desc + descending + elem_match + eq + exists + first + gt + gte + in + intersects_line + intersects_point + intersects_polygon + last + lt + lte + max + min + mod + ne + near + near_sphere + nin + not + push + sum + with_size + with_type + within_box + within_polygon + ], + TrueClass => %i[is_a?], + Time => %i[ + __evolve_date__ + __evolve_time__ + ], + ActiveSupport::TimeWithZone => %i[ + __evolve_date__ + __evolve_time__ + _bson_to_i + ], + BSON::Decimal128 => %i[ + __evolve_decimal128__ + ] + }.each_value(&:sort!) + + expected_class_methods = { + Object => %i[ + demongoize + evolve + re_define_method + ], + Float => %i[__numeric__], + Integer => %i[__numeric__], + String => %i[__expr_part__], + Symbol => %i[add_key] + }.each_value(&:sort!) + + def mongoid_method?(method) + method.source_location&.first&.include?('/lib/mongoid/') + end + + def added_instance_methods(klass) + methods = klass.instance_methods.select { |m| mongoid_method?(klass.instance_method(m)) } + methods -= added_instance_methods(Object) unless klass == Object + methods.sort + end + + def added_class_methods(klass) + methods = klass.methods.select { |m| mongoid_method?(klass.method(m)) } + methods -= added_instance_methods(Object) + methods -= added_class_methods(Object) unless klass == Object + methods.sort + end + + classes.each do |klass| + context klass.name do + it 'adds no unexpected instance methods' do + expect(added_instance_methods(klass)).to eq(expected_instance_methods[klass] || []) + end + + it 'adds no unexpected class methods' do + expect(added_class_methods(klass)).to eq(expected_class_methods[klass] || []) + end + end + end +end diff --git a/spec/mongoid/persistable/savable_spec.rb b/spec/mongoid/persistable/savable_spec.rb index d7931f73f1..5a5489dad2 100644 --- a/spec/mongoid/persistable/savable_spec.rb +++ b/spec/mongoid/persistable/savable_spec.rb @@ -295,11 +295,6 @@ expect(truck.crates[0].toys[0].name).to eq "Teddy bear" expect(truck.crates[1].volume).to eq 0.8 expect(truck.crates[1].toys.size).to eq 0 - - # TODO: MONGOID-5026: combine the updates so that there are - # no conflicts. - #expect(truck.atomic_updates[:conflicts]).to eq nil - expect { truck.save! }.not_to raise_error _truck = Truck.find(truck.id) diff --git a/spec/mongoid/railties/bson_object_id_serializer_spec.rb b/spec/mongoid/railties/bson_object_id_serializer_spec.rb index 0e04dc7afc..694e14232c 100644 --- a/spec/mongoid/railties/bson_object_id_serializer_spec.rb +++ b/spec/mongoid/railties/bson_object_id_serializer_spec.rb @@ -5,8 +5,9 @@ require 'active_job' require 'mongoid/railties/bson_object_id_serializer' -describe Mongoid::Railties::ActiveJobSerializers::BsonObjectIdSerializer do - let(:serializer) { described_class.instance } +describe 'Mongoid::Railties::ActiveJobSerializers::BsonObjectIdSerializer' do + + let(:serializer) { Mongoid::Railties::ActiveJobSerializers::BsonObjectIdSerializer.instance } let(:object_id) { BSON::ObjectId.new } describe '#serialize' do diff --git a/spec/mongoid/scopable_spec.rb b/spec/mongoid/scopable_spec.rb index 7535d6de1b..4f971c07f9 100644 --- a/spec/mongoid/scopable_spec.rb +++ b/spec/mongoid/scopable_spec.rb @@ -343,7 +343,6 @@ def self.default_scope context "when provided a criteria" do context 'when a collation is defined on the criteria' do - min_server_version '3.4' before do Band.scope(:tests, ->{ Band.where(name: 'TESTING').collation(locale: 'en_US', strength: 2) }) diff --git a/spec/mongoid/validatable/uniqueness_spec.rb b/spec/mongoid/validatable/uniqueness_spec.rb index d35f344095..e931ac3d73 100644 --- a/spec/mongoid/validatable/uniqueness_spec.rb +++ b/spec/mongoid/validatable/uniqueness_spec.rb @@ -20,8 +20,8 @@ end it "reads from the primary" do - expect_any_instance_of(Mongoid::Criteria).to receive(:read).once.and_wrap_original do |m, *args| - crit = m.call(*args) + expect_any_instance_of(Mongoid::Criteria).to receive(:read).once.and_wrap_original do |m, *args, **kwargs| + crit = m.call(*args, **kwargs) expect(crit.view.options["read"]).to eq({ "mode" => :primary }) crit end @@ -1670,8 +1670,8 @@ let(:word) { Word.create! } it "reads from the primary" do - expect_any_instance_of(Mongoid::Criteria).to receive(:read).once.and_wrap_original do |m, *args| - crit = m.call(*args) + expect_any_instance_of(Mongoid::Criteria).to receive(:read).once.and_wrap_original do |m, *args, **kwargs| + crit = m.call(*args, **kwargs) expect(crit.options[:read]).to eq({ mode: :primary }) crit end @@ -2533,9 +2533,7 @@ class SpanishActor < EuropeanActor end after do - # i18n 1.0 requires +send+ because +translations+ aren't public. - # Newer i18n versions have it as public. - I18n.backend.send(:translations).delete(:fr) + I18n.backend.translations.delete(:fr) end it "correctly translates the error message" do diff --git a/spec/shared b/spec/shared index 0fdadd7881..53a38fe8f1 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit 0fdadd7881fbc2ed4df999015a751de3e7475fda +Subproject commit 53a38fe8f165fd71687cf6205d2f1aac0dbde10b