diff --git a/.rubocop.yml b/.rubocop.yml index d38f97fdb..45db10559 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -142,9 +142,6 @@ Layout/EmptyLinesAroundClassBody: Layout/EmptyLinesAroundModuleBody: Enabled: false -Style/IfUnlessModifier: - Enabled: false - Performance/CollectionLiteralInLoop: Exclude: - 'spec/mongoid/association/embedded/embeds_many/proxy_spec.rb' @@ -153,6 +150,9 @@ Performance/CollectionLiteralInLoop: - 'spec/mongoid/criteria/queryable/selectable_spec.rb' - 'spec/mongoid/criteria/queryable/selector_spec.rb' +Style/IfUnlessModifier: + Enabled: false + # -------------------------------------------------- # These cops are intentionally disabled due to incompatibility. @@ -215,6 +215,9 @@ Naming/PredicateName: - has_attribute? - has_attribute_before_type_cast? +RSpec/IndexedLet: + Enabled: false + RSpec/NotToNot: EnforcedStyle: to_not diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8b95d8ee4..af5d756b5 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --exclude-limit 1000` -# on 2024-01-16 04:19:18 UTC using RuboCop version 1.60.0. +# on 2024-05-06 14:01:50 UTC using RuboCop version 1.63.4. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -21,7 +21,7 @@ Lint/SelfAssignment: - 'spec/integration/associations/has_one_spec.rb' - 'spec/mongoid/association/embedded/embeds_one/proxy_spec.rb' -# Offense count: 108 +# Offense count: 110 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 154 @@ -37,22 +37,22 @@ Metrics/BlockLength: Metrics/BlockNesting: Max: 6 -# Offense count: 17 +# Offense count: 18 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 337 -# Offense count: 51 +# Offense count: 52 # Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/CyclomaticComplexity: Max: 33 -# Offense count: 184 +# Offense count: 185 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Max: 87 -# Offense count: 22 +# Offense count: 21 # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: Max: 330 @@ -63,7 +63,7 @@ Metrics/ParameterLists: MaxOptionalParameters: 4 Max: 6 -# Offense count: 40 +# Offense count: 41 # Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/PerceivedComplexity: Max: 20 @@ -262,7 +262,15 @@ RSpec/DescribeClass: - 'spec/mongoid/railties/console_sandbox_spec.rb' - 'spec/mongoid/tasks/database_rake_spec.rb' -# Offense count: 124 +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: CustomTransform, IgnoredWords, DisallowedExamples. +# DisallowedExamples: works +RSpec/ExampleWording: + Exclude: + - 'spec/mongoid/persistable/updatable_spec.rb' + +# Offense count: 120 RSpec/ExpectInHook: Exclude: - 'spec/integration/associations/embedded_spec.rb' @@ -301,7 +309,6 @@ RSpec/ExpectInHook: - 'spec/mongoid/serializable_spec.rb' - 'spec/mongoid/tasks/database_rake_spec.rb' - 'spec/mongoid/tasks/encryption_spec.rb' - - 'spec/mongoid/validatable/associated_spec.rb' - 'spec/mongoid/validatable/presence_spec.rb' - 'spec/support/immutable_ids.rb' @@ -335,13 +342,14 @@ RSpec/FilePath: - 'spec/mongoid/validatable/uniqueness_spec.rb' - 'spec/mongoid/version_spec.rb' -# Offense count: 18 +# Offense count: 21 RSpec/IteratedExpectation: Exclude: - 'spec/mongoid/association/referenced/belongs_to/eager_spec.rb' - 'spec/mongoid/association/referenced/has_many/eager_spec.rb' - 'spec/mongoid/association/referenced/has_many/enumerable_spec.rb' - 'spec/mongoid/association/referenced/has_one/eager_spec.rb' + - 'spec/mongoid/clients_spec.rb' - 'spec/mongoid/contextual/memory_spec.rb' - 'spec/mongoid/contextual/mongo_spec.rb' - 'spec/mongoid/criteria_spec.rb' @@ -481,7 +489,7 @@ RSpec/LetSetup: - 'spec/mongoid/touchable_spec.rb' - 'spec/mongoid/validatable/uniqueness_spec.rb' -# Offense count: 308 +# Offense count: 306 # Configuration parameters: . # SupportedStyles: have_received, receive RSpec/MessageSpies: @@ -516,7 +524,7 @@ RSpec/NamedSubject: RSpec/NestedGroups: Max: 13 -# Offense count: 31 +# Offense count: 32 # Configuration parameters: AllowedPatterns. # AllowedPatterns: ^expect_, ^assert_ RSpec/NoExpectationExample: @@ -531,6 +539,7 @@ RSpec/NoExpectationExample: - 'spec/mongoid/collection_configurable_spec.rb' - 'spec/mongoid/document_spec.rb' - 'spec/mongoid/errors/mongoid_error_spec.rb' + - 'spec/mongoid/inspectable_spec.rb' - 'spec/mongoid/persistence_context_spec.rb' - 'spec/mongoid/scopable_spec.rb' - 'spec/mongoid/tasks/database_rake_spec.rb' @@ -543,18 +552,52 @@ RSpec/OverwritingSetup: - 'spec/mongoid/interceptable_spec.rb' - 'spec/mongoid/serializable_spec.rb' -# Offense count: 17 +# Offense count: 3 RSpec/PendingWithoutReason: Exclude: + - 'spec/mongoid/criteria/queryable/aggregable_spec.rb' + - 'spec/mongoid/serializable_spec.rb' + +# Offense count: 8 +# This cop supports safe autocorrection (--autocorrect). +RSpec/RedundantPredicateMatcher: + Exclude: + - 'spec/mongoid/copyable_spec.rb' + - 'spec/mongoid/document_spec.rb' + - 'spec/mongoid/equality_spec.rb' + +# Offense count: 105 +RSpec/RemoveConst: + Exclude: + - 'spec/integration/discriminator_key_spec.rb' + - 'spec/mongoid/association/auto_save_spec.rb' - 'spec/mongoid/association/embedded/embedded_in_spec.rb' - - 'spec/mongoid/association/embedded/embeds_many_spec.rb' - - 'spec/mongoid/association/embedded/embeds_one_spec.rb' + - 'spec/mongoid/association/embedded/embeds_many/proxy_spec.rb' + - 'spec/mongoid/association/macros_spec.rb' + - 'spec/mongoid/association/options_spec.rb' + - 'spec/mongoid/association/referenced/belongs_to/proxy_spec.rb' - 'spec/mongoid/association/referenced/belongs_to_spec.rb' - 'spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb' - 'spec/mongoid/association/referenced/has_many_spec.rb' + - 'spec/mongoid/association/referenced/has_one/proxy_spec.rb' - 'spec/mongoid/association/referenced/has_one_spec.rb' - - 'spec/mongoid/criteria/queryable/aggregable_spec.rb' - - 'spec/mongoid/serializable_spec.rb' + - 'spec/mongoid/association/syncable_spec.rb' + - 'spec/mongoid/clients_spec.rb' + - 'spec/mongoid/collection_configurable_spec.rb' + - 'spec/mongoid/copyable_spec.rb' + - 'spec/mongoid/criteria/includable_spec.rb' + - 'spec/mongoid/criteria/queryable/options_spec.rb' + - 'spec/mongoid/criteria/queryable/selectable_spec.rb' + - 'spec/mongoid/criteria/queryable/selectable_where_spec.rb' + - 'spec/mongoid/criteria/queryable/selector_spec.rb' + - 'spec/mongoid/document_spec.rb' + - 'spec/mongoid/fields/standard_spec.rb' + - 'spec/mongoid/scopable_spec.rb' + - 'spec/mongoid/stateful_spec.rb' + - 'spec/mongoid/tasks/database_spec.rb' + - 'spec/mongoid/timestamps/timeless_spec.rb' + - 'spec/mongoid/validatable/numericality_spec.rb' + - 'spec/support/feature_sandbox.rb' # Offense count: 17 RSpec/RepeatedDescription: @@ -639,6 +682,7 @@ RSpec/ScatteredLet: - 'spec/mongoid/scopable_spec.rb' # Offense count: 67 +# This cop supports safe autocorrection (--autocorrect). RSpec/ScatteredSetup: Exclude: - 'spec/integration/stringified_symbol_field_spec.rb' @@ -650,7 +694,37 @@ RSpec/ScatteredSetup: - 'spec/mongoid/copyable_spec.rb' - 'spec/mongoid/persistable/deletable_spec.rb' -# Offense count: 12 +# Offense count: 24 +# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. +# Include: **/*_spec.rb +RSpec/SpecFilePathFormat: + Exclude: + - 'spec/integration/atomic/modifiers_spec.rb' + - 'spec/integration/bson_regexp_raw_spec.rb' + - 'spec/integration/document_spec.rb' + - 'spec/mongoid/association/auto_save_spec.rb' + - 'spec/mongoid/association/counter_cache_spec.rb' + - 'spec/mongoid/association/eager_spec.rb' + - 'spec/mongoid/clients/transactions_spec.rb' + - 'spec/mongoid/criteria/queryable/extensions/boolean_spec.rb' + - 'spec/mongoid/criteria/queryable/extensions/numeric_spec.rb' + - 'spec/mongoid/criteria/queryable/extensions/regexp_raw_spec.rb' + - 'spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb' + - 'spec/mongoid/criteria/queryable/queryable_spec.rb' + - 'spec/mongoid/extensions/binary_spec.rb' + - 'spec/mongoid/extensions/boolean_spec.rb' + - 'spec/mongoid/extensions/raw_value_spec.rb' + - 'spec/mongoid/extensions/stringified_symbol_spec.rb' + - 'spec/mongoid/loading_spec.rb' + - 'spec/mongoid/validatable/associated_spec.rb' + - 'spec/mongoid/validatable/format_spec.rb' + - 'spec/mongoid/validatable/length_spec.rb' + - 'spec/mongoid/validatable/numericality_spec.rb' + - 'spec/mongoid/validatable/presence_spec.rb' + - 'spec/mongoid/validatable/uniqueness_spec.rb' + - 'spec/mongoid/version_spec.rb' + +# Offense count: 8 RSpec/StubbedMock: Exclude: - 'spec/mongoid/clients_spec.rb' @@ -658,7 +732,6 @@ RSpec/StubbedMock: - 'spec/mongoid/persistence_context_spec.rb' - 'spec/mongoid/tasks/database_spec.rb' - 'spec/mongoid/tasks/encryption_spec.rb' - - 'spec/mongoid/validatable/associated_spec.rb' # Offense count: 26 RSpec/SubjectDeclaration: @@ -668,7 +741,7 @@ RSpec/SubjectDeclaration: - 'spec/mongoid/collection_configurable_spec.rb' - 'spec/mongoid/contextual/mongo/documents_loader_spec.rb' -# Offense count: 27 +# Offense count: 23 # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. RSpec/VerifiedDoubles: Exclude: @@ -684,10 +757,10 @@ RSpec/VerifiedDoubles: - 'spec/mongoid/persistence_context_spec.rb' - 'spec/mongoid/tasks/database_spec.rb' - 'spec/mongoid/threaded_spec.rb' - - 'spec/mongoid/validatable/associated_spec.rb' - 'spec/rails/mongoid_spec.rb' # Offense count: 11 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowToTime. # SupportedStyles: strict, flexible Rails/Date: @@ -718,6 +791,19 @@ Rails/Delegate: - 'lib/mongoid/findable.rb' - 'lib/mongoid/scopable.rb' +# Offense count: 11 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedMethods, AllowedPatterns. +# AllowedMethods: order, limit, select, lock +Rails/FindEach: + Exclude: + - 'spec/mongoid/association/referenced/belongs_to/eager_spec.rb' + - 'spec/mongoid/association/referenced/has_and_belongs_to_many/eager_spec.rb' + - 'spec/mongoid/association/referenced/has_many/eager_spec.rb' + - 'spec/mongoid/association/referenced/has_many/proxy_spec.rb' + - 'spec/mongoid/association/referenced/has_one/eager_spec.rb' + - 'spec/mongoid/criteria_projection_spec.rb' + # Offense count: 91 # Configuration parameters: Include. # Include: spec/**/*.rb, test/**/*.rb @@ -737,6 +823,24 @@ Rails/I18nLocaleAssignment: - 'spec/mongoid/validatable/uniqueness_spec.rb' - 'spec/support/macros.rb' +# Offense count: 28 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedReceivers. +# AllowedReceivers: ActionMailer::Preview, ActiveSupport::TimeZone +Rails/RedundantActiveRecordAllMethod: + Exclude: + - 'spec/integration/criteria/time_with_zone_spec.rb' + - 'spec/mongoid/association/referenced/belongs_to/eager_spec.rb' + - 'spec/mongoid/association/referenced/has_and_belongs_to_many/eager_spec.rb' + - 'spec/mongoid/association/referenced/has_many/eager_spec.rb' + - 'spec/mongoid/association/referenced/has_one/eager_spec.rb' + - 'spec/mongoid/contextual/aggregable/memory_spec.rb' + - 'spec/mongoid/contextual/memory_spec.rb' + - 'spec/mongoid/criteria/includable_spec.rb' + - 'spec/mongoid/criteria/queryable/selectable_logical_spec.rb' + - 'spec/mongoid/criteria_spec.rb' + - 'spec/mongoid/serializable_spec.rb' + # Offense count: 133 # Configuration parameters: ForbiddenMethods, AllowedMethods. # ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all @@ -765,7 +869,7 @@ Rails/SkipsModelValidations: - 'spec/mongoid/touchable_spec.rb' - 'spec/support/models/server.rb' -# Offense count: 151 +# Offense count: 134 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: strict, flexible @@ -775,7 +879,6 @@ Rails/TimeZone: - 'lib/mongoid/extensions/date.rb' - 'lib/mongoid/extensions/string.rb' - 'lib/mongoid/extensions/time.rb' - - 'lib/mongoid/timestamps/updated.rb' - 'spec/integration/criteria/date_field_spec.rb' - 'spec/integration/criteria/raw_value_spec.rb' - 'spec/integration/persistence/range_field_spec.rb' @@ -804,7 +907,15 @@ Rails/TimeZone: - 'spec/mongoid/touchable_spec.rb' - 'spec/support/models/post.rb' -# Offense count: 4 +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: exists, where +Rails/WhereExists: + Exclude: + - 'lib/mongoid/association/referenced/has_many/enumerable.rb' + +# Offense count: 2 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowOnlyRestArgument, UseAnonymousForwarding, RedundantRestArgumentNames, RedundantKeywordRestArgumentNames, RedundantBlockArgumentNames. # RedundantRestArgumentNames: args, arguments @@ -812,7 +923,6 @@ Rails/TimeZone: # RedundantBlockArgumentNames: blk, block, proc Style/ArgumentsForwarding: Exclude: - - 'lib/mongoid/association/referenced/has_many/enumerable.rb' - 'lib/mongoid/association/referenced/has_many/proxy.rb' # Offense count: 5 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..477f3506b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,15 @@ +# Conduct of Code + +1. "I fight for the Users." - Tron + +2. "Talk is cheap. Show me the code." - Linus Torvalds + +3. "Everything should be made as simple as possible, but not simpler." - Albert Einstein + +4. "The most dangerous phrase in the language is: We've always done it this way." - Grace Hopper + +5. "A feeling of aversion or attachment toward something is your clue that there's work to be done." - Ram Dass + +6. "I'm smart enough to know that I'm dumb." - Richard Feynman + +7. "Be excellent to each other." - Abraham Lincoln diff --git a/docs/index.txt b/docs/index.txt index 76660e556..e7fed377a 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -21,6 +21,3 @@ for MongoDB in Ruby. contributing additional-resources ecosystem - - -For documentation on Mongoid 3 and 4, see ``_. diff --git a/docs/installation.txt b/docs/installation.txt index 7247273d6..44ffdaba6 100644 --- a/docs/installation.txt +++ b/docs/installation.txt @@ -34,7 +34,7 @@ Using Mongoid with a New Rails Application When creating a new Rails application and wish to use Mongoid for data access, give the ``--skip-active-record`` flag to the ``rails new`` -command to avoid depending on and configuring ActiveRecord.. +command to avoid depending on and configuring ActiveRecord. Using Mongoid with an Existing Rails Application ================================================ diff --git a/docs/reference/compatibility.txt b/docs/reference/compatibility.txt index 7f616c315..79299b23e 100644 --- a/docs/reference/compatibility.txt +++ b/docs/reference/compatibility.txt @@ -34,52 +34,17 @@ specified Mongoid versions. - Driver 2.17-2.10 - Driver 2.9-2.7 - * - 9.0 + * - 8.0 thru 9.0 - |checkmark| - - - * - 8.1 + * - 7.2 thru 7.5 - |checkmark| - - - - - - * - 8.0 - |checkmark| - - - - * - 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| @@ -107,9 +72,9 @@ is deprecated. - Ruby 2.4 - Ruby 2.3 - Ruby 2.2 - - JRuby 9.2 - - JRuby 9.3 - JRuby 9.4 + - JRuby 9.3 + - JRuby 9.2 * - 9.0 - |checkmark| @@ -121,9 +86,9 @@ is deprecated. - - - + - |checkmark| - - - - |checkmark| * - 8.1 - |checkmark| @@ -139,7 +104,6 @@ is deprecated. - |checkmark| - - * - 8.0 - - |checkmark| @@ -164,9 +128,9 @@ is deprecated. - - - - - D - - |checkmark| - + - |checkmark| + - D * - 7.4 - @@ -178,9 +142,9 @@ is deprecated. - - - - - |checkmark| - - + - |checkmark| * - 7.3 - @@ -192,9 +156,9 @@ is deprecated. - D - D - - - |checkmark| - - + - |checkmark| * - 7.2 - @@ -206,9 +170,9 @@ is deprecated. - D - D - - - |checkmark| - - + - |checkmark| * - 7.1 - @@ -220,9 +184,9 @@ is deprecated. - |checkmark| [#ruby-2.4]_ - |checkmark| - - - |checkmark| - - + - |checkmark| * - 7.0 - @@ -234,23 +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. @@ -296,33 +246,7 @@ and will be removed in a next version. - MongoDB 3.0 - MongoDB 2.6 - * - 9.0 - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - - - - - * - 8.1 - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - - - - - * - 8.0 + * - 8.0 thru 9.0 - |checkmark| - |checkmark| - |checkmark| @@ -335,7 +259,7 @@ and will be removed in a next version. - - - * - 7.5 + * - 7.4 thru 7.5 - |checkmark| - |checkmark| - |checkmark| @@ -348,72 +272,7 @@ and will be removed in a next version. - D - D - * - 7.4 - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |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 - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - D - - D - - D - - D - - * - 7.0 - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - D - - D - - D - - D - - * - 6.4 + * - 7.0 thru 7.3 - - - |checkmark| @@ -456,15 +315,7 @@ are supported by Mongoid. - - - * - 8.1 - - |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| @@ -520,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. diff --git a/docs/reference/configuration.txt b/docs/reference/configuration.txt index 0014cb452..a66174de7 100644 --- a/docs/reference/configuration.txt +++ b/docs/reference/configuration.txt @@ -396,7 +396,7 @@ for details on driver options. 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. + # collection. When this flag is on, the view filter is ignored. # broken_view_aggregate: true # When this flag is set to false, the view options will be correctly @@ -404,7 +404,7 @@ for details on driver options. # 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 the parameters and raise an error if they are invalid. # validate_update_replace: false @@ -541,9 +541,8 @@ Place the following in one of environment configuration files, such as .. note:: - The ``log_level`` Mongoid `configuration option `_ - is not used when Mongoid operates in a Rails application, because Mongoid - inherits Rails' log level in this case. + The ``log_level`` Mongoid configuration option is not used when Mongoid operates + in a Rails application, because Mongoid inherits Rails' log level in this case. To configure either Mongoid or driver logger differently from the Rails logger, use an initializer as follows: @@ -590,8 +589,8 @@ Standalone ---------- When not loaded in a Ruby on Rails application, Mongoid respects the -``log_level`` top level `configuration option `_. -It can be given in the configuration file as follows: +``log_level`` top level configuration option. It can be given in the +configuration file as follows: .. code-block:: yaml @@ -746,11 +745,11 @@ It may be desirable to further configure TLS options in your application, for example by enabling or disabling certain ciphers. This can be done by setting TLS context hooks on the Ruby driver -- TLS context -hooks are user-provided ``Proc``s that will be invoked before any TLS socket +hooks are user-provided ``Proc``\(s) that will be invoked before any TLS socket connection in the driver and can be used to modify the underlying ``OpenSSL::SSL::SSLContext`` object used by the socket. -To set TLS context hooks, add ``Proc``s to the ``Mongo.tls_context_hooks`` +To set TLS context hooks, add ``Proc``\(s) to the ``Mongo.tls_context_hooks`` array. This can be done in an initializer. The example below adds a hook that only enables the "AES256-SHA" cipher. @@ -830,7 +829,7 @@ Client-Side Encryption When loading the configuration file, Mongoid permits the file to contain ``BSON::Binary`` instances which are used for specifying ``keyId`` in the schema map for `client-side encryption -`_, +`_, as the following example shows: .. code-block:: yaml @@ -864,58 +863,57 @@ as the following example shows: Usage with Forking Servers ========================== -When using Mongoid with a forking web server such as Puma, Unicorn or -Passenger, it is recommended to not perform any operations on Mongoid models -in the parent process prior to the fork. +When using Mongoid with a forking web server such as Puma, or any application +that otherwise forks to spawn child processes, special considerations apply. -When a process forks, Ruby threads are not transferred to the child processes -and the Ruby driver Client objects lose their background monitoring. The -application will typically seem to work just fine until the deployment -state changes (for example due to network errors, a maintenance event) at -which point the application is likely to start getting ``NoServerAvailable`` -exception when performing MongoDB operations. +If possible, we recommend to not perform any MongoDB operations in the parent +process prior to forking, which will avoid any forking-related pitfalls. -If the parent process needs to perform operations on the MongoDB database, -reset all clients in the workers after they forked. How to do so depends -on the web server being used. +A detailed technical explanation of how the Mongo Ruby Driver handles forking +is given in the `driver's "Usage with Forking Servers" documentation +`. +In a nutshell, to avoid various connection errors such as ``Mongo::Error::SocketError`` +and ``Mongo::Error::NoServerAvailable``, you must do the following: -If the parent process does not need to perform operations on the MongoDB -database after child processes are forked, close the clients in the parent -prior to forking children. If the parent process performs operations on a Mongo -client and does not close it, the parent process will continue consuming a -connection slot in the cluster and will continue monitoring the cluster for -as long as the parent remains alive. +1. Disconnect MongoDB clients in the parent Ruby process immediately *before* + forking using ``Mongoid.disconnect_clients``. This ensures the parent and child + process do not accidentally reuse the same sockets and have I/O conflicts. + Note that ``Mongoid.disconnect_clients`` does not disrupt any in-flight + MongoDB operations, and will automatically reconnect when you perform new + operations. +2. Reconnect your MongoDB clients in the child Ruby process immediately *after* + forking using ``Mongoid.reconnect_clients``. This is required to respawn + the driver's monitoring threads in the child process. -.. note:: - - The close/reconnect pattern described here should be used with Ruby driver - version 2.6.2 or higher. Previous driver versions did not recreate - monitoring threads when reconnecting. +Most web servers provide hooks that can be used by applications to +perform actions when the worker processes are forked. The following +are configuration examples for several common Ruby web servers. Puma ---- Use the ``on_worker_boot`` hook to reconnect clients in the workers and -the ``before_fork`` hook to close clients in the parent process -(`Puma documentation `_): +the ``before_fork`` and ``on_refork`` hooks to close clients in the +parent process (`Puma documentation `_). .. code-block:: ruby - on_worker_boot do - if defined?(Mongoid) - Mongoid::Clients.clients.each do |name, client| - client.close - client.reconnect - end - else - raise "Mongoid is not loaded. You may have forgotten to enable app preloading." - end - end + # config/puma.rb + # Runs in the Puma master process before it forks a child worker. before_fork do - if defined?(Mongoid) - Mongoid.disconnect_clients - end + Mongoid.disconnect_clients + end + + # Required when using Puma's fork_worker option. Runs in the + # child worker 0 process before it forks grandchild workers. + on_refork do + Mongoid.disconnect_clients + end + + # Runs in each Puma child process after it forks from its parent. + on_worker_boot do + Mongoid.reconnect_clients end Unicorn @@ -927,21 +925,14 @@ the ``before_fork`` hook to close clients in the parent process .. code-block:: ruby - after_fork do |server, worker| - if defined?(Mongoid) - Mongoid::Clients.clients.each do |name, client| - client.close - client.reconnect - end - else - raise "Mongoid is not loaded. You may have forgotten to enable app preloading." - end + # config/unicorn.rb + + before_fork do |_server, _worker| + Mongoid.disconnect_clients end - before_fork do |server, worker| - if defined?(Mongoid) - Mongoid.disconnect_clients - end + after_fork do |_server, _worker| + Mongoid.reconnect_clients end Passenger @@ -957,12 +948,7 @@ before the workers are forked. if defined?(PhusionPassenger) PhusionPassenger.on_event(:starting_worker_process) do |forked| - if forked - Mongoid::Clients.clients.each do |name, client| - client.close - client.reconnect - end - end + Mongoid.reconnect_clients if forked end end diff --git a/docs/reference/fields.txt b/docs/reference/fields.txt index 6687a6a42..5e29bfeec 100644 --- a/docs/reference/fields.txt +++ b/docs/reference/fields.txt @@ -927,7 +927,7 @@ Reading Uncastable Values ````````````````````````` When documents in the database contain values of different types than their -represenations in Mongoid, if Mongoid cannot coerce them into the correct type, +representations in Mongoid, if Mongoid cannot coerce them into the correct type, it will replace the value with ``nil``. Consider the following model and document in the database: @@ -1105,7 +1105,7 @@ used for MongoDB serialization and deserialization as follows: end The instance method ``mongoize`` takes an instance of your custom type object, and -converts it into a represenation of how it will be stored in the database, i.e. to pass +converts it into a representation of how it will be stored in the database, i.e. to pass to the MongoDB Ruby driver. In our example above, we want to store our ``Point`` object as an ``Array`` in the form ``[ x, y ]``. @@ -1228,7 +1228,7 @@ which extend its behavior at the your time model classes are loaded. As an example, we will define a ``:max_length`` option which will add a length validator for the field. First, declare the new field option in an initializer, -specifiying its handler function as a block: +specifying its handler function as a block: .. code-block:: ruby diff --git a/docs/reference/queries.txt b/docs/reference/queries.txt index 4949045d4..746cea97a 100644 --- a/docs/reference/queries.txt +++ b/docs/reference/queries.txt @@ -1160,7 +1160,7 @@ examples. If any of the ``_id`` values are not found in the database, the behavior of ``find`` depends on the value of the ``raise_not_found_error`` configuration option. If the option is set to ``true``, ``find`` raises -``Mongoid::Errors::DocumentNotFound`` if any of the ``_id``s are not found. +``Mongoid::Errors::DocumentNotFound`` if any of the ``_id``\s are not found. If the option is set to ``false`` and ``find`` is given a single ``_id`` to find and there is no matching document, ``find`` returns ``nil``. If the option is set to ``false`` and ``find`` is given an array of ids to find diff --git a/docs/reference/rails-integration.txt b/docs/reference/rails-integration.txt index 4c7181cff..2f8f02ace 100644 --- a/docs/reference/rails-integration.txt +++ b/docs/reference/rails-integration.txt @@ -24,7 +24,7 @@ other Rails environment specific options by accessing config.mongoid. The ``mongoid:config`` generator will create an initializer in ``config/initializers/mongoid.rb`` which can also be used for configuring Mongoid. Note, though, that options set in your ``config/mongoid.yml`` will -take precendence over options set elsewhere; it is recommended that whenever +take precedence over options set elsewhere; it is recommended that whenever possible you use ``mongoid.yml`` as the default location for Mongoid configuration. diff --git a/docs/reference/sharding.txt b/docs/reference/sharding.txt index ab451bf4a..675e62cae 100644 --- a/docs/reference/sharding.txt +++ b/docs/reference/sharding.txt @@ -35,8 +35,8 @@ Shard keys can be declared on models using the ``shard_key`` macro: end Note that in order to shard a collection, the collection must have an index -that starts with the shard key. Mongoid provides `index management -`_ functionality, which the examples here take +that starts with the shard key. Mongoid provides :ref:`index management +` functionality, which the examples here take advantage of. Mongoid supports two syntaxes for declaring shard keys. The standard syntax diff --git a/docs/reference/text-search.txt b/docs/reference/text-search.txt index c962b3667..b7c3013de 100644 --- a/docs/reference/text-search.txt +++ b/docs/reference/text-search.txt @@ -35,8 +35,8 @@ To perform text search with Mongoid, follow these steps: Defining Text Search Index -------------------------- -Index definition through Mongoid is described in detail on the `indexes -`_ page. Text search indexes are described in detail +Index definition through Mongoid is described in detail on the :ref:`indexes +` page. Text search indexes are described in detail under `text indexes `_ in the MongoDB manual. Below is an example definition of a Band model with a text index utilizing the description field: diff --git a/docs/release-notes/mongoid-8.0.txt b/docs/release-notes/mongoid-8.0.txt index 69e17c6d9..b0d93132c 100644 --- a/docs/release-notes/mongoid-8.0.txt +++ b/docs/release-notes/mongoid-8.0.txt @@ -84,59 +84,69 @@ evolve an uncastable value, the inputted value is returned. See the section on Some ``mongoize``, ``demongoize`` and ``evolve`` methods were also changed to perform consistently with rails and the other ``mongoize``, ``demongoize`` and -``evolve`` methods. The following is a table of the changes in functionality: - -+--------------+------------------------+------------------------+-----------------------+ -| Field Type | Situation | Previous Functionality | New Functionality | -+==============+========================+========================+=======================+ -| Boolean | When a non-boolean | return ``false`` | return ``nil`` | -| | string is assigned: | | | -| | "bogus value" | | | -+--------------+------------------------+------------------------+-----------------------+ -| Array/Hash | When a value that is | raise ``InvalidValue`` | return ``nil`` | -| | not an array or hash | error | | -| | is assigned | | | -+--------------+------------------------+------------------------+-----------------------+ -| Set | When a value that is | raise ``NoMethodError``| return ``nil`` | -| | not a set is assigned: | Exception: undefined | | -| | 1 | method ``to_a`` for | | -| | | 1:Integer | | -+--------------+------------------------+------------------------+-----------------------+ -| Regexp | When persisting and | return a | return a | -| | reading a Regexp from | ``BSON::Regexp::Raw`` | ``Regexp`` | -| | the database | | | -+--------------+------------------------+------------------------+-----------------------+ -| Time/DateTime| When assigning a | raise ``NoMethodError``| return ``nil`` | -| | bogus value: ``:bogus``| Exception: undefined | | -| | | method ``to_i`` | | -| | | for :bogus:Symbol | | -+--------------+------------------------+------------------------+-----------------------+ -| Time/DateTime| When demongoizing a | raise ``NoMethodError``| "bogus": | -| | non-Time value: | Exception: undefined | return ``nil`` | -| | "bogus", | method ``getlocal`` | | -| | ``Date.today`` | for "bogus":String | ``Date.today``: | -| | | | return a | -| | | | ``Time/DateTime`` | -+--------------+------------------------+------------------------+-----------------------+ -| Date | When assigning or | raise ``NoMethodError``| return ``nil`` | -| | demongoizing a bogus | Exception: undefined | | -| | value: :bogus | method ``year`` | | -| | | for :bogus:Symbol | | -+--------------+------------------------+------------------------+-----------------------+ -| Time/DateTime| When demongoizing a | raise ``NoMethodError``| return a | -| /Date | valid string: | Exception: undefined | ``Time/DateTime/Date``| -| | "2022-07-14 14:45:51 | method ``getlocal`` | | -| | -0400" | for "2022-07-14 | | -| | | 14:45:51 -0400":String | | -+--------------+------------------------+------------------------+-----------------------+ -| All Types | When an uncastable | undefined behavior, | return ``nil`` | -| | value is assigned or | occasionally raise | | -| | demongoized | ``NoMethodError`` | | -+--------------+------------------------+------------------------+-----------------------+ -| All Types | When an uncastable | undefined behavior, | return inputted value | -| | value is evolved | occasionally raise | | -| | | ``NoMethodError`` | | -+--------------+------------------------+------------------------+-----------------------+ +``evolve`` methods. The following table shows the changes in functionality: + +.. list-table:: + :widths: 1 2 2 2 + :stub-columns: 1 + :header-rows: 1 + + * - Field Type + - Situation + - Previous Functionality + - New Functionality + + * - | Boolean + - | When a non-boolean string is assigned: "bogus value" + - | return ``false`` + - | return ``nil`` + + * - Array/Hash + - When a value that is not an array or hash is assigned + - raise ``InvalidValue`` error + - return ``nil`` + + * - | Set + - | When a value that is not a set is assigned: 1 + - | raise ``NoMethodError`` Exception: undefined method ``to_a`` for 1:Integer + - | return ``nil`` + + * - Regexp + - When persisting and reading a Regexp from the database + - return a ``BSON::Regexp::Raw`` + - return a ``Regexp`` + + * - | Time/DateTime + - | When assigning a bogus value: ``:bogus`` + - | raise ``NoMethodError`` Exception: undefined method ``to_i`` for :bogus:Symbol + - | return ``nil`` + + * - Time/DateTime + - When demongoizing a non-Time value: "bogus", ``Date.today`` + - raise ``NoMethodError`` Exception: undefined method ``getlocal`` for "bogus":String + - "bogus": return ``nil`` + + ``Date.today``: return ``Time/DateTime`` + + * - | Date + - | When assigning or demongoizing a bogus value: :bogus + - | raise ``NoMethodError`` Exception: undefined method ``year`` for :bogus:Symbol + - | return ``nil`` + + * - Time/DateTime/Date + - When demongoizing a valid string: "2022-07-14 14:45:51 -0400" + - raise ``NoMethodError`` Exception: undefined method ``getlocal`` for "2022-07-14 14:45:51 -0400":String + - return a ``Time/DateTime/Date`` + + * - | All Types + - | When an uncastable value is assigned or demongoized + - | undefined behavior, occasionally raise ``NoMethodError`` + - | return ``nil`` + + * - All Types + - When an uncastable value is evolved + - undefined behavior, occasionally raise ``NoMethodError`` + - return inputted value .. note:: @@ -145,7 +155,6 @@ perform consistently with rails and the other ``mongoize``, ``demongoize`` and https://jira.mongodb.org/browse/MONGOID-2951 for a longer discussion on these bugs. - Changes to the ``attributes_before_type_cast`` Hash --------------------------------------------------- @@ -177,98 +186,144 @@ invocation for documents with associations. Referenced associations (``has_one`` and ``has_many``): -+---------------------------------------+---------------------------------------+ -| Mongoid 8.0 | Mongoid 7 | -+=======================================+=======================================+ -| Parent :before_save | Parent :before_save | -+---------------------------------------+---------------------------------------+ -| Parent :around_save_open | Parent :around_save_open | -+---------------------------------------+---------------------------------------+ -| Parent :before_create | Parent :before_create | -+---------------------------------------+---------------------------------------+ -| Parent :around_create_open | Parent :around_create_open | -+---------------------------------------+---------------------------------------+ -| **Parent persisted in MongoDB** | **Parent persisted in MongoDB** | -+---------------------------------------+---------------------------------------+ -| Child :before_save | Parent :around_create_close | -+---------------------------------------+---------------------------------------+ -| Child :around_save_open | Parent :after_create | -+---------------------------------------+---------------------------------------+ -| Child :before_create | Child :before_save | -+---------------------------------------+---------------------------------------+ -| Child :around_create_open | Child :around_save_open | -+---------------------------------------+---------------------------------------+ -| | Child :before_create | -+---------------------------------------+---------------------------------------+ -| | Child :around_create_open | -+---------------------------------------+---------------------------------------+ -| **Child persisted in MongoDB** | **Child persisted in MongoDB** | -+---------------------------------------+---------------------------------------+ -| Child :around_create_close | Child :around_create_close | -+---------------------------------------+---------------------------------------+ -| Child :after_create | Child :after_create | -+---------------------------------------+---------------------------------------+ -| Child :around_save_close | Child :around_save_close | -+---------------------------------------+---------------------------------------+ -| Child :after_save | Child :after_save | -+---------------------------------------+---------------------------------------+ -| Parent :around_create_close | Parent :around_save_close | -+---------------------------------------+---------------------------------------+ -| Parent :after_create | Parent :after_save | -+---------------------------------------+---------------------------------------+ -| Parent :around_save_close | | -+---------------------------------------+---------------------------------------+ -| Parent :after_save | | -+---------------------------------------+---------------------------------------+ +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Mongoid 8.0 + - Mongoid 7 + + * - | Parent :before_save + - | Parent :before_save + + * - Parent :around_save_open + - Parent :around_save_open + + * - | Parent :before_create + - | Parent :before_create + + * - Parent :around_create_open + - Parent :around_create_open + + * - | **Parent persisted in MongoDB** + - | **Parent persisted in MongoDB** + + * - Child :before_save + - Parent :around_create_close + + * - | Child :around_save_open + - | Parent :after_create + + * - Child :before_create + - Child :before_save + + * - | Child :around_create_open + - | Child :around_save_open + + * - + - Child :before_create + + * - | + - | Child :around_create_open + + * - **Child persisted in MongoDB** + - **Child persisted in MongoDB** + + * - | Child :around_create_close + - | Child :around_create_close + + * - Child :after_create + - Child :after_create + + * - | Child :around_save_close + - | Child :around_save_close + + * - Child :after_save + - Child :after_save + + * - | Parent :around_create_close + - | Parent :around_save_close + + * - Parent :after_create + - Parent :after_save + + * - | Parent :around_save_close + - | + + * - Parent :after_save + - Embedded associations (``embeds_one`` and ``embeds_many``): -+---------------------------------------+---------------------------------------+ -| Mongoid 8.0 | Mongoid 7 | -+=======================================+=======================================+ -| Parent :before_save | Child :before_save | -+---------------------------------------+---------------------------------------+ -| Parent :around_save_open | Child :around_save_open | -+---------------------------------------+---------------------------------------+ -| Parent :before_create | Child :around_save_close | -+---------------------------------------+---------------------------------------+ -| Parent :around_create_open | Child :after_save | -+---------------------------------------+---------------------------------------+ -| Child :before_save | Parent :before_save | -+---------------------------------------+---------------------------------------+ -| Child :around_save_open | Parent :around_save_open | -+---------------------------------------+---------------------------------------+ -| Child :before_create | Child :before_create | -+---------------------------------------+---------------------------------------+ -| Child :around_create_open | Child :around_create_open | -+---------------------------------------+---------------------------------------+ -| | Child :around_create_close | -+---------------------------------------+---------------------------------------+ -| | Child :after_create | -+---------------------------------------+---------------------------------------+ -| | Parent :before_create | -+---------------------------------------+---------------------------------------+ -| | Parent :around_create_open | -+---------------------------------------+---------------------------------------+ -| **Document persisted in MongoDB** | **Document persisted in MongoDB** | -+---------------------------------------+---------------------------------------+ -| Child :around_create_close | | -+---------------------------------------+---------------------------------------+ -| Child :after_create | | -+---------------------------------------+---------------------------------------+ -| Child :around_save_close | | -+---------------------------------------+---------------------------------------+ -| Child :after_save | | -+---------------------------------------+---------------------------------------+ -| Parent :around_create_close | Parent :around_create_close | -+---------------------------------------+---------------------------------------+ -| Parent :after_create | Parent :after_create | -+---------------------------------------+---------------------------------------+ -| Parent :around_save_close | Parent :around_save_close | -+---------------------------------------+---------------------------------------+ -| Parent :after_save | Parent :after_save | -+---------------------------------------+---------------------------------------+ +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Mongoid 8.0 + - Mongoid 7 + + * - | Parent :before_save + - | Child :before_save + + * - Parent :around_save_open + - Child :around_save_open + + * - | Parent :before_create + - | Child :around_save_close + + * - Parent :around_create_open + - Child :after_save + + * - | Child :before_save + - | Parent :before_save + + * - Child :around_save_open + - Parent :around_save_open + + * - | Child :before_create + - | Child :before_create + + * - Child :around_create_open + - Child :around_create_open + + * - | + - | Child :around_create_close + + * - + - Child :after_create + + * - | + - | Parent :before_create + + * - + - Parent :around_create_open + + * - | **Document persisted in MongoDB** + - | **Document persisted in MongoDB** + + * - Child :around_create_close + - + + * - | Child :after_create + - | + + * - Child :around_save_close + - + + * - | Child :after_save + - | + + * - Parent :around_create_close + - Parent :around_create_close + + * - | Parent :after_create + - | Parent :after_create + + * - Parent :around_save_close + - Parent :around_save_close + * - | Parent :after_save + - | Parent :after_save ``Changeable`` Module Behavior Made Compatible With ``ActiveModel::Dirty`` -------------------------------------------------------------------------- diff --git a/docs/release-notes/mongoid-9.0.txt b/docs/release-notes/mongoid-9.0.txt index f92cee00a..a69f572e9 100644 --- a/docs/release-notes/mongoid-9.0.txt +++ b/docs/release-notes/mongoid-9.0.txt @@ -43,19 +43,6 @@ Consider using `MongoDB Atlas `_ to automate your MongoDB server upgrades. -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 ------------------------------------------------------------------ @@ -73,10 +60,11 @@ If you want to restore the old behavior, you can set ``Mongoid.around_embedded_document_callbacks`` to true in your application. .. note:: - Enabling ``around_*`` callbacks for embedded documents is not recommended - as it may cause ``SystemStackError`` exceptions when a document has many - embedded documents. See `MONGOID-5658 `_ - for more details. + + Enabling ``around_*`` callbacks for embedded documents is not recommended + as it may cause ``SystemStackError`` exceptions when a document has many + embedded documents. See `MONGOID-5658 `_ + for more details. ``for_js`` method is deprecated @@ -114,15 +102,16 @@ prior has been dropped (you must use a minimum of version 8.0.) Deprecated functionality removed -------------------------------- -The following previously deprecated functionality is now removed: +**Breaking change:** The following deprecated functionality is now removed: - The ``Mongoid::QueryCache`` module has been removed. Please replace any usages 1-for-1 with ``Mongo::QueryCache``. The method ``Mongoid::QueryCache#clear_cache`` should be replaced with ``Mongo::QueryCache#clear``. All other methods and submodules are identically named. Refer to the `driver query cache documentation `_ for more details. - ``Object#blank_criteria?`` method is removed (was previously deprecated.) -- ``Document#as_json :compact`` option is removed. Please call ```#compact`` - on the returned ``Hash`` object instead. +- ``Document#as_json :compact`` option is removed. Please call ```#compact`` on the + returned ``Hash`` object instead. +- The deprecated class ``Mongoid::Errors::InvalidStorageParent`` has been removed. - ``Criteria#geo_near`` is removed as MongoDB server versions 4.2 and later no longer support the ``$geoNear`` command. Please use the `$geoNear stage of the aggregation pipeline @@ -174,22 +163,22 @@ In Mongoid 8.x and older ``touch`` method leaves models in the changed state: .. code-block:: ruby - # Mongoid 8.x behaviour - band = Band.create! - band.touch - band.changed? # => true - band.changes # => {"updated_at"=>[2023-01-30 13:12:57.477191135 UTC, 2023-01-30 13:13:11.482975646 UTC]} + # Mongoid 8.x behaviour + band = Band.create! + band.touch + band.changed? # => true + band.changes # => {"updated_at"=>[2023-01-30 13:12:57.477191135 UTC, 2023-01-30 13:13:11.482975646 UTC]} Starting from 9.0 Mongoid now correctly clears changed state after using ``touch`` method. .. code-block:: ruby - # Mongoid 9.0 behaviour - band = Band.create! - band.touch - band.changed? # => false - band.changes # => {} + # Mongoid 9.0 behaviour + band = Band.create! + band.touch + band.changed? # => false + band.changes # => {} Sandbox Mode for Rails Console ------------------------------ @@ -201,8 +190,9 @@ the commands executed in the console using the ``:default`` client won't be persisted in the database. .. note:: - If you execute commands in the sandbox mode *using any other client than default*, - these changes will be persisted as usual. + + If you execute commands in the sandbox mode *using any other client than default*, + these changes will be persisted as usual. New Transactions API -------------------- @@ -211,15 +201,15 @@ Mongoid 9.0 introduces new transactions API that is inspired by ActiveRecord: .. code-block:: ruby - Band.transaction do - Band.create(title: 'Led Zeppelin') - end + Band.transaction do + Band.create(title: 'Led Zeppelin') + end - band = Band.create(title: 'Deep Purple') - band.transaction do - band.active = false - band.save! - end + band = Band.create(title: 'Deep Purple') + band.transaction do + band.active = false + band.save! + end Please consult :ref:`transactions documentation ` for more details. @@ -260,11 +250,11 @@ when the instance was loaded, Mongoid will now raise a .. code-block:: ruby - Band.only(:name).first.label - #=> raises Mongoid::Errors::AttributeNotLoaded + Band.only(:name).first.label + #=> raises Mongoid::Errors::AttributeNotLoaded - Band.without(:label).first.label = 'Sub Pop Records' - #=> raises Mongoid::Errors::AttributeNotLoaded + Band.without(:label).first.label = 'Sub Pop Records' + #=> raises Mongoid::Errors::AttributeNotLoaded In earlier Mongoid versions, the same conditions would raise an ``ActiveModel::MissingAttributeError``. Please check your code for @@ -282,17 +272,17 @@ considers ``Time.zone`` to perform type conversion. .. code-block:: ruby - class Magazine - include Mongoid::Document + class Magazine + include Mongoid::Document - field :published_at, type: Time - end + field :published_at, type: Time + end - Time.zone = 'Asia/Tokyo' + Time.zone = 'Asia/Tokyo' - Magazine.gte(published_at: Date.parse('2022-09-26')) - #=> will return all results on or after Sept 26th, 2022 - # at 0:00 in Asia/Tokyo time zone. + Magazine.gte(published_at: Date.parse('2022-09-26')) + #=> will return all results on or after Sept 26th, 2022 + # at 0:00 in Asia/Tokyo time zone. In prior Mongoid versions, the above code would ignore the ``Time.zone`` (irrespective of the now-removed ``:use_activesupport_time_zone`` @@ -311,25 +301,25 @@ invoke ``#touch`` on its parent document. .. code-block:: ruby - class Address - include Mongoid::Document - include Mongoid::Timestamps + class Address + include Mongoid::Document + include Mongoid::Timestamps - embedded_in :mall, touch: false - end + embedded_in :mall, touch: false + end - class Mall - include Mongoid::Document - include Mongoid::Timestamps + class Mall + include Mongoid::Document + include Mongoid::Timestamps - embeds_many :addresses - end + embeds_many :addresses + end - mall = Mall.create! - address = mall.addresses.create! + mall = Mall.create! + address = mall.addresses.create! - address.touch - #=> updates address.updated_at but not mall.updated_at + address.touch + #=> updates address.updated_at but not mall.updated_at In addition, the ``#touch`` method has been optimized to perform one persistence operation per parent document, even when using multiple @@ -344,12 +334,12 @@ unless you explicitly set ``touch: false`` on the relation: .. code-block:: ruby - class Address - include Mongoid::Document - include Mongoid::Timestamps + class Address + include Mongoid::Document + include Mongoid::Timestamps - embedded_in :mall, touch: false - end + embedded_in :mall, touch: false + end For all other associations, the default remains ``touch: false``. @@ -381,7 +371,7 @@ defaults to ``true``. .. code-block:: ruby - Mongoid::Config.immutable_ids = true + Mongoid::Config.immutable_ids = true When set to false, the older, inconsistent behavior is restored. @@ -424,18 +414,18 @@ of the ``index`` macro: ``partial_filter_expression``, ``weights``, .. code-block:: ruby - class Person - include Mongoid::Document - field :a, as: :age - index({ age: 1 }, { partial_filter_expression: { age: { '$gte' => 20 } }) - end + class Person + include Mongoid::Document + field :a, as: :age + index({ age: 1 }, { partial_filter_expression: { age: { '$gte' => 20 } }) + end .. note:: - The expansion of field name aliases in index options such as - ``partial_filter_expression`` is performed according to the behavior of MongoDB - server 6.0. Future server versions may change how they interpret these options, - and Mongoid's functionality may not support such changes. + The expansion of field name aliases in index options such as + ``partial_filter_expression`` is performed according to the behavior of MongoDB + server 6.0. Future server versions may change how they interpret these options, + and Mongoid's functionality may not support such changes. BSON 5 and BSON::Decimal128 Fields @@ -447,32 +437,32 @@ declared as BSON::Decimal128 will return a BigDecimal value by default. .. code-block:: ruby - class Model - include Mongoid::Document + class Model + include Mongoid::Document - field :decimal_field, type: BSON::Decimal128 - end + field :decimal_field, type: BSON::Decimal128 + end - # under BSON <= 4 - Model.first.decimal_field.class #=> BSON::Decimal128 + # under BSON <= 4 + Model.first.decimal_field.class #=> BSON::Decimal128 - # under BSON >= 5 - Model.first.decimal_field.class #=> BigDecimal + # under BSON >= 5 + Model.first.decimal_field.class #=> BigDecimal If you need literal BSON::Decimal128 values with BSON 5, you may instruct Mongoid to allow literal BSON::Decimal128 fields: .. code-block:: ruby - Model.first.decimal_field.class #=> BigDecimal + Model.first.decimal_field.class #=> BigDecimal - Mongoid.allow_bson5_decimal128 = true - Model.first.decimal_field.class #=> BSON::Decimal128 + Mongoid.allow_bson5_decimal128 = true + Model.first.decimal_field.class #=> BSON::Decimal128 .. note:: - The ``allow_bson5_decimal128`` option only has any effect under - BSON 5 and later. BSON 4 and earlier ignore the setting entirely. + The ``allow_bson5_decimal128`` option only has any effect under + BSON 5 and later. BSON 4 and earlier ignore the setting entirely. Search Index Management with MongoDB Atlas @@ -484,23 +474,23 @@ API: .. code-block:: ruby - class SearchablePerson - include Mongoid::Document + class SearchablePerson + include Mongoid::Document - search_index { ... } # define the search index here - end + search_index { ... } # define the search index here + end - # create the declared search indexes; this returns immediately, but the - # search indexes may take several minutes before they are available. - SearchablePerson.create_search_indexes + # create the declared search indexes; this returns immediately, but the + # search indexes may take several minutes before they are available. + SearchablePerson.create_search_indexes - # query the available search indexes - SearchablePerson.search_indexes.each do |index| - # ... - end + # query the available search indexes + SearchablePerson.search_indexes.each do |index| + # ... + end - # remove all search indexes from the model's collection - SearchablePerson.remove_search_indexes + # remove all search indexes from the model's collection + SearchablePerson.remove_search_indexes If you are not connected to MongoDB Atlas, the search index definitions are ignored. Trying to create, enumerate, or remove search indexes will result in @@ -510,16 +500,16 @@ There are also rake tasks available, for convenience: .. code-block:: bash - # create search indexes for all models; waits for indexes to be created - # and shows progress on the terminal. - $ rake mongoid:db:create_search_indexes + # create search indexes for all models; waits for indexes to be created + # and shows progress on the terminal. + $ rake mongoid:db:create_search_indexes - # as above, but returns immediately and lets the indexes be created in the - # background - $ rake WAIT_FOR_SEARCH_INDEXES=0 mongoid:db:create_search_indexes + # as above, but returns immediately and lets the indexes be created in the + # background + $ rake WAIT_FOR_SEARCH_INDEXES=0 mongoid:db:create_search_indexes - # removes search indexes from all models - $ rake mongoid:db:remove_search_indexes + # removes search indexes from all models + $ rake mongoid:db:remove_search_indexes ``Time.configured`` has been removed @@ -533,33 +523,33 @@ Mongoid now requires that you set a time zone if you intend to do anything with time values (including using timestamps in your documents). Any uses of ``Time.configured`` must be replaced with ``Time.zone``. -... code-block:: ruby +.. code-block:: ruby - # before: - puts Time.configured.now + # before: + puts Time.configured.now - # after: - puts Time.zone.now + # after: + puts Time.zone.now - # or, better for finding the current Time specifically: - puts Time.current + # or, better for finding the current Time specifically: + puts Time.current If you do not set a time zone, you will see errors in your code related to ``nil`` values. If you are using Rails, the default time zone is already set to UTC. If you are not using Rails, you may set a time zone at the start of your program like this: -... code-block:: ruby +.. code-block:: ruby - Time.zone = 'UTC' + Time.zone = 'UTC' This will set the time zone to UTC. You can see all available time zone names by running the following command: -... code-block:: bash +.. 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 @@ -567,10 +557,10 @@ Records now remember the persistence context in which they were loaded/created Consider the following code: -... code-block:: ruby +.. code-block:: ruby - record = Model.with(collection: 'other_collection') { Model.first } - record.update(field: 'value') + 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 @@ -587,9 +577,9 @@ 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 +.. code-block:: ruby - Mongoid.legacy_persistence_context_behavior = true + Mongoid.legacy_persistence_context_behavior = true This flag defaults to false in Mongoid 9. diff --git a/docs/release-notes/upgrading.txt b/docs/release-notes/upgrading.txt index 1ae90c1cf..db27af622 100644 --- a/docs/release-notes/upgrading.txt +++ b/docs/release-notes/upgrading.txt @@ -49,10 +49,10 @@ Before you Upgrade ------------------ - *Test Coverage:* The best way to be sure that your application still works after upgrading -is to have good test coverage before you start the process. + is to have good test coverage before you start the process. - *Upgrade Ruby and Rails:* See `"Upgrading Ruby on Rails" `_ -for more information + for more information Upgrading Mongoid diff --git a/docs/tutorials/automatic-encryption.txt b/docs/tutorials/automatic-encryption.txt index dd7ce2890..4da882595 100644 --- a/docs/tutorials/automatic-encryption.txt +++ b/docs/tutorials/automatic-encryption.txt @@ -64,9 +64,11 @@ This can be done one of two ways. Install the automatic encryption shared library (Ruby driver 2.19+) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you use the Ruby driver version 2.19 and above the automatic encryption -shared library should be installed by following the instructions in the -`MongoDB manual `_. +If you use the Ruby driver version 2.19 and above, the automatic encryption +shared library should be installed by following the instructions on the +:manual:`Automatic Encryption Shared Library for Queryable Encryption +` +page in the Server manual. The steps required are as follows: @@ -253,9 +255,9 @@ Now we can tell Mongoid what should be encrypted: Known Limitations ~~~~~~~~~~~~~~~~~ -* MongoDB CSFLE has some limitations that are described in - `the server documentation. `_ - These limitations also apply to Mongoid. +* MongoDB CSFLE has some limitations that are described on the + :manual:`CSFLE Limitations ` + page in the Server manual. These limitations also apply to Mongoid. * Mongoid does not support encryption of ``embeds_many`` relations. * If you use ``:key_name_field`` option, the field must be encrypted using non-deterministic algorithm. To encrypt your field deterministically, you must diff --git a/gemfiles/driver_master.gemfile b/gemfiles/driver_master.gemfile index b823f21c6..c0a23181d 100644 --- a/gemfiles/driver_master.gemfile +++ b/gemfiles/driver_master.gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' gemspec path: '..' -gem 'bson', git: 'https://github.com/mongodb/bson-ruby', branch: '4-stable' +gem 'bson', git: 'https://github.com/mongodb/bson-ruby' gem 'mongo', git: 'https://github.com/mongodb/mongo-ruby-driver' gem 'actionpack', '>= 6.0' gem 'activemodel', '>= 6.0' diff --git a/gemfiles/driver_stable.gemfile b/gemfiles/driver_stable.gemfile index 6a84330de..53b0fed57 100644 --- a/gemfiles/driver_stable.gemfile +++ b/gemfiles/driver_stable.gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' gemspec path: '..' -gem 'mongo', git: 'https://github.com/mongodb/mongo-ruby-driver', branch: '2.18-stable' +gem 'mongo', git: 'https://github.com/mongodb/mongo-ruby-driver', branch: '2.19-stable' gem 'actionpack', '>= 6.0' gem 'activemodel', '>= 6.0' diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index fed0abd5f..52b45aef8 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -8,11 +8,11 @@ def standard_dependencies end group :development, :test do - gem 'rubocop', '~> 1.60.0' - gem 'rubocop-performance', '~> 1.16.0' - gem 'rubocop-rails', '~> 2.17.4' + gem 'rubocop', '~> 1.63.4' + gem 'rubocop-performance', '~> 1.21.0' + gem 'rubocop-rails', '~> 2.24.1' gem 'rubocop-rake', '~> 0.6.0' - gem 'rubocop-rspec', '~> 2.19.0' + gem 'rubocop-rspec', '~> 2.29.2' end group :test do diff --git a/lib/config/locales/en.yml b/lib/config/locales/en.yml index dcf17ef46..02edd9e83 100644 --- a/lib/config/locales/en.yml +++ b/lib/config/locales/en.yml @@ -710,3 +710,14 @@ en: %{document} when the child '%{relation}' still has documents in it." resolution: "Don't attempt to delete the parent %{document} when it has children, or change the dependent option on the association." + client_session_mismatch: + message: "%{model} used within another client's session" + summary: > + Each model is associated with a client. Each session (transaction) + is also associated with a client. You've attempted to create or + update a model within a session associated with a different client. + This may be a bug in your code. + resolution: > + Either process the %{model} model outside the enclosing + transaction, or open a new transaction for that model before + performing the create/update. diff --git a/lib/mongoid.rb b/lib/mongoid.rb index 45e7261cc..491dbe471 100644 --- a/lib/mongoid.rb +++ b/lib/mongoid.rb @@ -99,6 +99,16 @@ def disconnect_clients Clients.disconnect end + # Reconnect all active clients. + # + # @example Reconnect all active clients. + # Mongoid.reconnect_clients + # + # @return [ true ] True. + def reconnect_clients + Clients.reconnect + end + # Convenience method for getting a named client. # # @example Get a named client. diff --git a/lib/mongoid/association.rb b/lib/mongoid/association.rb index d37757c24..ad1e6b177 100644 --- a/lib/mongoid/association.rb +++ b/lib/mongoid/association.rb @@ -129,10 +129,10 @@ def referenced_one? # @return [ Hash ] The association metadata. def reload_relations relations.each_pair do |name, _meta| - next unless instance_variable_defined?("@_#{name}") && - (_parent.nil? || instance_variable_get("@_#{name}") != _parent) + next unless instance_variable_defined?(:"@_#{name}") && + (_parent.nil? || instance_variable_get(:"@_#{name}") != _parent) - remove_instance_variable("@_#{name}") + remove_instance_variable(:"@_#{name}") end end end diff --git a/lib/mongoid/association/accessors.rb b/lib/mongoid/association/accessors.rb index 309b88d36..e585730b9 100644 --- a/lib/mongoid/association/accessors.rb +++ b/lib/mongoid/association/accessors.rb @@ -67,7 +67,7 @@ def create_relation(object, association, selected_fields = nil) # # @param [ Symbol ] name The name of the association. def reset_relation_criteria(name) - return unless instance_variable_defined?("@_#{name}") + return unless instance_variable_defined?(:"@_#{name}") send(name).reset_unloaded end @@ -83,7 +83,7 @@ def reset_relation_criteria(name) # # @return [ Mongoid::Association::Proxy ] The association. def set_relation(name, relation) - instance_variable_set("@_#{name}", relation) + instance_variable_set(:"@_#{name}", relation) end # Adds the existence check for associations. @@ -129,7 +129,7 @@ def self.define_getter!(association) klass.re_define_method(name) do |reload = false| value = get_relation(name, association, nil, reload) if value.nil? && association.autobuilding? && !without_autobuild? - value = send("build_#{name}") + value = send(:"build_#{name}") end value end @@ -222,7 +222,7 @@ def self.define_builder!(association) attributes, _options = parse_args(*args) document = Factory.build(association.relation_class, attributes) _building do - child = send("#{name}=", document) + child = send(:"#{name}=", document) child.run_callbacks(:build) child end @@ -247,7 +247,7 @@ def self.define_creator!(association) attributes, _options = parse_args(*args) document = Factory.build(association.klass, attributes) doc = _assigning do - send("#{name}=", document) + send(:"#{name}=", document) end doc.save save if new_record? && association.stores_foreign_key? diff --git a/lib/mongoid/association/builders.rb b/lib/mongoid/association/builders.rb index 56354b8d2..4eab3940b 100644 --- a/lib/mongoid/association/builders.rb +++ b/lib/mongoid/association/builders.rb @@ -33,7 +33,7 @@ def self.define_builder!(association) attributes, _options = parse_args(*args) document = Factory.execute_build(association.relation_class, attributes, execute_callbacks: false) _building do - child = send("#{association.name}=", document) + child = send(:"#{association.name}=", document) child.run_pending_callbacks child.run_callbacks(:build) child @@ -57,7 +57,7 @@ def self.define_creator!(association) attributes, _options = parse_args(*args) document = Factory.execute_build(association.relation_class, attributes, execute_callbacks: false) doc = _assigning do - send("#{association.name}=", document) + send(:"#{association.name}=", document) end doc.run_pending_callbacks doc.save diff --git a/lib/mongoid/association/depending.rb b/lib/mongoid/association/depending.rb index d96dd4465..9a9b490db 100644 --- a/lib/mongoid/association/depending.rb +++ b/lib/mongoid/association/depending.rb @@ -90,7 +90,7 @@ def self.validate!(association) def apply_destroy_dependencies! self.class._all_dependents.each do |association| if (dependent = association.try(:dependent)) - send("_dependent_#{dependent}!", association) + send(:"_dependent_#{dependent}!", association) end end end diff --git a/lib/mongoid/association/referenced/counter_cache.rb b/lib/mongoid/association/referenced/counter_cache.rb index af15089df..e5deb481e 100644 --- a/lib/mongoid/association/referenced/counter_cache.rb +++ b/lib/mongoid/association/referenced/counter_cache.rb @@ -103,8 +103,8 @@ def self.define_callbacks!(association) klass.after_update do foreign_key = association.foreign_key - if send("#{foreign_key}_previously_changed?") - original, current = send("#{foreign_key}_previous_change") + if send(:"#{foreign_key}_previously_changed?") + original, current = send(:"#{foreign_key}_previous_change") unless original.nil? association.klass.with(persistence_context.for_child(association.klass)) do |k| diff --git a/lib/mongoid/atomic.rb b/lib/mongoid/atomic.rb index c0cdcfefd..7486a05aa 100644 --- a/lib/mongoid/atomic.rb +++ b/lib/mongoid/atomic.rb @@ -178,11 +178,15 @@ def atomic_position # # @return [ Object ] The associated path. def atomic_paths - @atomic_paths ||= if _association - _association.path(self) - else - Atomic::Paths::Root.new(self) - end + return @atomic_paths if @atomic_paths + + paths = if _association + _association.path(self) + else + Atomic::Paths::Root.new(self) + end + + paths.tap { @atomic_paths = paths unless new_record? } end # Get all the attributes that need to be pulled. diff --git a/lib/mongoid/attributes.rb b/lib/mongoid/attributes.rb index 6e585acce..8ca238611 100644 --- a/lib/mongoid/attributes.rb +++ b/lib/mongoid/attributes.rb @@ -321,15 +321,15 @@ def alias_attribute(name, original) aliased_fields[name.to_s] = original.to_s alias_method name, original - alias_method "#{name}=", "#{original}=" - alias_method "#{name}?", "#{original}?" - alias_method "#{name}_change", "#{original}_change" - alias_method "#{name}_changed?", "#{original}_changed?" - alias_method "reset_#{name}!", "reset_#{original}!" - alias_method "reset_#{name}_to_default!", "reset_#{original}_to_default!" - alias_method "#{name}_was", "#{original}_was" - alias_method "#{name}_will_change!", "#{original}_will_change!" - alias_method "#{name}_before_type_cast", "#{original}_before_type_cast" + alias_method :"#{name}=", :"#{original}=" + alias_method :"#{name}?", :"#{original}?" + alias_method :"#{name}_change", :"#{original}_change" + alias_method :"#{name}_changed?", :"#{original}_changed?" + alias_method :"reset_#{name}!", :"reset_#{original}!" + alias_method :"reset_#{name}_to_default!", :"reset_#{original}_to_default!" + alias_method :"#{name}_was", :"#{original}_was" + alias_method :"#{name}_will_change!", :"#{original}_will_change!" + alias_method :"#{name}_before_type_cast", :"#{original}_before_type_cast" end # Removes a field alias. @@ -341,15 +341,15 @@ def unalias_attribute(name) end remove_method name - remove_method "#{name}=" - remove_method "#{name}?" - remove_method "#{name}_change" - remove_method "#{name}_changed?" - remove_method "reset_#{name}!" - remove_method "reset_#{name}_to_default!" - remove_method "#{name}_was" - remove_method "#{name}_will_change!" - remove_method "#{name}_before_type_cast" + remove_method :"#{name}=" + remove_method :"#{name}?" + remove_method :"#{name}_change" + remove_method :"#{name}_changed?" + remove_method :"reset_#{name}!" + remove_method :"reset_#{name}_to_default!" + remove_method :"#{name}_was" + remove_method :"#{name}_will_change!" + remove_method :"#{name}_before_type_cast" end end diff --git a/lib/mongoid/attributes/dynamic.rb b/lib/mongoid/attributes/dynamic.rb index 43b6b0354..f4179c766 100644 --- a/lib/mongoid/attributes/dynamic.rb +++ b/lib/mongoid/attributes/dynamic.rb @@ -49,7 +49,7 @@ def define_dynamic_reader(name) # @param [ String ] name The name of the field. def define_dynamic_before_type_cast_reader(name) class_eval do - define_method("#{name}_before_type_cast") do + define_method(:"#{name}_before_type_cast") do attribute_will_change!(name) read_attribute_before_type_cast(name) end @@ -68,7 +68,7 @@ def define_dynamic_writer(name) return unless name.valid_method_name? class_eval do - define_method("#{name}=") do |value| + define_method(:"#{name}=") do |value| write_attribute(name, value) end end @@ -83,9 +83,9 @@ def define_dynamic_writer(name) # @param [ Symbol ] name The name of the field. # @param [ Object ] value The value of the field. def process_attribute(name, value) - responds = respond_to?("#{name}=") + responds = respond_to?(:"#{name}=") if responds - send("#{name}=", value) + send(:"#{name}=", value) else write_attribute(name, value) end diff --git a/lib/mongoid/attributes/processing.rb b/lib/mongoid/attributes/processing.rb index f936a17d3..fb4e76e0a 100644 --- a/lib/mongoid/attributes/processing.rb +++ b/lib/mongoid/attributes/processing.rb @@ -119,13 +119,13 @@ def pending_nested # @param [ Symbol ] name The name of the field. # @param [ Object ] value The value of the field. def process_attribute(name, value) - if !respond_to?("#{name}=", true) && (store_as = aliased_fields.invert[name.to_s]) + if !respond_to?(:"#{name}=", true) && (store_as = aliased_fields.invert[name.to_s]) name = store_as end - responds = respond_to?("#{name}=", true) + responds = respond_to?(:"#{name}=", true) raise Errors::UnknownAttribute.new(self.class, name) unless responds - send("#{name}=", value) + send(:"#{name}=", value) end # Process all the pending nested attributes that needed to wait until @@ -135,7 +135,7 @@ def process_attribute(name, value) # document.process_nested def process_nested pending_nested.each_pair do |name, value| - send("#{name}=", value) + send(:"#{name}=", value) end end @@ -160,7 +160,7 @@ def process_relations if value.is_a?(Hash) association.nested_builder(value, {}).build(self) else - send("#{name}=", value) + send(:"#{name}=", value) end end end diff --git a/lib/mongoid/changeable.rb b/lib/mongoid/changeable.rb index bf5bfff1e..d24b04777 100644 --- a/lib/mongoid/changeable.rb +++ b/lib/mongoid/changeable.rb @@ -336,9 +336,9 @@ def reset_attribute!(attr) def reset_attribute_to_default!(attr) attr = database_field_name(attr) if (field = fields[attr]) - __send__("#{attr}=", field.eval_default(self)) + __send__(:"#{attr}=", field.eval_default(self)) else - __send__("#{attr}=", nil) + __send__(:"#{attr}=", nil) end end diff --git a/lib/mongoid/clients.rb b/lib/mongoid/clients.rb index ca6866ab3..90040ef69 100644 --- a/lib/mongoid/clients.rb +++ b/lib/mongoid/clients.rb @@ -49,6 +49,18 @@ def default # @return [ true ] True. def disconnect clients.each_value(&:close) + true + end + + # Reconnect all active clients. + # + # @example Reconnect all active clients. + # Mongoid::Clients.reconnect + # + # @return [ true ] True. + def reconnect + clients.each_value(&:reconnect) + true end # Get a stored client with the provided name. If no client exists diff --git a/lib/mongoid/clients/sessions.rb b/lib/mongoid/clients/sessions.rb index e0edfd54c..89e3ef191 100644 --- a/lib/mongoid/clients/sessions.rb +++ b/lib/mongoid/clients/sessions.rb @@ -59,6 +59,8 @@ def with_session(options = {}) end raise e + rescue *transactions_not_supported_exceptions + raise Mongoid::Errors::TransactionsNotSupported ensure Threaded.clear_session(client: persistence_context.client) end @@ -89,6 +91,8 @@ def transaction(options = {}, session_options: {}) session.start_transaction(options) yield commit_transaction(session) + rescue *transactions_not_supported_exceptions + raise Mongoid::Errors::TransactionsNotSupported rescue Mongoid::Errors::Rollback abort_transaction(session) rescue Mongoid::Errors::InvalidSessionNesting @@ -148,6 +152,22 @@ def after_rollback(*args, &block) private + # Driver version 2.20 introduced a new exception for reporting that + # transactions are not supported. Prior to that, the condition was + # discovered by the rescue clause falling through to a different + # exception. + # + # This method ensures that Mongoid continues to work with older driver + # versions, by only returning the new exception. + # + # Once support is removed for all versions prior to 2.20.0, we can + # replace this method. + def transactions_not_supported_exceptions + return nil unless defined? Mongo::Error::TransactionsNotSupported + + Mongo::Error::TransactionsNotSupported + end + # @return [ Mongo::Session ] Session for the current client. def _session Threaded.get_session(client: persistence_context.client) @@ -231,6 +251,42 @@ def transaction_include_any_action?(actions) end end end + + private + + # If at least one session is active, this ensures that the + # current model's client is compatible with one of them. + # + # "Compatible" is defined to mean: the same client was used + # to open one of the active sessions. + # + # Currently emits a warning. + def ensure_client_compatibility! + # short circuit: if no sessions are active, there's nothing + # to check. + return unless Threaded.sessions.any? + + # at this point, we know that at least one session is currently + # active. let's see if one of them was started with the model's + # client... + session = Threaded.get_session(client: persistence_context.client) + return unless session.nil? + + # if the session is nil, then we have a case of the programmer trying to use + # a model within a transaction, where the model is not itself + # controlled by that transaction. this is potentially a bug, so + # let's tell them about it. + # + # This is hacky; we're hijacking Mongoid::Errors::MongoidError in + # order to get the spiffy error message translation. If we later + # decide to raise an error instead of just writing a message, we can + # subclass MongoidError and raise that exception here. + message = Errors::MongoidError.new.compose_message( + 'client_session_mismatch', + model: self.class.name + ) + logger.info(message) + end end end end diff --git a/lib/mongoid/config.rb b/lib/mongoid/config.rb index 7864e0c24..7c5c3be2f 100644 --- a/lib/mongoid/config.rb +++ b/lib/mongoid/config.rb @@ -337,7 +337,7 @@ def options=(options) Validators::AsyncQueryExecutor.validate(options) options.each_pair do |option, value| Validators::Option.validate(option) - send("#{option}=", value) + send(:"#{option}=", value) end end @@ -367,10 +367,14 @@ def time_zone # config.running_with_passenger? # # @return [ true | false ] If the app is deployed on Passenger. + # + # @deprecated def running_with_passenger? @running_with_passenger ||= defined?(PhusionPassenger) end + Mongoid.deprecate(self, :running_with_passenger?) + private def set_log_levels diff --git a/lib/mongoid/config/defaults.rb b/lib/mongoid/config/defaults.rb index 9b4ea50b0..2857931ea 100644 --- a/lib/mongoid/config/defaults.rb +++ b/lib/mongoid/config/defaults.rb @@ -24,6 +24,8 @@ def load_defaults(version) when '8.1' self.immutable_ids = false self.legacy_persistence_context_behavior = true + self.around_callbacks_for_embeds = true + self.prevent_multiple_calls_of_embedded_callbacks = false load_defaults '9.0' when '9.0' diff --git a/lib/mongoid/config/options.rb b/lib/mongoid/config/options.rb index befb6e108..a0a606b82 100644 --- a/lib/mongoid/config/options.rb +++ b/lib/mongoid/config/options.rb @@ -38,12 +38,12 @@ def option(name, options = {}) end end - define_method("#{name}=") do |value| + define_method(:"#{name}=") do |value| settings[name] = value options[:on_change]&.call(value) end - define_method("#{name}?") do + define_method(:"#{name}?") do !!send(name) end end diff --git a/lib/mongoid/contextual/memory.rb b/lib/mongoid/contextual/memory.rb index 18593f540..c748704b3 100644 --- a/lib/mongoid/contextual/memory.rb +++ b/lib/mongoid/contextual/memory.rb @@ -746,7 +746,7 @@ def retrieve_value_at_path(document, field_path) # If this is a localized field, and there are remaining, get the # _translations hash so that we can get the specified translation in # the remaining - document.send("#{segment}_translations") if field&.localized? + document.send(:"#{segment}_translations") if field&.localized? end meth = klass.aliased_associations[segment] || segment res.nil? ? document.try(meth) : res diff --git a/lib/mongoid/copyable.rb b/lib/mongoid/copyable.rb index 12c4b1a79..6ce3982e8 100644 --- a/lib/mongoid/copyable.rb +++ b/lib/mongoid/copyable.rb @@ -52,12 +52,12 @@ def self.clone_with_hash(klass, attrs) dynamic_attrs.each do |attr_name, value| assoc = object.embedded_relations[attr_name] if assoc&.one? && value.is_a?(Hash) - object.send("#{attr_name}=", clone_with_hash(assoc.klass, value)) + object.send(:"#{attr_name}=", clone_with_hash(assoc.klass, value)) elsif assoc&.many? && value.is_a?(Array) docs = value.map { |h| clone_with_hash(assoc.klass, h) } - object.send("#{attr_name}=", docs) - elsif object.respond_to?("#{attr_name}=") - object.send("#{attr_name}=", value) + object.send(:"#{attr_name}=", docs) + elsif object.respond_to?(:"#{attr_name}=") + object.send(:"#{attr_name}=", value) else object.attributes[attr_name] = value end diff --git a/lib/mongoid/errors/mongoid_error.rb b/lib/mongoid/errors/mongoid_error.rb index e4b6a5917..161f65eb3 100644 --- a/lib/mongoid/errors/mongoid_error.rb +++ b/lib/mongoid/errors/mongoid_error.rb @@ -26,9 +26,9 @@ def compose_message(key, attributes = {}) @summary_title = translate('summary_title', {}) @resolution_title = translate('resolution_title', {}) - "\n#{@problem_title}:\n #{@problem}" \ - "\n#{@summary_title}:\n #{@summary}" \ - "\n#{@resolution_title}:\n #{@resolution}" + "\n#{@problem_title}:\n #{@problem&.strip}" \ + "\n#{@summary_title}:\n #{@summary&.strip}" \ + "\n#{@resolution_title}:\n #{@resolution&.strip}" end private diff --git a/lib/mongoid/extensions/date.rb b/lib/mongoid/extensions/date.rb index 6dc80bde6..4708891ac 100644 --- a/lib/mongoid/extensions/date.rb +++ b/lib/mongoid/extensions/date.rb @@ -68,13 +68,13 @@ def demongoize(object) def mongoize(object) return if object.blank? - begin - time = if object.is_a?(String) - # https://jira.mongodb.org/browse/MONGOID-4460 - ::Time.parse(object) - else - object.__mongoize_time__ - end + time = begin + if object.is_a?(String) + # https://jira.mongodb.org/browse/MONGOID-4460 + ::Time.parse(object) + else + object.try(:__mongoize_time__) + end rescue ArgumentError nil end diff --git a/lib/mongoid/extensions/object.rb b/lib/mongoid/extensions/object.rb index 91e9c42d6..e5987306b 100644 --- a/lib/mongoid/extensions/object.rb +++ b/lib/mongoid/extensions/object.rb @@ -31,22 +31,6 @@ def __find_args__ end Mongoid.deprecate(self, :__find_args__) - # Mongoize a plain object into a time. - # - # @note This method should not be used, because it does not - # return correct results for non-Time objects. Override - # __mongoize_time__ in classes that are time-like to return an - # instance of Time or ActiveSupport::TimeWithZone. - # - # @example Mongoize the object. - # object.__mongoize_time__ - # - # @return [ Object ] self. - # @deprecated - def __mongoize_time__ - self - end - # Try to form a setter from this object. # # @example Try to form a setter. @@ -158,8 +142,8 @@ def numeric? # # @return [ true | false ] If the variable was defined. def remove_ivar(name) - if instance_variable_defined?("@_#{name}") - remove_instance_variable("@_#{name}") + if instance_variable_defined?(:"@_#{name}") + remove_instance_variable(:"@_#{name}") else false end diff --git a/lib/mongoid/extensions/time.rb b/lib/mongoid/extensions/time.rb index 3c88c0fe1..56607238d 100644 --- a/lib/mongoid/extensions/time.rb +++ b/lib/mongoid/extensions/time.rb @@ -72,7 +72,7 @@ def mongoize(object) return if object.blank? begin - time = object.__mongoize_time__ + time = object.try(:__mongoize_time__) rescue ArgumentError return end diff --git a/lib/mongoid/inspectable.rb b/lib/mongoid/inspectable.rb index 80cfa129c..d83cded92 100644 --- a/lib/mongoid/inspectable.rb +++ b/lib/mongoid/inspectable.rb @@ -18,6 +18,37 @@ def inspect "#<#{self.class.name} _id: #{_id}, #{inspection * ', '}>" end + # This pretty prints the same information as the inspect method. This is + # meant to be called by the standard 'pp' library. + # + # @param [ PP ] pretty_printer The pretty printer. + # + # @example Pretty print the document. + # person.pretty_inspect + # + # @api private + def pretty_print(pretty_printer) + keys = fields.keys | attributes.keys + pretty_printer.group(1, "#<#{self.class.name}", '>') do + sep = -> { pretty_printer.text(',') } + pretty_printer.seplist(keys, sep) do |key| + pretty_printer.breakable + field = fields[key] + as = "(#{field.options[:as]})" if field && field.options[:as] + pretty_printer.text("#{key}#{as}") + pretty_printer.text(':') + pretty_printer.group(1) do + pretty_printer.breakable + if key == '_id' + pretty_printer.text(_id.to_s) + else + pretty_printer.pp(@attributes[key]) + end + end + end + end + end + private # Get an array of inspected fields for the document. diff --git a/lib/mongoid/interceptable.rb b/lib/mongoid/interceptable.rb index d598c37bc..956a5c298 100644 --- a/lib/mongoid/interceptable.rb +++ b/lib/mongoid/interceptable.rb @@ -64,7 +64,7 @@ module Interceptable # # @return [ true | false ] If the callback can be executed. def callback_executable?(kind) - respond_to?("_#{kind}_callbacks") + respond_to?(:"_#{kind}_callbacks") end # Is the document currently in a state that could potentially require @@ -401,7 +401,7 @@ def run_targeted_callbacks(place, kind) name = "_run__#{place}__#{kind}__callbacks" unless respond_to?(name) chain = ActiveSupport::Callbacks::CallbackChain.new(name, {}) - send("_#{kind}_callbacks").each do |callback| + send(:"_#{kind}_callbacks").each do |callback| chain.append(callback) if callback.kind == place end self.class.send :define_method, name do diff --git a/lib/mongoid/persistable/creatable.rb b/lib/mongoid/persistable/creatable.rb index ad9f3e893..017bced7e 100644 --- a/lib/mongoid/persistable/creatable.rb +++ b/lib/mongoid/persistable/creatable.rb @@ -107,6 +107,7 @@ def prepare_insert(options = {}) return self if performing_validations?(options) && invalid?(options[:context] || :create) + ensure_client_compatibility! run_callbacks(:commit, with_children: true, skip_if: -> { in_transaction? }) do run_callbacks(:save, with_children: false) do run_callbacks(:create, with_children: false) do diff --git a/lib/mongoid/persistable/updatable.rb b/lib/mongoid/persistable/updatable.rb index 20a2d4b61..ec8a5efe1 100644 --- a/lib/mongoid/persistable/updatable.rb +++ b/lib/mongoid/persistable/updatable.rb @@ -99,6 +99,7 @@ def prepare_update(options = {}) raise Errors::ReadonlyDocument.new(self.class) if readonly? && !Mongoid.legacy_readonly enforce_immutability_of_id_field! + ensure_client_compatibility! return false if performing_validations?(options) && invalid?(options[:context] || :update) diff --git a/lib/mongoid/railtie.rb b/lib/mongoid/railtie.rb index 77a218ca0..5e5b0f723 100644 --- a/lib/mongoid/railtie.rb +++ b/lib/mongoid/railtie.rb @@ -114,9 +114,9 @@ def handle_configuration_error(error) # Add custom serializers for BSON::ObjectId initializer 'mongoid.active_job.custom_serializers' do - require 'mongoid/railties/bson_object_id_serializer' + ActiveSupport.on_load :active_job do + require 'mongoid/railties/bson_object_id_serializer' - config.after_initialize do ActiveJob::Serializers.add_serializers( [::Mongoid::Railties::ActiveJobSerializers::BsonObjectIdSerializer] ) diff --git a/lib/mongoid/scopable.rb b/lib/mongoid/scopable.rb index a6ae5b939..c18922fe2 100644 --- a/lib/mongoid/scopable.rb +++ b/lib/mongoid/scopable.rb @@ -288,17 +288,15 @@ def check_scope_validity(value) # @return [ Method ] The defined method. def define_scope_method(name) singleton_class.class_eval do - ruby2_keywords( - define_method(name) do |*args, **kwargs| - scoping = _declared_scopes[name] - 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) - criteria.extend(extension) - criteria - end - ) + define_method(name) do |*args, **kwargs| + scoping = _declared_scopes[name] + 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) + criteria.extend(extension) + criteria + end end end diff --git a/lib/mongoid/validatable.rb b/lib/mongoid/validatable.rb index c95a520ed..821ff2c24 100644 --- a/lib/mongoid/validatable.rb +++ b/lib/mongoid/validatable.rb @@ -37,6 +37,14 @@ def exit_validate Threaded.exit_validate(self) end + # Perform a validation within the associated block. + def validating + begin_validate + yield + ensure + exit_validate + end + # Given the provided options, are we performing validations? # # @example Are we performing validations? diff --git a/lib/mongoid/validatable/associated.rb b/lib/mongoid/validatable/associated.rb index 1aba1be99..bf89043d6 100644 --- a/lib/mongoid/validatable/associated.rb +++ b/lib/mongoid/validatable/associated.rb @@ -15,32 +15,108 @@ module Validatable # # validates_associated :name, :addresses # end - class AssociatedValidator < ActiveModel::EachValidator + class AssociatedValidator < ActiveModel::Validator + # Required by `validates_with` so that the validator + # gets added to the correct attributes. + def attributes + options[:attributes] + end - # Validates that the associations provided are either all nil or all - # valid. If neither is true then the appropriate errors will be added to - # the parent document. + # Checks that the named associations of the given record + # (`attributes`) are valid. This does NOT load the associations + # from the database, and will only validate records that are dirty + # or unpersisted. # - # @example Validate the association. - # validator.validate_each(document, :name, name) + # If anything is not valid, appropriate errors will be added to + # the `document` parameter. + # + # @param [ Mongoid::Document ] document the document with the + # associations to validate. + def validate(document) + options[:attributes].each do |attr_name| + validate_association(document, attr_name) + end + end + + private + + # Validates that the given association provided is either nil, + # persisted and unchanged, or invalid. Otherwise, the appropriate errors + # will be added to the parent document. # # @param [ Mongoid::Document ] document The document to validate. # @param [ Symbol ] attribute The association to validate. - # @param [ Object ] value The value of the association. - def validate_each(document, attribute, value) - begin - document.begin_validate - valid = Array.wrap(value).collect do |doc| - if doc.nil? || doc.flagged_for_destroy? - true + def validate_association(document, attribute) + # grab the proxy from the instance variable directly; we don't want + # any loading logic to run; we just want to see if it's already + # been loaded. + proxy = document.ivar(attribute) + return unless proxy + + # if the variable exists, now we see if it is a proxy, or an actual + # document. It might be a literal document instead of a proxy if this + # document was created with a Document instance as a provided attribute, + # e.g. "Post.new(message: Message.new)". + target = proxy.respond_to?(:_target) ? proxy._target : proxy + + # Now, fetch the list of documents from the target. Target may be a + # single value, or a list of values, and in the case of HasMany, + # might be a rather complex collection. We need to do this without + # triggering a load, so it's a bit of a delicate dance. + list = get_target_documents(target) + + valid = document.validating do + # Now, treating the target as an array, look at each element + # and see if it is valid, but only if it has already been + # persisted, or changed, and hasn't been flagged for destroy. + list.all? do |value| + if value && !value.flagged_for_destroy? && (!value.persisted? || value.changed?) + value.validated? ? true : value.valid? else - doc.validated? ? true : doc.valid? + true end - end.all? - ensure - document.exit_validate + end end - document.errors.add(attribute, :invalid, **options) unless valid + + document.errors.add(attribute, :invalid) unless valid + end + + # Examine the given target object and return an array of + # documents (possibly empty) that the target represents. + # + # @param [ Array | Mongoid::Document | Mongoid::Association::Proxy | HasMany::Enumerable ] target + # the target object to examine. + # + # @return [ Array ] the list of documents + def get_target_documents(target) + if target.respond_to?(:_loaded?) + get_target_documents_for_has_many(target) + else + get_target_documents_for_other(target) + end + end + + # Returns the list of all currently in-memory values held by + # the target. The target will not be loaded. + # + # @param [ HasMany::Enumerable ] target the target that will + # be examined for in-memory documents. + # + # @return [ Array ] the in-memory documents + # held by the target. + def get_target_documents_for_has_many(target) + [*target._loaded.values, *target._added.values] + end + + # Returns the target as an array. If the target represents a single + # value, it is wrapped in an array. + # + # @param [ Array | Mongoid::Document | Mongoid::Association::Proxy ] target + # the target to return. + # + # @return [ Array ] the target, as an array. + def get_target_documents_for_other(target) + Array.wrap(target) end end end diff --git a/lib/mongoid/version.rb b/lib/mongoid/version.rb index ef36d090c..781b777e5 100644 --- a/lib/mongoid/version.rb +++ b/lib/mongoid/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Mongoid - VERSION = '9.0.0.0.alpha1' + VERSION = '10.0.0.0.alpha1' GEM_NAME = 'mongoid-ultra' diff --git a/lib/mongoid/warnings.rb b/lib/mongoid/warnings.rb index 67cee0cc6..8ef08d22b 100644 --- a/lib/mongoid/warnings.rb +++ b/lib/mongoid/warnings.rb @@ -18,11 +18,11 @@ class << self # @api private def warning(id, message) singleton_class.class_eval do - define_method("warn_#{id}") do - return if instance_variable_get("@#{id}") + define_method(:"warn_#{id}") do + return if instance_variable_get(:"@#{id}") Mongoid.logger.warn(message) - instance_variable_set("@#{id}", true) + instance_variable_set(:"@#{id}", true) end end end diff --git a/mongoid.gemspec b/mongoid.gemspec index cf9a0cf10..2d295cd08 100644 --- a/mongoid.gemspec +++ b/mongoid.gemspec @@ -34,12 +34,7 @@ Gem::Specification.new do |s| s.add_dependency('mongo', ['>=2.18.0', '<3.0.0']) s.add_dependency('concurrent-ruby', ['>= 1.0.5', '< 2.0']) - # The ruby2_keywords gem normalizes Ruby 2.7's arg delegation. - # It can be removed when Ruby 2.7 is removed. - # See: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/#delegation - s.add_dependency('ruby2_keywords', '~> 0.0.5') - - s.add_development_dependency('bson', ['>= 4.14.0', '< 5.0.0']) + s.add_development_dependency('bson', ['>= 4.14.0', '< 6.0.0']) s.files = Dir.glob('lib/**/*') + %w[LICENSE README.md Rakefile] s.require_path = 'lib' diff --git a/spec/integration/associations/has_and_belongs_to_many_spec.rb b/spec/integration/associations/has_and_belongs_to_many_spec.rb index 30ba61b8e..5d03fe1b3 100644 --- a/spec/integration/associations/has_and_belongs_to_many_spec.rb +++ b/spec/integration/associations/has_and_belongs_to_many_spec.rb @@ -2,6 +2,28 @@ require 'spec_helper' +module HabtmSpec + class Page + include Mongoid::Document + embeds_many :blocks, class_name: 'HabtmSpec::Block' + end + + class Block + include Mongoid::Document + embedded_in :page, class_name: 'HabtmSpec::Page' + end + + class ImageBlock < Block + has_and_belongs_to_many :attachments, inverse_of: nil, class_name: 'HabtmSpec::Attachment' + accepts_nested_attributes_for :attachments + end + + class Attachment + include Mongoid::Document + field :file, type: String + end +end + describe 'has_and_belongs_to_many associations' do context 'when an anonymous class defines a has_and_belongs_to_many association' do @@ -18,4 +40,22 @@ expect(klass.new.movies.build).to be_a Movie end end + + context 'when an embedded has habtm relation' do + let(:attachment) { HabtmSpec::Attachment.create!(file: 'foo.jpg') } + + let(:page) { HabtmSpec::Page.create! } + + let(:image_block) do + page.blocks.build({ + _type: 'HabtmSpec::ImageBlock', + attachment_ids: [attachment.id.to_s], + attachments_attributes: { '1234' => { file: 'bar.jpg', id: attachment.id.to_s } } + }) + end + + it 'does not raise on save' do + expect { image_block.save! }.to_not raise_error + end + end end diff --git a/spec/mongoid/association/auto_save_spec.rb b/spec/mongoid/association/auto_save_spec.rb index a700a76ba..570dac3e2 100644 --- a/spec/mongoid/association/auto_save_spec.rb +++ b/spec/mongoid/association/auto_save_spec.rb @@ -9,16 +9,16 @@ describe '.auto_save' do before(:all) do - Person.has_many :drugs, validate: false, autosave: true - Person.has_one :account, validate: false, autosave: true + PersonAutosave.has_many :drugs, class_name: 'DrugAutosave', validate: false, autosave: true + PersonAutosave.has_one :account, class_name: 'AccountAutosave', validate: false, autosave: true end after(:all) do - Person.reset_callbacks(:save) + PersonAutosave.reset_callbacks(:save) end let(:person) do - Person.new + PersonAutosave.new end context 'when the option is not provided' do @@ -85,11 +85,11 @@ context 'when the relation has already had the autosave callback added' do before do - Person.has_many :drugs, validate: false, autosave: true + PersonAutosave.has_many :drugs, class_name: 'DrugAutosave', validate: false, autosave: true end let(:drug) do - Drug.new(name: 'Percocet') + DrugAutosave.new(name: 'Percocet') end it 'does not add the autosave callback twice' do @@ -102,7 +102,7 @@ context 'when the relation is a references many' do let(:drug) do - Drug.new(name: 'Percocet') + DrugAutosave.new(name: 'Percocet') end context 'when saving a new parent document' do @@ -110,7 +110,7 @@ context 'when persistence options are not set on the parent' do before do - Person.has_many :drugs, validate: false, autosave: true + PersonAutosave.has_many :drugs, class_name: 'DrugAutosave', validate: false, autosave: true end before do @@ -130,8 +130,8 @@ end after do - Person.with(database: other_database, &:delete_all) - Drug.with(database: other_database, &:delete_all) + PersonAutosave.with(database: other_database, &:delete_all) + DrugAutosave.with(database: other_database, &:delete_all) end before do @@ -142,7 +142,7 @@ end it 'saves the relation with the persistence options' do - Drug.with(database: other_database) do |drug_class| + DrugAutosave.with(database: other_database) do |drug_class| expect(drug_class.count).to eq(1) end end @@ -165,7 +165,7 @@ context 'when not updating the document' do let(:from_db) do - Person.find person.id + PersonAutosave.find person.id end before do @@ -183,7 +183,7 @@ context 'when the relation is a references one' do let(:account) do - Account.new(name: 'Testing') + AccountAutosave.new(name: 'Testing') end context 'when saving a new parent document' do @@ -237,7 +237,7 @@ context 'when not updating the document' do let(:from_db) do - Person.find person.id + PersonAutosave.find person.id end before do @@ -291,25 +291,25 @@ context 'when it has two relations with autosaves' do let!(:person) do - Person.create!(drugs: [percocet], account: account) + PersonAutosave.create!(drugs: [percocet], account: account) end let(:from_db) do - Person.find person.id + PersonAutosave.find person.id end let(:percocet) do - Drug.new(name: 'Percocet') + DrugAutosave.new(name: 'Percocet') end let(:account) do - Account.new(name: 'Testing') + AccountAutosave.new(name: 'Testing') end context 'when updating one document' do let(:placebo) do - Drug.new(name: 'Placebo') + DrugAutosave.new(name: 'Placebo') end before do diff --git a/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb b/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb index 3dffb7ad2..dab566c26 100644 --- a/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +++ b/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb @@ -755,7 +755,7 @@ expect(drug).to be_destroyed end - it "doesn't deletes parent" do + it "doesn't delete parent" do expect(person).to_not be_destroyed end diff --git a/spec/mongoid/association/referenced/has_many/proxy_spec.rb b/spec/mongoid/association/referenced/has_many/proxy_spec.rb index b7265d873..d190bd12f 100644 --- a/spec/mongoid/association/referenced/has_many/proxy_spec.rb +++ b/spec/mongoid/association/referenced/has_many/proxy_spec.rb @@ -735,7 +735,7 @@ def initialize(*args) let(:child) { parent.children.send(method) } it 'calls #initialize' do - expect(child.name).to be == 'default' + expect(child.name).to eq 'default' end end diff --git a/spec/mongoid/attributes/dynamic_spec.rb b/spec/mongoid/attributes/dynamic_spec.rb index 2530177a3..a4d922aec 100644 --- a/spec/mongoid/attributes/dynamic_spec.rb +++ b/spec/mongoid/attributes/dynamic_spec.rb @@ -112,7 +112,7 @@ it 'cannot be written' do expect do - bar.send("#{attr_name}=", 'foo bar') + bar.send(:"#{attr_name}=", 'foo bar') bar.save! end.to raise_error(NoMethodError) end @@ -122,7 +122,7 @@ let(:bar) { Bar.new(attr_name => 'foo bar') } it 'can be written' do - bar.send("#{attr_name}=", 'new foo bar') + bar.send(:"#{attr_name}=", 'new foo bar') bar.save! bar_found = Bar.find(bar.id) @@ -161,7 +161,7 @@ end it 'responds to writer method' do - expect(bar.respond_to?("#{attr_name}=")).to be true + expect(bar.respond_to?(:"#{attr_name}=")).to be true end end @@ -173,7 +173,7 @@ end it 'does not respond to writer method' do - expect(bar.respond_to?("#{attr_name}=")).to be false + expect(bar.respond_to?(:"#{attr_name}=")).to be false end end end diff --git a/spec/mongoid/attributes_spec.rb b/spec/mongoid/attributes_spec.rb index d84a2934f..d16ab9d24 100644 --- a/spec/mongoid/attributes_spec.rb +++ b/spec/mongoid/attributes_spec.rb @@ -2650,7 +2650,7 @@ let(:doc) { NestedBook.create! } before do - doc.send("#{meth}_cover", attrs) + doc.send(:"#{meth}_cover", attrs) end it 'updates the attributes' do diff --git a/spec/mongoid/clients/sessions_spec.rb b/spec/mongoid/clients/sessions_spec.rb index 3453ceaa3..6619aabd8 100644 --- a/spec/mongoid/clients/sessions_spec.rb +++ b/spec/mongoid/clients/sessions_spec.rb @@ -3,19 +3,8 @@ require 'spec_helper' describe Mongoid::Clients::Sessions do - - before(:all) do - CONFIG[:clients][:other] = CONFIG[:clients][:default].dup - CONFIG[:clients][:other][:database] = 'other' - Mongoid::Clients.clients.each_value(&:close) - Mongoid::Config.send(:clients=, CONFIG[:clients]) - Mongoid::Clients.with_name(:other).subscribe(Mongo::Monitoring::COMMAND, EventSubscriber.new) - end - - after(:all) do - Mongoid::Clients.with_name(:other).close - Mongoid::Clients.clients.delete(:other) - end + let(:buffer) { StringIO.new } + let(:logger) { Logger.new(buffer, Logger::DEBUG) } let(:subscriber) do client = Mongoid::Clients.with_name(:other) @@ -35,6 +24,27 @@ subscriber.started_events.select { |event| event.command_name.to_s == 'update' } end + around do |example| + old_logger = Mongoid.logger + Mongoid.logger = logger + example.run + ensure + Mongoid.logger = old_logger + end + + before(:all) do + CONFIG[:clients][:other] = CONFIG[:clients][:default].dup + CONFIG[:clients][:other][:database] = 'other' + Mongoid::Clients.clients.each_value(&:close) + Mongoid::Config.send(:clients=, CONFIG[:clients]) + Mongoid::Clients.with_name(:other).subscribe(Mongo::Monitoring::COMMAND, EventSubscriber.new) + end + + after(:all) do + Mongoid::Clients.with_name(:other).close + Mongoid::Clients.clients.delete(:other) + end + context 'when a session is used on a model class' do context 'when sessions are supported' do @@ -229,6 +239,10 @@ expect(insert_lsids_sent.uniq.size).to eq(1) expect(update_lsids_sent.uniq).to eq(insert_lsids_sent.uniq) end + + it 'does not warn about a different client' do + expect(buffer.string).to_not include("used within another client's session") + end end context 'when the other class uses a different client' do @@ -257,6 +271,10 @@ update_lsids_sent = update_events.collect { |event| event.command['lsid'] } expect(update_lsids_sent.size).to eq(2) end + + it 'warns about a different client' do + expect(buffer.string).to include("used within another client's session") + end end context 'when sessions are nested' do diff --git a/spec/mongoid/clients_spec.rb b/spec/mongoid/clients_spec.rb index fa87dac6a..1091b7c1b 100644 --- a/spec/mongoid/clients_spec.rb +++ b/spec/mongoid/clients_spec.rb @@ -1194,4 +1194,48 @@ class StoreChild2 < StoreParent end end end + + describe '#disconnect' do + + let(:clients) do + described_class.clients.values + end + + before do + Band.all.entries + end + + it 'disconnects from all active clients' do + clients.each do |client| + expect(client).to receive(:close).and_call_original + end + described_class.disconnect + end + + it 'returns true' do + expect(described_class.disconnect).to be(true) + end + end + + describe '#reconnect' do + + let(:clients) do + described_class.clients.values + end + + before do + Band.all.entries + end + + it 'reconnects all active clients' do + clients.each do |client| + expect(client).to receive(:reconnect).and_call_original + end + described_class.reconnect + end + + it 'returns true' do + expect(described_class.reconnect).to be(true) + end + end end diff --git a/spec/mongoid/config/defaults_spec.rb b/spec/mongoid/config/defaults_spec.rb index 2b9624672..54e597b66 100644 --- a/spec/mongoid/config/defaults_spec.rb +++ b/spec/mongoid/config/defaults_spec.rb @@ -26,6 +26,8 @@ it 'uses settings for 8.1' do expect(Mongoid.immutable_ids).to be false expect(Mongoid.legacy_persistence_context_behavior).to be true + expect(Mongoid.around_callbacks_for_embeds).to be true + expect(Mongoid.prevent_multiple_calls_of_embedded_callbacks).to be false end end @@ -33,6 +35,8 @@ 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 + expect(Mongoid.around_callbacks_for_embeds).to be false + expect(Mongoid.prevent_multiple_calls_of_embedded_callbacks).to be true end end diff --git a/spec/mongoid/contextual/none_spec.rb b/spec/mongoid/contextual/none_spec.rb index ef8990de2..ef733ca39 100644 --- a/spec/mongoid/contextual/none_spec.rb +++ b/spec/mongoid/contextual/none_spec.rb @@ -188,7 +188,7 @@ describe "##{meth}!" do it 'raises an error' do expect do - context.send("#{meth}!") + context.send(:"#{meth}!") end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./) end end diff --git a/spec/mongoid/criteria/queryable/selectable_logical_spec.rb b/spec/mongoid/criteria/queryable/selectable_logical_spec.rb index 4833c08bd..a0a1dea18 100644 --- a/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +++ b/spec/mongoid/criteria/queryable/selectable_logical_spec.rb @@ -1664,7 +1664,7 @@ # Date instance is converted to a Time instance in local time, # because we are querying on a Time field and dates are interpreted # in local time when assigning to Time fields - { 'published' => { '$gt' => Time.local(2020, 2, 3) } } + { 'published' => { '$gt' => Time.zone.local(2020, 2, 3) } } ] }) end @@ -2531,7 +2531,7 @@ # Date instance is converted to a Time instance in local time, # because we are querying on a Time field and dates are interpreted # in local time when assigning to Time fields - { 'published' => { '$gt' => Time.local(2020, 2, 3) } } + { 'published' => { '$gt' => Time.zone.local(2020, 2, 3) } } ] }) end diff --git a/spec/mongoid/document_persistence_context_spec.rb b/spec/mongoid/document_persistence_context_spec.rb index baf2e2fe6..fcff8ee18 100644 --- a/spec/mongoid/document_persistence_context_spec.rb +++ b/spec/mongoid/document_persistence_context_spec.rb @@ -39,28 +39,28 @@ config_override :legacy_persistence_context_behavior, false it 'remembers its persistence context when created' do - expect(person.collection_name).to be == :extra_people + expect(person.collection_name).to eq :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 + expect(person_by_id.collection_name).to eq :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 + expect(person_generally.collection_name).to eq :extra_people end it 'can be reloaded without specifying the context' do expect { person.reload }.to_not raise_error - expect(person.collection_name).to be == :extra_people + expect(person.collection_name).to eq :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' + expect(Person.with(options) { Person.first.username }).to eq 'zyg15' end it 'an explicit context takes precedence over a remembered context when persisting' do @@ -81,18 +81,18 @@ config_override :legacy_persistence_context_behavior, true it 'does not remember its persistence context when created' do - expect(person.collection_name).to be == :people + expect(person.collection_name).to eq :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 + expect(person_by_id.collection_name).to eq :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 + expect(person_generally.collection_name).to eq :people end it 'cannot be reloaded without specifying the context' do @@ -101,7 +101,7 @@ it 'cannot be updated without specifying the context' do person.update username: 'zyg15' - expect(Person.with(options) { Person.first.username }).to be == 'zyg14' + expect(Person.with(options) { Person.first.username }).to eq 'zyg14' end end end diff --git a/spec/mongoid/extensions/object_spec.rb b/spec/mongoid/extensions/object_spec.rb index 3855117d0..6b37bb48e 100644 --- a/spec/mongoid/extensions/object_spec.rb +++ b/spec/mongoid/extensions/object_spec.rb @@ -22,13 +22,6 @@ end end - describe '#__mongoize_time__' do - - it 'returns self' do - expect(object.__mongoize_time__).to eq(object) - end - end - describe '.demongoize' do let(:object) do diff --git a/spec/mongoid/extensions/time_spec.rb b/spec/mongoid/extensions/time_spec.rb index 48da3a422..82393ce79 100644 --- a/spec/mongoid/extensions/time_spec.rb +++ b/spec/mongoid/extensions/time_spec.rb @@ -7,7 +7,7 @@ describe '.demongoize' do let!(:time) do - Time.local(2010, 11, 19) + Time.zone.local(2010, 11, 19) end context 'when the time zone is not defined' do @@ -21,7 +21,7 @@ it 'returns the local time' do expect(Time.demongoize(time).utc_offset).to eq( - Time.local(2010, 11, 19).utc_offset + Time.zone.local(2010, 11, 19).utc_offset ) end end @@ -40,7 +40,7 @@ context 'when we have a time close to midnight' do let(:time) do - Time.local(2010, 11, 19, 0, 30).utc + Time.zone.local(2010, 11, 19, 0, 30).utc end it 'changes it back to the equivalent local time' do @@ -321,7 +321,7 @@ describe '.mongoize' do let!(:time) do - Time.local(2010, 11, 19) + Time.zone.local(2010, 11, 19) end context 'when given nil' do @@ -607,7 +607,7 @@ end it 'converts to a utc time' do - expect(Time.mongoize(date)).to eq(Time.local(date.year, date.month, date.day)) + expect(Time.mongoize(date)).to eq(Time.zone.local(date.year, date.month, date.day)) end it 'has a zero utc offset' do @@ -634,7 +634,7 @@ end it 'returns a time' do - expect(Time.mongoize(array)).to eq(Time.local(*array)) + expect(Time.mongoize(array)).to eq(Time.zone.local(*array)) end context 'when setting ActiveSupport time zone' do @@ -652,11 +652,11 @@ describe '#mongoize' do let!(:time) do - Time.local(2010, 11, 19) + Time.zone.local(2010, 11, 19) end let!(:eom_time) do - Time.local(2012, 11, 30, 23, 59, 59, 999999.999) + Time.zone.local(2012, 11, 30, 23, 59, 59, 999999.999) end let!(:eom_time_mongoized) do diff --git a/spec/mongoid/extensions/time_with_zone_spec.rb b/spec/mongoid/extensions/time_with_zone_spec.rb index b54f7d6a2..1f5b6d418 100644 --- a/spec/mongoid/extensions/time_with_zone_spec.rb +++ b/spec/mongoid/extensions/time_with_zone_spec.rb @@ -7,7 +7,7 @@ describe '.demongoize' do let!(:time) do - Time.local(2010, 11, 19) + Time.zone.local(2010, 11, 19) end context 'when the time zone is not defined' do @@ -21,7 +21,7 @@ it 'returns the local time' do expect(ActiveSupport::TimeWithZone.demongoize(time).utc_offset).to eq( - Time.local(2010, 11, 19).utc_offset + Time.zone.local(2010, 11, 19).utc_offset ) end end @@ -40,7 +40,7 @@ context 'when we have a time close to midnight' do let(:time) do - Time.local(2010, 11, 19, 0, 30).utc + Time.zone.local(2010, 11, 19, 0, 30).utc end it 'changes it back to the equivalent local time' do @@ -135,7 +135,7 @@ describe '.mongoize' do let!(:time) do - Time.local(2010, 11, 19) + Time.zone.local(2010, 11, 19) end context 'when given nil' do @@ -170,7 +170,7 @@ it 'returns a local date from the string' do expect(ActiveSupport::TimeWithZone.mongoize(time.to_s)).to eq( - Time.local(time.year, time.month, time.day, time.hour, time.min, time.sec) + Time.zone.local(time.year, time.month, time.day, time.hour, time.min, time.sec) ) end end @@ -274,7 +274,7 @@ end it 'converts to a utc time' do - expect(ActiveSupport::TimeWithZone.mongoize(date)).to eq(Time.local(date.year, date.month, date.day)) + expect(ActiveSupport::TimeWithZone.mongoize(date)).to eq(Time.zone.local(date.year, date.month, date.day)) end it 'has a zero utc offset' do @@ -301,7 +301,7 @@ end it 'returns a time' do - expect(ActiveSupport::TimeWithZone.mongoize(array)).to eq(Time.local(*array)) + expect(ActiveSupport::TimeWithZone.mongoize(array)).to eq(Time.zone.local(*array)) end context 'when setting ActiveSupport time zone' do @@ -319,7 +319,7 @@ describe '#mongoize' do let!(:time) do - Time.local(2010, 11, 19) + Time.zone.local(2010, 11, 19) end it 'converts to a utc time' do diff --git a/spec/mongoid/fields_spec.rb b/spec/mongoid/fields_spec.rb index 5c0158ac6..07fb36b25 100644 --- a/spec/mongoid/fields_spec.rb +++ b/spec/mongoid/fields_spec.rb @@ -575,7 +575,7 @@ end it 'is declared as BSON::Decimal128' do - expect(Mop.fields['decimal128_field'].type).to be == BSON::Decimal128 + expect(Mop.fields['decimal128_field'].type).to eq BSON::Decimal128 end context 'when BSON version <= 4' do diff --git a/spec/mongoid/inspectable_spec.rb b/spec/mongoid/inspectable_spec.rb index d9306555e..b166998cb 100644 --- a/spec/mongoid/inspectable_spec.rb +++ b/spec/mongoid/inspectable_spec.rb @@ -83,4 +83,84 @@ end end end + + describe '#pretty_inspect' do + + context 'when not allowing dynamic fields' do + + let(:person) do + Person.new(title: 'CEO') + end + + let(:pretty_inspected) do + person.pretty_inspect + end + + it 'includes the model type' do + expect(pretty_inspected).to include('#\n" + end + end + end end diff --git a/spec/mongoid/interceptable_spec_models.rb b/spec/mongoid/interceptable_spec_models.rb index 7bad8f01b..9276c55f3 100644 --- a/spec/mongoid/interceptable_spec_models.rb +++ b/spec/mongoid/interceptable_spec_models.rb @@ -20,15 +20,15 @@ module CallbackTracking whens = %i[before after] %i[validation save create update].each do |what| whens.each do |whn| - send("#{whn}_#{what}", :"#{whn}_#{what}_stub") - define_method("#{whn}_#{what}_stub") do + send(:"#{whn}_#{what}", :"#{whn}_#{what}_stub") + define_method(:"#{whn}_#{what}_stub") do callback_registry&.record_call(self.class, :"#{whn}_#{what}") end end next if what == :validation - send("around_#{what}", :"around_#{what}_stub") - define_method("around_#{what}_stub") do |&block| + send(:"around_#{what}", :"around_#{what}_stub") + define_method(:"around_#{what}_stub") do |&block| callback_registry&.record_call(self.class, :"around_#{what}_open") block.call callback_registry&.record_call(self.class, :"around_#{what}_close") diff --git a/spec/mongoid/monkey_patches_spec.rb b/spec/mongoid/monkey_patches_spec.rb index 883be9c41..47aee17ca 100644 --- a/spec/mongoid/monkey_patches_spec.rb +++ b/spec/mongoid/monkey_patches_spec.rb @@ -45,7 +45,6 @@ __intersect_from_array__ __intersect_from_object__ __mongoize_object_id__ - __mongoize_time__ __union__ __union_from_object__ ivar @@ -58,6 +57,7 @@ Array => %i[ __evolve_date__ __evolve_time__ + __mongoize_time__ __sort_option__ __sort_pair__ delete_one @@ -69,15 +69,18 @@ Date => %i[ __evolve_date__ __evolve_time__ + __mongoize_time__ ], DateTime => %i[ __evolve_date__ __evolve_time__ + __mongoize_time__ ], FalseClass => %i[is_a?], Float => %i[ __evolve_date__ __evolve_time__ + __mongoize_time__ ], Hash => %i[ __sort_option__ @@ -85,6 +88,7 @@ Integer => %i[ __evolve_date__ __evolve_time__ + __mongoize_time__ ], Module => %i[ re_define_method @@ -106,6 +110,7 @@ __evolve_time__ __expr_part__ __mongo_expression__ + __mongoize_time__ __sort_option__ before_type_cast? collectionize @@ -154,10 +159,12 @@ Time => %i[ __evolve_date__ __evolve_time__ + __mongoize_time__ ], ActiveSupport::TimeWithZone => %i[ __evolve_date__ __evolve_time__ + __mongoize_time__ _bson_to_i ], BSON::Decimal128 => %i[ diff --git a/spec/mongoid/search_indexable_spec.rb b/spec/mongoid/search_indexable_spec.rb index d86db9fd0..69e86d249 100644 --- a/spec/mongoid/search_indexable_spec.rb +++ b/spec/mongoid/search_indexable_spec.rb @@ -85,7 +85,7 @@ def filter_results(result, names) context 'when search indexes have been defined' do it 'has search index specs' do - expect(model.search_index_specs).to be == [ + expect(model.search_index_specs).to eq [ { definition: { mappings: { dynamic: false } } }, { name: 'with_dynamic_mappings', definition: { mappings: { dynamic: true } } } ] @@ -101,7 +101,7 @@ def filter_results(result, names) describe '.create_search_indexes' do it 'creates the indexes' do - expect(actual_definitions).to be == requested_definitions + expect(actual_definitions).to eq requested_definitions end end @@ -111,7 +111,7 @@ def filter_results(result, names) let(:queried_definitions) { model.search_indexes.pluck('latestDefinition') } it 'queries the available search indexes' do - expect(queried_definitions).to be == requested_definitions + expect(queried_definitions).to eq requested_definitions end end diff --git a/spec/mongoid/validatable/associated_spec.rb b/spec/mongoid/validatable/associated_spec.rb index 83eb52dd9..180a73a93 100644 --- a/spec/mongoid/validatable/associated_spec.rb +++ b/spec/mongoid/validatable/associated_spec.rb @@ -75,23 +75,20 @@ end it 'does not run validation on them' do - expect(description).to_not receive(:valid?) expect(user).to be_valid end - end - end end - describe '#validate_each' do + describe '#validate' do let(:person) do Person.new end let(:validator) do - described_class.new(attributes: person.attributes) + described_class.new(attributes: person.relations.keys) end context 'when the association is a one to one' do @@ -99,7 +96,7 @@ context 'when the association is nil' do before do - validator.validate_each(person, :name, nil) + validator.validate(person) end it 'adds no errors' do @@ -108,14 +105,9 @@ end context 'when the association is valid' do - - let(:associated) do - double(valid?: true, flagged_for_destroy?: false) - end - before do - expect(associated).to receive(:validated?).and_return(false) - validator.validate_each(person, :name, associated) + person.name = Name.new(first_name: 'A', last_name: 'B') + validator.validate(person) end it 'adds no errors' do @@ -125,13 +117,9 @@ context 'when the association is invalid' do - let(:associated) do - double(valid?: false, flagged_for_destroy?: false) - end - before do - expect(associated).to receive(:validated?).and_return(false) - validator.validate_each(person, :name, associated) + person.name = Name.new(first_name: 'Jamis', last_name: 'Buck') + validator.validate(person) end it 'adds errors to the parent document' do @@ -149,7 +137,7 @@ context 'when the association is empty' do before do - validator.validate_each(person, :addresses, []) + validator.validate(person) end it 'adds no errors' do @@ -159,13 +147,9 @@ context 'when the association has invalid documents' do - let(:associated) do - double(valid?: false, flagged_for_destroy?: false) - end - before do - expect(associated).to receive(:validated?).and_return(false) - validator.validate_each(person, :addresses, [associated]) + person.addresses << Address.new(street: '123') + validator.validate(person) end it 'adds errors to the parent document' do @@ -175,13 +159,10 @@ context 'when the association has all valid documents' do - let(:associated) do - double(valid?: true, flagged_for_destroy?: false) - end - before do - expect(associated).to receive(:validated?).and_return(false) - validator.validate_each(person, :addresses, [associated]) + person.addresses << Address.new(street: '123 First St') + person.addresses << Address.new(street: '456 Second St') + validator.validate(person) end it 'adds no errors' do diff --git a/spec/mongoid/warnings_spec.rb b/spec/mongoid/warnings_spec.rb index 1735d1bbb..85dd62923 100644 --- a/spec/mongoid/warnings_spec.rb +++ b/spec/mongoid/warnings_spec.rb @@ -16,19 +16,19 @@ before do warn_id = id described_class.class_eval do - instance_variable_set("@#{warn_id}", false) + instance_variable_set(:"@#{warn_id}", false) end end it 'logs the warning' do expect_any_instance_of(Logger).to receive(:warn).once.with(message) - described_class.send("warn_#{id}") + described_class.send(:"warn_#{id}") end it 'logs the warning only once' do expect_any_instance_of(Logger).to receive(:warn).once.with(message) - described_class.send("warn_#{id}") - described_class.send("warn_#{id}") + described_class.send(:"warn_#{id}") + described_class.send(:"warn_#{id}") end end end diff --git a/spec/mongoid_spec.rb b/spec/mongoid_spec.rb index cc469b2d4..0fde18879 100644 --- a/spec/mongoid_spec.rb +++ b/spec/mongoid_spec.rb @@ -80,6 +80,32 @@ end described_class.disconnect_clients end + + it 'returns true' do + expect(described_class.disconnect_clients).to be(true) + end + end + + describe '.reconnect_clients' do + + let(:clients) do + Mongoid::Clients.clients.values + end + + before do + Band.all.entries + end + + it 'reconnects all active clients' do + clients.each do |client| + expect(client).to receive(:reconnect).and_call_original + end + described_class.reconnect_clients + end + + it 'returns true' do + expect(described_class.reconnect_clients).to be(true) + end end describe '.client' do diff --git a/spec/support/immutable_ids.rb b/spec/support/immutable_ids.rb index b35379c67..5cd072593 100644 --- a/spec/support/immutable_ids.rb +++ b/spec/support/immutable_ids.rb @@ -43,7 +43,7 @@ def immutable_id_examples_as(name) it 'allows _id to be updated' do invoke_operation! expect(object.new_record?).to be false - expect(object.reload._id).to be == new_id_value + expect(object.reload._id).to eq new_id_value end end @@ -66,7 +66,7 @@ def immutable_id_examples_as(name) it 'allows _id to be updated' do invoke_operation! expect(object.new_record?).to be false - expect(parent.reload.favorites.first._id).to be == new_id_value + expect(parent.reload.favorites.first._id).to eq new_id_value end end @@ -92,7 +92,7 @@ def immutable_id_examples_as(name) new_id = BSON::ObjectId.new expect { parent.update(pet: { _id: new_id }) }.to_not raise_error - expect(parent.reload.pet._id.to_s).to be == original_id.to_s + expect(parent.reload.pet._id.to_s).to eq original_id.to_s end end diff --git a/spec/support/macros.rb b/spec/support/macros.rb index a99b331ef..fb615cad6 100644 --- a/spec/support/macros.rb +++ b/spec/support/macros.rb @@ -22,11 +22,11 @@ def config_override(key, value) around do |example| existing = Mongoid.send(key) - Mongoid.send("#{key}=", value) + Mongoid.send(:"#{key}=", value) example.run - Mongoid.send("#{key}=", existing) + Mongoid.send(:"#{key}=", existing) end end @@ -44,11 +44,11 @@ def driver_config_override(key, value) around do |example| existing = Mongo.send(key) - Mongo.send("#{key}=", value) + Mongo.send(:"#{key}=", value) example.run - Mongo.send("#{key}=", existing) + Mongo.send(:"#{key}=", existing) end end @@ -96,9 +96,9 @@ def persistence_context_override(component, value) around do |example| meth = "#{component}_override" old_value = Mongoid::Threaded.send(meth) - Mongoid::Threaded.send("#{meth}=", value) + Mongoid::Threaded.send(:"#{meth}=", value) example.run - Mongoid::Threaded.send("#{meth}=", old_value) + Mongoid::Threaded.send(:"#{meth}=", old_value) end end diff --git a/spec/support/models/account_autosave.rb b/spec/support/models/account_autosave.rb new file mode 100644 index 000000000..d1c2873a6 --- /dev/null +++ b/spec/support/models/account_autosave.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class AccountAutosave + include Mongoid::Document + + field :_id, type: String, overwrite: true, default: -> { name.try(:parameterize) } + + field :number, type: String + field :balance, type: Integer + field :nickname, type: String + field :name, type: String + field :balanced, type: Mongoid::Boolean, default: -> { balance? } + + field :overridden, type: String + + embeds_many :memberships + belongs_to :creator, class_name: 'User' + belongs_to :person, class_name: 'PersonAutosave' + has_many :alerts, autosave: false + has_and_belongs_to_many :agents + has_one :comment, validate: false + + validates_presence_of :name + validates_presence_of :nickname, on: :upsert + validates_length_of :name, maximum: 10, on: :create + + def overridden + self[:overridden] = 'not recommended' + end + + # MONGOID-3365 + field :period_started_at, type: Time + has_many :consumption_periods, dependent: :destroy, validate: false + + def current_consumption + consumption_periods.find_or_create_by(started_at: period_started_at) + end +end diff --git a/spec/support/models/drug_autosave.rb b/spec/support/models/drug_autosave.rb new file mode 100644 index 000000000..5bf288ed6 --- /dev/null +++ b/spec/support/models/drug_autosave.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class DrugAutosave + include Mongoid::Document + field :name, type: String + field :generic, type: Mongoid::Boolean + belongs_to :person, class_name: 'PersonAutosave', inverse_of: nil, counter_cache: true +end diff --git a/spec/support/models/name.rb b/spec/support/models/name.rb index 64e422bc2..e81c24831 100644 --- a/spec/support/models/name.rb +++ b/spec/support/models/name.rb @@ -4,6 +4,8 @@ class Name include Mongoid::Document include Mongoid::Attributes::Dynamic + validate :is_not_jamis + field :_id, type: String, overwrite: true, default: lambda { "#{first_name}-#{last_name}" } @@ -23,4 +25,12 @@ class Name def set_parent=(set = false) self.parent_title = namable.title if set end + + private + + def is_not_jamis + return unless first_name == 'Jamis' && last_name == 'Buck' + + errors.add(:base, :invalid) + end end diff --git a/spec/support/models/person_autosave.rb b/spec/support/models/person_autosave.rb new file mode 100644 index 000000000..e17306e96 --- /dev/null +++ b/spec/support/models/person_autosave.rb @@ -0,0 +1,236 @@ +# frozen_string_literal: true + +class PersonAutosave + include Mongoid::Document + include Mongoid::Attributes::Dynamic + attr_accessor :mode + + class_attribute :somebody_elses_important_class_options + self.somebody_elses_important_class_options = { keep_me_around: true } + + field :username, default: -> { "arthurnn#{rand(0..10)}" } + field :title + field :terms, type: Mongoid::Boolean + field :pets, type: Mongoid::Boolean, default: false + field :age, type: Integer, default: '100' + field :dob, type: Date + field :employer_id + field :lunch_time, type: Time + field :aliases, type: Array + field :map, type: Hash + field :map_with_default, type: Hash, default: {} + field :score, type: Integer + field :blood_alcohol_content, type: Float, default: -> { 0.0 } + field :last_drink_taken_at, type: Date, default: -> { 1.day.ago.in_time_zone('Alaska') } + field :ssn + field :owner_id, type: Integer + field :security_code + field :reading, type: Object + field :bson_id, type: BSON::ObjectId + field :pattern, type: Regexp + field :override_me, type: Integer + field :at, as: :aliased_timestamp, type: Time + field :t, as: :test, type: String + field :i, as: :inte, type: Integer + field :a, as: :array, type: Array + field :desc, localize: true + field :localized_translations, localize: true + field :test_array, type: Array + field :overridden_setter, type: String + field :arrays, type: Array + field :range, type: Range + field :species, type: Symbol + field :posts_count, type: Integer, default: 0 + + index age: 1 + index addresses: 1 + index dob: 1 + index name: 1 + index title: 1 + + attr_reader :rescored + + embeds_many :favorites, order: :title.desc, inverse_of: :perp, validate: false + embeds_many :videos, order: [%i[title asc]], validate: false + embeds_many :phone_numbers, class_name: 'Phone', validate: false + embeds_many :phones, store_as: :mobile_phones, validate: false + embeds_many :addresses, as: :addressable, validate: false do + def extension + 'Testing' + end + + def find_by_street(street) + where(street: street).first + end + end + + embeds_many :address_components, validate: false + embeds_many :services, cascade_callbacks: true, validate: false + embeds_many :symptoms, validate: false + embeds_many :appointments, validate: false + embeds_many :messages, validate: false + + embeds_one :passport, autobuild: true, store_as: :pass, validate: false + embeds_one :purse, store_as: 'Purse' + embeds_one :pet, class_name: 'Animal', validate: false + embeds_one :name, as: :namable, validate: false do + def extension + 'Testing' + end + + def dawkins? + first_name == 'Richard' && last_name == 'Dawkins' + end + end + embeds_one :quiz, validate: false + + # Must have dependent: :destroy + has_one :game, dependent: :destroy, validate: false do + def extension + 'Testing' + end + end + + has_many \ + :posts, + dependent: :delete_all, + validate: false do + def extension + 'Testing' + end + end + has_many :ordered_posts, order: :rating.desc, validate: false + has_and_belongs_to_many \ + :preferences, + index: true, + dependent: :nullify, + validate: false + has_and_belongs_to_many :user_accounts, validate: false + has_and_belongs_to_many :houses, validate: false + has_and_belongs_to_many :ordered_preferences, order: :value.desc, validate: false + + has_many :drugs, class_name: 'DrugAutosave', validate: false + # Must not have dependent: :destroy + has_one :account, class_name: 'AccountAutosave', validate: false + has_one :cat, dependent: :nullify, validate: false, primary_key: :username + has_one :book, autobuild: true, validate: false + has_one :home, dependent: :delete_all, validate: false + + has_and_belongs_to_many \ + :administrated_events, + class_name: 'Event', + inverse_of: :administrators, + dependent: :nullify, + validate: false + + belongs_to :mother, class_name: 'PersonAutosave' + has_many :children, class_name: 'PersonAutosave' + + accepts_nested_attributes_for :addresses + accepts_nested_attributes_for :name, update_only: true + accepts_nested_attributes_for :pet, allow_destroy: true + accepts_nested_attributes_for :game, allow_destroy: true + accepts_nested_attributes_for :favorites, allow_destroy: true, limit: 5 + accepts_nested_attributes_for :posts + accepts_nested_attributes_for :preferences + accepts_nested_attributes_for :quiz + accepts_nested_attributes_for :services, allow_destroy: true + + scope :minor, -> { where(:age.lt => 18) } + scope :without_ssn, -> { without(:ssn) } + scope :search, ->(query) { any_of({ title: query }) } + + def self.older_than(age:) + where(:age.gt => age) + end + + def score_with_rescoring=(score) + @rescored = score.to_i + 20 + self.score_without_rescoring = score + end + + alias_method :score_without_rescoring=, :score= + alias_method :score=, :score_with_rescoring= + + def update_addresses + addresses.each do |address| + address.street = 'Updated Address' + end + end + + def employer=(emp) + self.employer_id = emp.id + end + + def overridden_addresses=(addresses) + self.addresses = addresses + end + + def override_me + read_attribute(:override_me).to_s + end + + def overridden_setter=(value) + @override_called = true + super(value) + end + + class << self + def accepted + scoped.where(terms: true) + end + + def knight + scoped.where(title: 'Sir') + end + + def old + scoped.where(age: { '$gt' => 50 }) + end + end + + def reject_if_city_is_empty(attrs) + attrs[:city].blank? + end + + def reject_if_name_is_blank(attrs) + attrs[:first_name].blank? + end + + def foo + 'i_am_foo' + end + + def preference_names=(names) + names.split(',').each do |name| + preference = Preference.where(name: name).first + if preference + preferences << preference + else + preferences.build(name: name) + end + end + end + + def overridden_map_with_default=(value) + map_with_default['key'] = value + end + + def set_personal_data(ssn:, age:) + self.ssn = ssn + self.age = age + end + + reset_callbacks(:validate) + reset_callbacks(:create) + reset_callbacks(:save) + reset_callbacks(:destroy) + + private + + def secret_name + 'secret' + end +end + +require 'support/models/doctor' diff --git a/upload-api-docs b/upload-api-docs index a24d24613..589a23355 100755 --- a/upload-api-docs +++ b/upload-api-docs @@ -7,7 +7,7 @@ gemfile true do source 'https://rubygems.org' gem 'nokogiri' gem 'aws-sdk-s3' - gem 'yard' + gem 'yard', '>= 0.9.35' end require 'aws-sdk-s3' @@ -29,6 +29,7 @@ class FileUploader end def upload_docs + puts "Uploading to #{@bucket}" Dir.glob("#{@docs_path}/**/*").each do |file| next if File.directory?(file) @@ -115,6 +116,12 @@ def generate_docs(options) '--readme', './README.md', '-o', options[:docs_path] ) + + begin + File.delete(File.join(options[:docs_path], 'frames.html')) + rescue StandardError + nil + end end options = Options.new