diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..42afacfef2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,131 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement through GitHub. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/docs/index.txt b/docs/index.txt index 76660e556e..0f320dd552 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -6,8 +6,9 @@ Mongoid .. default-domain:: mongodb -Mongoid is the officially supported object-document mapper (ODM) -for MongoDB in Ruby. +Mongoid is the officially supported object-document mapper (ODM) for MongoDB in +Ruby. To work with Mongoid from the command line using ``rails``-like tooling, +the `railsmdb `_ utility can be used. .. toctree:: :titlesonly: @@ -21,6 +22,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 7247273d61..2f4ecf0b4a 100644 --- a/docs/installation.txt +++ b/docs/installation.txt @@ -32,16 +32,33 @@ To install the gem with bundler, include the following in your ``Gemfile``: 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.. +By using the `railsmdb CLI `_ a new +Ruby on Rails application can be quickly generated using the same options as +``rails new``, but configured to work with MongoDB: + +.. code-block:: sh + + railsmdb new my_new_rails_app + +The ``rails`` CLI can also be used, however when creating a new Rails application +and where Mongoid will be used for data access, provide the ``--skip-active-record`` +flag to the ``rails new`` command to avoid depending on and configuring ActiveRecord. + +Additional examples can be found in the `tutorials `_. Using Mongoid with an Existing Rails Application ================================================ -When converting an existing Rails application to use Mongoid for data access, -the ``config/application.rb`` file needs to be updated to remove the -``require 'rails/all'`` line and explicitly include the required frameworks -(which could be all of the frameworks provided by Rails with the exception of -ActiveRecord). Any references to ActiveRecord in files in the ``config`` -directory and in the models also need to be removed. +Using the `railsmdb CLI `_ an existing +Rails application can easily be configured for use with Mongoid: + +.. code-block:: sh + + railsmdb setup + +Converting an existing Rails application without using ``railsmdb`` can be done +by updating the ``config/application.rb`` file to remove the ``require 'rails/all'`` +line and explicitly include the required frameworks (which could be all of the +frameworks provided by Rails with the exception ofActiveRecord). +Any references to ActiveRecord in files in the ``config`` directory and in the +models also need to be removed. diff --git a/docs/reference/configuration.txt b/docs/reference/configuration.txt index 92b1a44e21..93e2581773 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 diff --git a/docs/reference/fields.txt b/docs/reference/fields.txt index d5046b2aec..9ac33251c1 100644 --- a/docs/reference/fields.txt +++ b/docs/reference/fields.txt @@ -887,7 +887,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: @@ -1076,7 +1076,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 ]``. @@ -1199,7 +1199,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 b8dd5c9026..7cef79b94d 100644 --- a/docs/reference/queries.txt +++ b/docs/reference/queries.txt @@ -1151,7 +1151,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 4c7181cff0..2f8f02ace7 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 ab451bf4af..675e62cae0 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 c962b3667d..b7c3013de6 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 9f2996bede..95e6b55763 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 fa9c74160d..653eb4dc20 100644 --- a/docs/release-notes/mongoid-9.0.txt +++ b/docs/release-notes/mongoid-9.0.txt @@ -17,6 +17,70 @@ The complete list of releases is available `on GitHub please consult GitHub releases for detailed release notes and JIRA for the complete list of issues fixed in each release, including bug fixes. +Railsmdb for Mongoid +-------------------- + +To coincide with the release of Mongoid 9.0 a new command-line utility for creating, +updating, managing, and maintaining Rails applications has also +been made generally available! + +``railsmdb`` makes it easier to work with MongoDB from the command line through +common generators that Ruby on Rails developers are already familiar with. + +For example, you can use ``railsmdb`` to generate stubs for new Mongoid models: + +.. code-block:: sh + + $ bin/railsmdb generate model person + +This will create a new model at ``app/models/person.rb``: + +.. code-block:: ruby + + class Person + include Mongoid::Document + include Mongoid::Timestamp + end + +You can specify the fields of the model as well: + +.. code-block:: ruby + + # bin/railsmdb generate model person name:string birth:date + + class Person + include Mongoid::Document + include Mongoid::Timestamp + field :name, type: String + field :birth, type: Date + end + +You can instruct the generator to make the new model a subclass of another, +by passing the ``--parent`` option: + +.. code-block:: ruby + + # bin/railsmdb generate model student --parent=person + + class Student < Person + include Mongoid::Timestamp + end + +And if you need to store your models in a different collection than can be +inferred from the model name, you can specify ``--collection``: + +.. code-block:: ruby + + # bin/railsmdb generate model course --collection=classes + + class Course + include Mongoid::Document + include Mongoid::Timestamp + store_in collection: 'classes' + end + +For more information see the `GitHub Repository `_. + Support for Ruby 2.6 and JRuby 9.3 Dropped ------------------------------------------- @@ -48,10 +112,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 @@ -89,11 +154,16 @@ prior has been dropped (you must use a minimum of version 8.0.) Deprecated functionality 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. +- The deprecated class ``Mongoid::Errors::InvalidStorageParent`` has been removed. ``touch`` method now clears changed state @@ -103,22 +173,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 ------------------------------ @@ -130,8 +200,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 -------------------- @@ -140,15 +211,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. @@ -189,11 +260,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 @@ -211,17 +282,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`` @@ -240,25 +311,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 @@ -273,12 +344,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``. @@ -310,7 +381,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. @@ -324,18 +395,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 @@ -347,32 +418,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 @@ -384,23 +455,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 @@ -410,16 +481,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 @@ -433,33 +504,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 @@ -467,10 +538,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 @@ -487,9 +558,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 1ae90c1cfc..db27af622a 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 dd7ce2890b..4da8825950 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/gem-public_cert.pem b/gem-public_cert.pem index ab7c15c7ce..68fa1da731 100644 --- a/gem-public_cert.pem +++ b/gem-public_cert.pem @@ -1,7 +1,7 @@ -----BEGIN CERTIFICATE----- MIIEeDCCAuCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBBMREwDwYDVQQDDAhkYngt cnVieTEXMBUGCgmSJomT8ixkARkWB21vbmdvZGIxEzARBgoJkiaJk/IsZAEZFgNj -b20wHhcNMjMwMTMxMTE1NjM1WhcNMjQwMTMxMTE1NjM1WjBBMREwDwYDVQQDDAhk +b20wHhcNMjQwMjA5MTc0NzIyWhcNMjUwMjA4MTc0NzIyWjBBMREwDwYDVQQDDAhk YngtcnVieTEXMBUGCgmSJomT8ixkARkWB21vbmdvZGIxEzARBgoJkiaJk/IsZAEZ FgNjb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC0/Veq9l47cTfX tQ+kHq2NOCwJuJGt1iXWQ/vH/yp7pZ/bLej7gPDl2CfIngAXRjM7r1FkR9ya7VAm @@ -14,13 +14,13 @@ VYSiCHuc3yEDyq5M+98WGX2etbj6esYtzI3rDevpIAHPB6HQmtoJIA4dSl3gjFb+ D+YQSuB2qYu021FI9zeY9sbZyWysEXBxhwrmTk+XUV0qz+OQZkMCAwEAAaN7MHkw CQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFH4nnr4tYlatU57RbExW jG86YM5nMB8GA1UdEQQYMBaBFGRieC1ydWJ5QG1vbmdvZGIuY29tMB8GA1UdEgQY -MBaBFGRieC1ydWJ5QG1vbmdvZGIuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQAVSlgM -nFDWCCNLOCqG5/Lj4U62XoALkdCI+OZ30+WrA8qiRLSL9ZEziVK9AV7ylez+sriQ -m8XKZKsCN5ON4+zXw1S+6Ftz/R4zDg7nTb9Wgw8ibzsoiP6e4pRW3Fls3ZdaG4pW -+qMTbae9OiSrgI2bxNTII+v+1FcbQjOlMu8HPZ3ZfXnurXPgN5GxSyyclZI1QONO -HbUoKHRirZu0F7JCvQQq4EkSuLWPplRJfYEeJIYm05zhhFeEyqea2B/TTlCtXa42 -84vxXsxGzumuO8F2Q9m6/p95sNhqCp0B/SkKXIrRGJ7FBzupoORNRXHviS2OC3ty -4lwUzOlLTF/yO0wwYYfmtQOALQwKnW838vbYthMXvTjxB0EgVZ5PKto99WbjsXzy -wkeAWhd5b+5JS0zgDL4SvGB8/W2IY+y0zELkojBMgJPyrpAWHL/WSsSBMuhyI2Pv -xxaBVLklnJJ/qCCOZ3lG2MyVc/Nb0Mmq8ygWNsfwHmKKYuuWcviit0D0Tek= +MBaBFGRieC1ydWJ5QG1vbmdvZGIuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQBKGtHA +fpi3N/BL1J5O4CBsAjtF4jHDiw2r5MwK+66NzMh3uedjgPI7MoosemLy++SB+8BR +SE8bDkb6gfDQQzrI6KSXXyqH2TbQXpY5Tac7/yqXRiu8G2qOrOj4czB/Hq7j09CV +YoH88v6hL11i5jt6jPjFh8hXYG0hDQxhi3atRz5Wwd98tUf2DSbyJXJiRgCBeZjl +rP7AnKsWMu0C+zPlL+nXtQr+nTFtkKXRWfUJMqePpBqtriQvgQ+Y1ItqYVTSLuiM +iwUMcn/rGhdCMBSaKDXdFkIveCHQE2f2WBo2EdErrcTrgEKYYdNfzcb/43j7L1kx +AUwyTtk+HFrviBynQbKN82rjbZE+5gukVea5c7idQPkqacPYsoU37DI+hTlUyJkV +dcTtfEg44lLlfNukBslfiQf54r+uWbyB0m0rDUN/py7/Ghyzt5GLBU91uCO3dGoI +55uFRHMvEcJMTDeImC/nuucPCAiEGMHggr9+NPC0tqpxjGKTo7lS7GzUFjg= -----END CERTIFICATE----- diff --git a/gemfiles/driver_master.gemfile b/gemfiles/driver_master.gemfile index 0622627985..2daf4c8a13 100644 --- a/gemfiles/driver_master.gemfile +++ b/gemfiles/driver_master.gemfile @@ -1,7 +1,7 @@ # rubocop:todo all source "https://rubygems.org" -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' diff --git a/gemfiles/driver_stable.gemfile b/gemfiles/driver_stable.gemfile index f28c5be54a..8df367525e 100644 --- a/gemfiles/driver_stable.gemfile +++ b/gemfiles/driver_stable.gemfile @@ -1,7 +1,7 @@ # rubocop:todo all source "https://rubygems.org" -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' diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index 59cdf8d0ff..e89e2ae899 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -3,7 +3,7 @@ def standard_dependencies gem 'rake' group :development do - gem 'yard' + gem 'yard', '>= 0.9.35' platform :mri do # Debugger for VSCode. diff --git a/lib/config/locales/en.yml b/lib/config/locales/en.yml index d344a6a466..776959251e 100644 --- a/lib/config/locales/en.yml +++ b/lib/config/locales/en.yml @@ -715,3 +715,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 8561c73463..7bc8ac8bfb 100644 --- a/lib/mongoid.rb +++ b/lib/mongoid.rb @@ -4,7 +4,6 @@ require "forwardable" require "time" require "set" -require "ruby2_keywords" require "active_support" require "active_support/core_ext" diff --git a/lib/mongoid/atomic.rb b/lib/mongoid/atomic.rb index 7dc970c537..3401f339ac 100644 --- a/lib/mongoid/atomic.rb +++ b/lib/mongoid/atomic.rb @@ -179,11 +179,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/clients/sessions.rb b/lib/mongoid/clients/sessions.rb index b859069789..9bf4f60b31 100644 --- a/lib/mongoid/clients/sessions.rb +++ b/lib/mongoid/clients/sessions.rb @@ -59,6 +59,8 @@ def with_session(options = {}) else raise ex end + rescue *transactions_not_supported_exceptions + raise Mongoid::Errors::TransactionsNotSupported ensure Threaded.clear_session(client: persistence_context.client) end @@ -90,6 +92,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 @@ -150,6 +154,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) @@ -233,6 +253,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) + + # if not, 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. + if session.nil? + # 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 end diff --git a/lib/mongoid/document.rb b/lib/mongoid/document.rb index e55d932c01..cf70ff9552 100644 --- a/lib/mongoid/document.rb +++ b/lib/mongoid/document.rb @@ -135,33 +135,6 @@ def as_document BSON::Document.new(as_attributes) end - # Calls #as_json on the document with additional, Mongoid-specific options. - # - # @note Rails 6 changes return value of as_json for non-primitive types - # such as BSON::ObjectId. In Rails <= 5, as_json returned these as - # instances of the class. In Rails 6, these are returned serialized to - # primitive types (e.g. {'$oid'=>'5bcfc40bde340b37feda98e9'}). - # See https://github.com/rails/rails/commit/2e5cb980a448e7f4ab00df6e9ad4c1cc456616aa - # for more information. - # - # @example Get the document as json. - # document.as_json(compact: true) - # - # @param [ Hash ] options The options. - # - # @option options [ true | false ] :compact (Deprecated) Whether to include fields - # with nil values in the json document. - # - # @return [ Hash ] The document as json. - def as_json(options = nil) - rv = super - if options && options[:compact] - Mongoid::Warnings.warn_as_json_compact_deprecated - rv = rv.compact - end - rv - end - # Returns an instance of the specified class with the attributes, # errors, and embedded documents of the current document. # diff --git a/lib/mongoid/errors/mongoid_error.rb b/lib/mongoid/errors/mongoid_error.rb index 828c44dbb2..d15e411af7 100644 --- a/lib/mongoid/errors/mongoid_error.rb +++ b/lib/mongoid/errors/mongoid_error.rb @@ -28,9 +28,9 @@ def compose_message(key, attributes = {}) @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/persistable/creatable.rb b/lib/mongoid/persistable/creatable.rb index bf4f89f80a..c519dcb177 100644 --- a/lib/mongoid/persistable/creatable.rb +++ b/lib/mongoid/persistable/creatable.rb @@ -105,6 +105,7 @@ def prepare_insert(options = {}) raise Errors::ReadonlyDocument.new(self.class) if readonly? && !Mongoid.legacy_readonly 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 f31ae88cd4..0ba8dc08a6 100644 --- a/lib/mongoid/persistable/updatable.rb +++ b/lib/mongoid/persistable/updatable.rb @@ -99,6 +99,7 @@ def init_atomic_updates 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) process_flagged_destroys diff --git a/lib/mongoid/railtie.rb b/lib/mongoid/railtie.rb index 14012a7e05..7a97d59172 100644 --- a/lib/mongoid/railtie.rb +++ b/lib/mongoid/railtie.rb @@ -120,9 +120,9 @@ def handle_configuration_error(e) # 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/validatable.rb b/lib/mongoid/validatable.rb index e43fe432f2..ce7f5c4c1d 100644 --- a/lib/mongoid/validatable.rb +++ b/lib/mongoid/validatable.rb @@ -38,6 +38,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 f58b36a9ad..b811cf1324 100644 --- a/lib/mongoid/validatable/associated.rb +++ b/lib/mongoid/validatable/associated.rb @@ -16,32 +16,110 @@ 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 [ 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) unless valid + end + + private + + # 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 - document.errors.add(attribute, :invalid, **options) unless valid + 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/warnings.rb b/lib/mongoid/warnings.rb index 0a41abf3e9..79333dcfcf 100644 --- a/lib/mongoid/warnings.rb +++ b/lib/mongoid/warnings.rb @@ -30,7 +30,6 @@ def warning(id, message) end warning :geo_haystack_deprecated, 'The geoHaystack type is deprecated.' - warning :as_json_compact_deprecated, '#as_json :compact option is deprecated. Please call #compact on the returned Hash object instead.' warning :symbol_type_deprecated, 'The BSON Symbol type is deprecated by MongoDB. Please use String or StringifiedSymbol field types instead of the Symbol field type.' warning :legacy_readonly, 'The readonly! method will only mark the document readonly when the legacy_readonly feature flag is switched off.' warning :mutable_ids, 'Ignoring updates to immutable attribute `_id`. Please set Mongoid::Config.immutable_ids to true and update your code so that `_id` is never updated.' diff --git a/mongoid.gemspec b/mongoid.gemspec index d6266ea5c4..c364c9fc92 100644 --- a/mongoid.gemspec +++ b/mongoid.gemspec @@ -42,20 +42,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 is recommended for handling argument delegation issues, - # especially if support for 2.6 or prior is required. - # See https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/#delegation - # - # We have a bunch of complex delegation logic, including various method_missings. - # If we try to fix them "right", it will add too much logic. We will have to - # handle different Ruby versions (including minor ones, Ruby 2.6 and 2.7 - # behave differently), hash key types (strings vs symbols), ways of passing - # arguments (with curly braces vs without ones). - # - # Therefore, usage of this gem looks like a reasonable solution at the moment. - 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(CHANGELOG.md LICENSE README.md Rakefile) s.test_files = Dir.glob("spec/**/*") 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 e6d269e8e4..838b4533fc 100644 --- a/spec/integration/associations/has_and_belongs_to_many_spec.rb +++ b/spec/integration/associations/has_and_belongs_to_many_spec.rb @@ -3,6 +3,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 @@ -19,4 +41,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 + image_block = 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! }.not_to raise_error + end + end end diff --git a/spec/mongoid/clients/sessions_spec.rb b/spec/mongoid/clients/sessions_spec.rb index 87783dd1ca..eba32d8ea9 100644 --- a/spec/mongoid/clients/sessions_spec.rb +++ b/spec/mongoid/clients/sessions_spec.rb @@ -4,6 +4,16 @@ require "spec_helper" describe Mongoid::Clients::Sessions do + let(:buffer) { StringIO.new } + let(:logger) { ::Logger.new(buffer, Logger::DEBUG) } + + 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 @@ -232,6 +242,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).not_to include("used within another client's session") + end end context 'when the other class uses a different client' do @@ -260,6 +274,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/criteria_spec.rb b/spec/mongoid/criteria_spec.rb index 69d1dbb3f6..65d1ca8092 100644 --- a/spec/mongoid/criteria_spec.rb +++ b/spec/mongoid/criteria_spec.rb @@ -288,7 +288,6 @@ Band.where(name: "Depeche Mode") end - it "returns the criteria as a json hash" do expect(criteria.as_json).to eq([ band.serializable_hash.as_json ]) end diff --git a/spec/mongoid/document_spec.rb b/spec/mongoid/document_spec.rb index 938ac8a8fa..03915b1669 100644 --- a/spec/mongoid/document_spec.rb +++ b/spec/mongoid/document_spec.rb @@ -429,35 +429,6 @@ class << self; attr_accessor :name; end end end end - - context "when the Mongoid-specific options are provided" do - - let(:options) do - { compact: true } - end - - it "applies the Mongoid-specific options" do - expect(person.as_json(options)["title"]).to eq("Sir") - expect(person.as_json(options)["age"]).to eq(100) - expect(person.as_json(options).keys).not_to include("lunch_time") - end - - context "when options for the super method are provided" do - - let(:options) do - { compact: true, only: [:title, :pets, :ssn] } - end - - it "passes the options through to the super method" do - expect(person.as_json(options)["title"]).to eq("Sir") - expect(person.as_json(options)["pets"]).to eq(false) - end - - it "applies the Mongoid-specific options" do - expect(person.as_json(options).keys).not_to include("ssn") - end - end - end end describe "#as_document" do diff --git a/spec/mongoid/tasks/database_spec.rb b/spec/mongoid/tasks/database_spec.rb index 91a0ecc9a7..3758282040 100644 --- a/spec/mongoid/tasks/database_spec.rb +++ b/spec/mongoid/tasks/database_spec.rb @@ -231,44 +231,32 @@ class Note end context 'when wait is true' do - before do - allow(described_class).to receive(:wait_for_search_indexes) - described_class.create_search_indexes([ searchable_model_spy ], wait: true) - end - it 'invokes both create_search_indexes and wait_for_search_indexes' do - expect(searchable_model_spy).to have_received(:create_search_indexes) - expect(described_class).to have_received(:wait_for_search_indexes).with(searchable_model_spy => index_names) + expect(searchable_model_spy).to receive(:create_search_indexes) + expect(described_class).to receive(:wait_for_search_indexes).with({ searchable_model_spy => index_names }) + + described_class.create_search_indexes([searchable_model_spy], wait: true) end end context 'when wait is false' do - before do - allow(described_class).to receive(:wait_for_search_indexes) - described_class.create_search_indexes([ searchable_model_spy ], wait: false) - end - it 'invokes only create_search_indexes' do - expect(searchable_model_spy).to have_received(:create_search_indexes) - expect(described_class).not_to have_received(:wait_for_search_indexes) + expect(searchable_model_spy).to receive(:create_search_indexes) + expect(described_class).to_not receive(:wait_for_search_indexes) + + described_class.create_search_indexes([searchable_model_spy], wait: false) end end end describe '.remove_search_indexes' do - before do + it 'calls remove_search_indexes on all non-embedded models' do models.each do |model| - allow(model).to receive(:remove_search_indexes) unless model.embedded? + expect(model).to receive(:remove_search_indexes) unless model.embedded? end described_class.remove_search_indexes(models) end - - it 'calls remove_search_indexes on all non-embedded models' do - models.each do |model| - expect(model).to have_received(:remove_search_indexes) unless model.embedded? - end - end end describe ".undefined_indexes" do diff --git a/spec/mongoid/validatable/associated_spec.rb b/spec/mongoid/validatable/associated_spec.rb index 4a86e9fbb9..b9b72b7dc1 100644 --- a/spec/mongoid/validatable/associated_spec.rb +++ b/spec/mongoid/validatable/associated_spec.rb @@ -76,7 +76,6 @@ end it "does not run validation on them" do - expect(description).to receive(:valid?).never expect(user).to be_valid end @@ -85,14 +84,14 @@ 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 @@ -100,7 +99,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 @@ -109,14 +108,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 @@ -126,13 +120,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 @@ -150,7 +140,7 @@ context "when the association is empty" do before do - validator.validate_each(person, :addresses, []) + validator.validate(person) end it "adds no errors" do @@ -160,13 +150,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 @@ -176,13 +162,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/shared b/spec/shared index 53a38fe8f1..cee4bc0264 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit 53a38fe8f165fd71687cf6205d2f1aac0dbde10b +Subproject commit cee4bc02649a573c8256b0505c1d23f503ac2609 diff --git a/spec/support/models/name.rb b/spec/support/models/name.rb index 83a43c71fd..9083504b78 100644 --- a/spec/support/models/name.rb +++ b/spec/support/models/name.rb @@ -5,6 +5,8 @@ class Name include Mongoid::Document include Mongoid::Attributes::Dynamic + validate :is_not_jamis + field :_id, type: String, overwrite: true, default: ->{ "#{first_name}-#{last_name}" } @@ -24,4 +26,12 @@ class Name def set_parent=(set = false) self.parent_title = namable.title if set end + + private + + def is_not_jamis + if first_name == 'Jamis' && last_name == 'Buck' + errors.add(:base, :invalid) + end + end end diff --git a/upload-api-docs b/upload-api-docs index 01b0534b5d..33f36f74a0 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,11 @@ 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